Refactored Auth with Sessions #12

Merged
igorpropisnov merged 9 commits from feature/refactor-auth into main 2024-06-06 12:58:52 +02:00
10 changed files with 83 additions and 34 deletions
Showing only changes of commit d8f65f1241 - Show all commits

View File

@ -1,19 +1,21 @@
import { import {
Controller, Controller,
Post, Post,
Get,
Body, Body,
HttpCode, HttpCode,
HttpStatus, HttpStatus,
Req, Req,
UseGuards, UseGuards,
} from '@nestjs/common'; } from '@nestjs/common';
import { ApiCreatedResponse, ApiTags } from '@nestjs/swagger'; import { ApiBody, ApiCreatedResponse, ApiTags } from '@nestjs/swagger';
import { Request } from 'express'; import { Request } from 'express';
import { SessionGuard } from 'src/modules/session/guard'; import { SessionGuard } from 'src/modules/session/guard';
import { SuccessDto } from 'src/shared';
import { Public } from 'src/shared/decorator'; import { Public } from 'src/shared/decorator';
import { LocalAuthGuard } from '../guard'; import { LocalAuthGuard } from '../guard';
import { LoginResponseDto, UserCredentialsDto } from '../models/dto'; import { SigninResponseDto, UserCredentialsDto } from '../models/dto';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@ApiTags('Authentication') @ApiTags('Authentication')
@ -23,28 +25,29 @@ export class AuthController {
@ApiCreatedResponse({ @ApiCreatedResponse({
description: 'User signed up successfully', description: 'User signed up successfully',
type: LoginResponseDto, type: SuccessDto,
}) })
@Public()
@Post('signup') @Post('signup')
@HttpCode(HttpStatus.CREATED) @HttpCode(HttpStatus.CREATED)
@Public()
public async signup( public async signup(
@Body() userCredentials: UserCredentialsDto @Body() userCredentials: UserCredentialsDto
): Promise<{ success: boolean }> { ): Promise<SuccessDto> {
return this.authService.signup(userCredentials); return this.authService.signup(userCredentials);
} }
@ApiCreatedResponse({ @ApiCreatedResponse({
description: 'User signin successfully', description: 'User signin successfully',
type: LoginResponseDto, type: SigninResponseDto,
}) })
@ApiBody({ type: UserCredentialsDto })
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@Public()
@UseGuards(LocalAuthGuard) @UseGuards(LocalAuthGuard)
@Public()
@Post('signin') @Post('signin')
public async signin(@Req() request: Request): Promise<LoginResponseDto> { public async signin(@Req() request: Request): Promise<SigninResponseDto> {
return this.authService.getLoginResponse( return this.authService.getLoginResponse(
request.user as LoginResponseDto & { userAgent: string } request.user as SigninResponseDto & { userAgent: string }
); );
} }
@ -54,7 +57,21 @@ export class AuthController {
@HttpCode(HttpStatus.OK) @HttpCode(HttpStatus.OK)
@UseGuards(SessionGuard) @UseGuards(SessionGuard)
@Post('logout') @Post('logout')
public async logout(@Req() request: Request): Promise<{ success: boolean }> { public async logout(@Req() request: Request): Promise<SuccessDto> {
return this.authService.logout(request.sessionID); return this.authService.logout(request.sessionID);
} }
@ApiCreatedResponse({
description: 'Check if user is authenticated',
type: SuccessDto,
})
@HttpCode(HttpStatus.OK)
@UseGuards(SessionGuard)
@Get('status')
public status(@Req() request: Request): Promise<SuccessDto> {
return this.authService.checkAuthStatus(
request.sessionID,
request.headers['user-agent']
);
}
} }

View File

@ -1,2 +1,2 @@
export * from './user-credentials.dto'; export * from './user-credentials.dto';
export * from './login-response.dto'; export * from './signin-response.dto';

View File

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
export class LoginResponseDto { export class SigninResponseDto {
@ApiProperty({ @ApiProperty({
title: 'Email', title: 'Email',
description: 'User Email', description: 'User Email',

View File

@ -30,13 +30,4 @@ export class UserCredentialsRepository {
): Promise<UserCredentials | undefined> { ): Promise<UserCredentials | undefined> {
return this.repository.findOne({ where: { id: userId } }); return this.repository.findOne({ where: { id: userId } });
} }
// public async updateUserRefreshToken(
// userId: string,
// refreshToken: string | null
// ): Promise<number> {
// const result = await this.repository.update(userId, { refreshToken });
// return result.affected ?? 0;
// }
} }

View File

@ -7,12 +7,12 @@ import {
} from '@nestjs/common'; } from '@nestjs/common';
import { UserCredentials } from 'src/entities'; import { UserCredentials } from 'src/entities';
import { SessionService } from 'src/modules/session/services/session.service'; import { SessionService } from 'src/modules/session/services/session.service';
import { EncryptionService } from 'src/shared'; import { EncryptionService, SuccessDto } from 'src/shared';
import { PasswordConfirmationMailService } from '../../sendgrid-module/services/password-confirmation.mail.service'; import { PasswordConfirmationMailService } from '../../sendgrid-module/services/password-confirmation.mail.service';
import { UserDataRepository } from '../../user-module/repositories/user-data.repository'; import { UserDataRepository } from '../../user-module/repositories/user-data.repository';
import { EmailVerificationService } from '../../verify-module/services/email-verification.service'; import { EmailVerificationService } from '../../verify-module/services/email-verification.service';
import { LoginResponseDto, UserCredentialsDto } from '../models/dto'; import { SigninResponseDto, UserCredentialsDto } from '../models/dto';
import { UserCredentialsRepository } from '../repositories/user-credentials.repository'; import { UserCredentialsRepository } from '../repositories/user-credentials.repository';
@Injectable() @Injectable()
@ -27,7 +27,7 @@ export class AuthService {
public async signup( public async signup(
userCredentials: UserCredentialsDto userCredentials: UserCredentialsDto
): Promise<{ success: boolean }> { ): Promise<SuccessDto> {
try { try {
const existingUser = await this.userCredentialsRepository.findUserByEmail( const existingUser = await this.userCredentialsRepository.findUserByEmail(
userCredentials.email userCredentials.email
@ -64,11 +64,11 @@ export class AuthService {
} catch (error) { } catch (error) {
if (error instanceof ConflictException) { if (error instanceof ConflictException) {
throw new ConflictException( throw new ConflictException(
'Diese E-Mail-Adresse ist bereits registriert.' 'User already exists. Please try to login instead.'
); );
} else { } else {
throw new HttpException( throw new HttpException(
'Fehler bei der Registrierung', 'Error while signing up',
HttpStatus.INTERNAL_SERVER_ERROR HttpStatus.INTERNAL_SERVER_ERROR
); );
} }
@ -99,22 +99,49 @@ export class AuthService {
} catch (error) { } catch (error) {
if (error instanceof ForbiddenException) { if (error instanceof ForbiddenException) {
throw new ForbiddenException( throw new ForbiddenException(
'Die eingebenen Daten sind nicht korrekt. Bitte versuchen Sie es erneut.' 'E-Mail address or password is incorrect. Please try again.'
); );
} else { } else {
throw new HttpException( throw new HttpException(
'Fehler beim Login', 'Error while validating user credentials',
HttpStatus.INTERNAL_SERVER_ERROR HttpStatus.INTERNAL_SERVER_ERROR
); );
} }
} }
} }
public async checkAuthStatus(
sessionId: string,
userAgend: string
): Promise<SuccessDto> {
try {
const session =
await this.sessionService.findSessionBySessionId(sessionId);
if (!session) {
throw new ForbiddenException('Session not found');
}
const userAgendFromSession = JSON.parse(session.json).passport.user
.userAgent;
if (userAgendFromSession !== userAgend) {
throw new ForbiddenException('User-Agent does not match');
}
return { success: true };
} catch (error) {
throw new HttpException(
'Error while checking auth status',
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
public getLoginResponse( public getLoginResponse(
user: LoginResponseDto & { userAgent: string } user: SigninResponseDto & { userAgent: string }
): LoginResponseDto { ): SigninResponseDto {
const { id, email }: LoginResponseDto = user; const { id, email }: SigninResponseDto = user;
const responseData: LoginResponseDto = { id, email }; const responseData: SigninResponseDto = { id, email };
return responseData; return responseData;
} }

View File

@ -3,7 +3,7 @@ import { PassportStrategy } from '@nestjs/passport';
import { Request } from 'express'; import { Request } from 'express';
import { Strategy } from 'passport-local'; import { Strategy } from 'passport-local';
import { LoginResponseDto } from '../models/dto'; import { SigninResponseDto } from '../models/dto';
import { AuthService } from '../services/auth.service'; import { AuthService } from '../services/auth.service';
@Injectable() @Injectable()
@ -20,7 +20,7 @@ export class LocalStrategy extends PassportStrategy(Strategy) {
request: Request, request: Request,
email: string, email: string,
password: string password: string
): Promise<LoginResponseDto & { userAgent: string }> { ): Promise<SigninResponseDto & { userAgent: string }> {
const user = await this.authService.validateUser(email, password); const user = await this.authService.validateUser(email, password);
if (!user) { if (!user) {

View File

@ -1,2 +1,3 @@
export * from './utils/index'; export * from './utils/index';
export * from './decorator/index'; export * from './decorator/index';
export * from './models/index';

View File

@ -0,0 +1 @@
export * from './success.dto';

View File

@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean } from 'class-validator';
export class SuccessDto {
@ApiProperty({
description: 'Success status',
type: Boolean,
})
@IsBoolean()
public success: boolean;
}

View File

@ -0,0 +1 @@
export * from './dto/index';