diff --git a/src/auth/controllers/auth.controller.ts b/src/auth/controllers/auth.controller.ts index 13168d8..b81750e 100644 --- a/src/auth/controllers/auth.controller.ts +++ b/src/auth/controllers/auth.controller.ts @@ -91,16 +91,31 @@ export class AuthController { @Get('verify-email') @UseGuards(LoginGuard) - @Render('auth/verify-email') @ApiExcludeEndpoint() - public async getVerifyEmail(@Query('code') code?: string): Promise { + public async verifyEmail(@Res() response: Response, @Query('code') code: string): Promise { if (!code) { - //TODO: Write error page. + return response.render('base/error', { + error_header: 'Invalid Verification Code', + error_message: + 'The verification code provided is invalid. Please try sending your verification email again.', + button_name: 'Go Back to Login', + button_link: '/auth/login', + }); } - return { - login: 'login', - }; + try { + await this.authService.markEmailVerified(code); + } catch (e) { + return response.render('base/error', { + error_header: 'Invalid Verification Code', + error_message: + 'The verification code provided is invalid. Please try sending your verification email again.', + button_name: 'Go Back to Login', + button_link: '/auth/login', + }); + } + + response.redirect('/auth/login'); } //TODO: Work on interaction view. diff --git a/src/auth/services/auth.service.ts b/src/auth/services/auth.service.ts index f9477d8..7a7a787 100644 --- a/src/auth/services/auth.service.ts +++ b/src/auth/services/auth.service.ts @@ -42,8 +42,11 @@ export class AuthService { }); } - await this.userService.createUser(username, email, password); - await this.mailService.sendVerificationEmail(email, '11111'); + const user = await this.userService.createUser(username, email, password); + + const emailVerificationCode = await this.generateCode(PASSWORD_RESET_CACHE_KEY); + await this.storeEmailVerifyCode(emailVerificationCode, user.id); + await this.mailService.sendVerificationEmail(email, emailVerificationCode); return { error: false, message: 'User registered' }; } @@ -180,7 +183,7 @@ export class AuthService { await this.cleanupOldEmailVerificationCode(userId); await Promise.all([ - this.redisService.set(code, userId, PASSWORD_RESET_EXPIRATION), + this.redisService.set(getEmailVerifyKey(code), userId, PASSWORD_RESET_EXPIRATION), this.redisService.set(getUserToVerifyKey(userId), code, PASSWORD_RESET_EXPIRATION), ]); } diff --git a/src/database/models/user.model.ts b/src/database/models/user.model.ts index 8475716..7a36a6d 100644 --- a/src/database/models/user.model.ts +++ b/src/database/models/user.model.ts @@ -25,8 +25,8 @@ export class User { @Column({ length: MAX_STRING_LENGTH, unique: true }) email: string; - @Column({ name: 'pending_email', length: MAX_STRING_LENGTH, nullable: true }) - pendingEmail: string | null; + @Column({ name: 'pending_email', type: String, length: MAX_STRING_LENGTH, nullable: true }) + pendingEmail?: string | null; @Column({ name: 'email_verified', default: false }) emailVerified: boolean; diff --git a/src/user/service/user.service.ts b/src/user/service/user.service.ts index b78008a..9ef6e8d 100644 --- a/src/user/service/user.service.ts +++ b/src/user/service/user.service.ts @@ -1,5 +1,6 @@ import { BadRequestException, + ForbiddenException, Injectable, NotFoundException, UnauthorizedException, @@ -13,6 +14,7 @@ import { RedisService } from '../../redis/service/redis.service'; import { Span } from 'nestjs-otel'; import { DISABLED_USER_ERROR, + EMAIL_NOT_VERIFIED_ERROR, INVALID_CREDENTIALS_ERROR, USER_NOT_FOUND_ERROR, userCacheKeyGenerate, @@ -36,7 +38,7 @@ export class UserService { * @throws NotFoundException */ @Span() - async getUserById(id: number, relations: string[] = []): Promise { + async getUserById(id: number | string, relations: string[] = []): Promise { if (this.clsService.get('authType') === 'session') { if (this.clsService.get('user').id === id) { return this.clsService.get('user'); @@ -142,9 +144,9 @@ export class UserService { async createUser(username: string, email: string, password: string): Promise { const hashedPassword = await hash(password); - const user = this.userRepository.create({ email, username, password: hashedPassword }); + const userObject = this.userRepository.create({ email, username, password: hashedPassword }); - return await this.userRepository.save(user); + return await this.userRepository.save(userObject); } /** @@ -171,6 +173,10 @@ export class UserService { throw new UnauthorizedException(INVALID_CREDENTIALS_ERROR); } + if (!user.emailVerified) { + throw new ForbiddenException(EMAIL_NOT_VERIFIED_ERROR); + } + if (user.disabled) { throw new UnauthorizedException(DISABLED_USER_ERROR); } diff --git a/src/user/user.constant.ts b/src/user/user.constant.ts index 301dfc5..3bc5c0f 100644 --- a/src/user/user.constant.ts +++ b/src/user/user.constant.ts @@ -2,6 +2,7 @@ export const USER_NOT_FOUND_ERROR = 'User not found'; export const INVALID_CREDENTIALS_ERROR = 'The email or password you entered is incorrect or the user was not found'; export const DISABLED_USER_ERROR = 'User is disabled'; +export const EMAIL_NOT_VERIFIED_ERROR = 'Email requires verification'; // Caching Constants for Redis diff --git a/views/auth/login.hbs b/views/auth/login.hbs index f77ffaa..84c7641 100644 --- a/views/auth/login.hbs +++ b/views/auth/login.hbs @@ -38,6 +38,17 @@ justify-content: center; } } + .shake { + animation: shake 0.5s; + animation-iteration-count: 1; + } + @keyframes shake { + 0% { transform: translateX(0); } + 25% { transform: translateX(-5px); } + 50% { transform: translateX(5px); } + 75% { transform: translateX(-5px); } + 100% { transform: translateX(0); } + } @@ -53,10 +64,11 @@ + + - \ No newline at end of file + diff --git a/views/base/error.hbs b/views/base/error.hbs new file mode 100644 index 0000000..139384b --- /dev/null +++ b/views/base/error.hbs @@ -0,0 +1,65 @@ + + + + + + Error Page + + + + + {{#if background_image}} +
+ {{else}} + + {{/if}} +
+
+

{{ error_header }}

+

{{ error_message }}

+ {{#if button_name}} + +
+ {{/if}} + + +