Merge branch 'main' into feature/adc/#243-leaderboard

This commit is contained in:
danis
2023-10-08 21:54:42 +02:00
92 changed files with 6324 additions and 11694 deletions

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
*/
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "email" DROP NOT NULL;

View File

@@ -19,7 +19,7 @@ model User {
id Int @id @default(autoincrement())
username String @unique
password String?
email String
email String? @unique
emailVerified Boolean @default(false)
googleID String? @unique
isGuest Boolean @default(false)

View File

@@ -19,8 +19,8 @@ import {
HttpStatus,
ParseFilePipeBuilder,
Response,
Param,
Query,
Param,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
@@ -50,6 +50,7 @@ import { SettingsService } from 'src/settings/settings.service';
import { AuthGuard } from '@nestjs/passport';
import { FileInterceptor } from '@nestjs/platform-express';
import { writeFile } from 'fs';
import { PasswordResetDto } from './dto/password_reset.dto ';
@ApiTags('auth')
@Controller('auth')
@@ -115,11 +116,31 @@ export class AuthController {
@ApiOperation({description: 'Resend the verification email'})
async reverify(@Request() req: any): Promise<void> {
const user = await this.usersService.user({ id: req.user.id });
if (!user) throw new BadRequestException("Invalid user");
if (!user) throw new BadRequestException('Invalid user');
await this.authService.sendVerifyMail(user);
}
@HttpCode(200)
@Put('password-reset')
async password_reset(
@Body() resetDto: PasswordResetDto,
@Query('token') token: string,
): Promise<void> {
if (await this.authService.changePassword(resetDto.password, token)) return;
throw new BadRequestException('Invalid token. Expired or invalid.');
}
@HttpCode(200)
@Put('forgot-password')
async forgot_password(@Query('email') email: string): Promise<void> {
console.log(email);
const user = await this.usersService.user({ email: email });
if (!user) throw new BadRequestException('Invalid user');
await this.authService.sendPasswordResetMail(user);
}
@Post('login')
@ApiBody({ type: LoginDto })
@HttpCode(200)
@UseGuards(LocalAuthGuard)
@ApiBody({ type: LoginDto })

View File

@@ -36,8 +36,9 @@ export class AuthService {
}
async sendVerifyMail(user: User) {
if (process.env.IGNORE_MAILS === "true") return;
console.log("Sending verification mail to", user.email);
if (process.env.IGNORE_MAILS === 'true') return;
if (user.email == null) return;
console.log('Sending verification mail to', user.email);
const token = await this.jwtService.signAsync(
{
userId: user.id,
@@ -48,15 +49,49 @@ export class AuthService {
to: user.email,
from: 'chromacase@octohub.app',
subject: 'Mail verification for Chromacase',
html: `To verify your mail, please click on this <a href="{${process.env.PUBLIC_URL}/verify?token=${token}">link</a>.`,
html: `To verify your mail, please click on this <a href="${process.env.PUBLIC_URL}/verify?token=${token}">link</a>.`,
});
}
async sendPasswordResetMail(user: User) {
if (process.env.IGNORE_MAILS === 'true') return;
if (user.email == null) return;
console.log('Sending password reset mail to', user.email);
const token = await this.jwtService.signAsync(
{
userId: user.id,
},
{ expiresIn: '10h' },
);
await this.emailService.sendMail({
to: user.email,
from: 'chromacase@octohub.app',
subject: 'Password reset for Chromacase',
html: `To reset your password, please click on this <a href="${process.env.PUBLIC_URL}/password_reset?token=${token}">link</a>.`,
});
}
async changePassword(new_password: string, token: string): Promise<boolean> {
let verified;
try {
verified = await this.jwtService.verifyAsync(token);
} catch (e) {
console.log('Password reset token failure', e);
return false;
}
console.log(verified)
await this.userService.updateUser({
where: { id: verified.userId },
data: { password: new_password },
});
return true;
}
async verifyMail(userId: number, token: string): Promise<boolean> {
try {
await this.jwtService.verifyAsync(token);
} catch(e) {
console.log("Verify mail token failure", e);
} catch (e) {
console.log('Verify mail token failure', e);
return false;
}
await this.userService.updateUser({

View File

@@ -0,0 +1,8 @@
import { IsNotEmpty } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class PasswordResetDto {
@ApiProperty()
@IsNotEmpty()
password: string;
}

View File

@@ -10,7 +10,7 @@ import {
} from '@nestjs/common';
import { RequestLogger, RequestLoggerOptions } from 'json-logger-service';
import { tap } from 'rxjs';
import { PrismaModel } from './_gen/prisma-class'
import { PrismaModel } from './_gen/prisma-class';
import { PrismaService } from './prisma/prisma.service';
@Injectable()
@@ -32,15 +32,14 @@ export class AspectLogger implements NestInterceptor {
};
return next.handle().pipe(
tap((data) =>
tap((/* data */) =>
console.log(
JSON.stringify({
...toPrint,
statusCode,
data,
//data, //TODO: Data crashed with images
}),
),
),
),),
);
}
}
@@ -59,7 +58,9 @@ async function bootstrap() {
.setDescription('The chromacase API')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config, { extraModels: [...PrismaModel.extraModels]});
const document = SwaggerModule.createDocument(app, config, {
extraModels: [...PrismaModel.extraModels],
});
SwaggerModule.setup('api', app, document);
app.useGlobalPipes(new ValidationPipe());

View File

@@ -6,7 +6,7 @@ export class User {
@ApiProperty()
username: string;
@ApiProperty()
email: string;
email: string | null;
@ApiProperty()
isGuest: boolean;
@ApiProperty()

View File

@@ -1,6 +1,7 @@
import {
Injectable,
InternalServerErrorException,
NotFoundException,
} from '@nestjs/common';
import { User, Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
@@ -53,7 +54,7 @@ export class UsersService {
username: `Guest ${randomUUID()}`,
isGuest: true,
// Not realyl clean but better than a separate table or breaking the api by adding nulls.
email: '',
email: null,
password: '',
},
});
@@ -89,6 +90,7 @@ export class UsersService {
// We could not find a profile icon locally, using gravatar instead.
const user = await this.user({ id: userId });
if (!user) throw new InternalServerErrorException();
if (!user.email) throw new NotFoundException();
const hash = createHash('md5')
.update(user.email.trim().toLowerCase())
.digest('hex');