Refactored Auth with Sessions #12
|
@ -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']
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
export * from './user-credentials.dto';
|
export * from './user-credentials.dto';
|
||||||
export * from './login-response.dto';
|
export * from './signin-response.dto';
|
||||||
|
|
|
@ -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',
|
|
@ -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;
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
export * from './utils/index';
|
export * from './utils/index';
|
||||||
export * from './decorator/index';
|
export * from './decorator/index';
|
||||||
|
export * from './models/index';
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './success.dto';
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './dto/index';
|
Loading…
Reference in New Issue