Skip to content

Commit

Permalink
added a optional authentication guard for find collection
Browse files Browse the repository at this point in the history
  • Loading branch information
Ratatinator97 committed Mar 14, 2024
1 parent af70abc commit dd40a74
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 180 deletions.
14 changes: 5 additions & 9 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,22 @@ import {
Get,
Inject,
Logger,
NotFoundException,
Param,
Post,
Put,
UseGuards,
ValidationPipe,
} from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';
import { User } from 'src/entities/user.entity';
import { Collection } from 'src/entities/collection.entity';
import { CollectionService } from 'src/collection/collection.service';
import { CreateUserDto } from './dto/create-user.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { EditUserDto } from './dto/edit-user.dto';
import { Notif } from 'src/entities/notification.entity';
import { usernameRegexp } from 'src/utilities/creation';
import { modifyCollectionDto } from 'src/collection/dto/collection.modify.dto';
import { AuthenticatedUser, Public, AuthGuard } from 'nest-keycloak-connect';
import { UserGuard } from './user.guard';
import { GetUser } from './get-user.decorator';
import { AuthenticatedUser } from 'nest-keycloak-connect';
@UseGuards(UserGuard)
@Controller('')
export class AuthController {
Expand All @@ -41,11 +35,13 @@ export class AuthController {
) {}

@Post('auth/signup')
@Public(false)
async signUp(
@Body(ValidationPipe) createUserDto: CreateUserDto,
@AuthenticatedUser() kcUser: any,
): Promise<void> {
this.logger.verbose(`User signin up`);
this.logger.verbose(`User sign up`);
const username = kcUser?.preferred_username || kcUser?.email;
createUserDto.username = username;
const user = await this.authService.signUp(createUserDto);
const rootId: number = await this.collectionService.createRoot(user);
await this.collectionService.createShared(user);
Expand Down
17 changes: 2 additions & 15 deletions src/auth/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,27 +7,14 @@ import {
IsNotEmpty,
IsOptional,
IsNumberString,
IsEmpty,
} from 'class-validator';
import { usernameRegexp } from 'src/utilities/creation';
import { languagesRegex } from 'src/utilities/supported.languages';

export class CreateUserDto {
@ApiProperty()
@IsString()
@MinLength(3)
@MaxLength(254)
@Matches(usernameRegexp, { message: 'Not a valid email address' })
@IsEmpty()
username: string;

@ApiProperty()
@IsString()
@MinLength(8)
@MaxLength(32)
@Matches(/((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/, {
message: 'password too weak',
})
password: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
Expand Down
21 changes: 21 additions & 0 deletions src/auth/optionnal-user.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable()
export class OptionnalUserGuard implements CanActivate {
constructor(private authService: AuthService) {}

async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// Keycloak users can have either `preferred_username` or `email`
const username = request?.user?.preferred_username || request?.user?.email;
if (username) {
const user = await this.authService.findWithUsername(username);
request.user = user;
} else {
request.user = undefined;
}
return true;
}
}
7 changes: 2 additions & 5 deletions src/auth/user.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import { AuthService } from './auth.service';
export class UserGuard implements CanActivate {
constructor(private authService: AuthService) {}

canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
console.log(request.user);
// Keycloak users can have either `preferred_username` or `email`
const username = request?.user?.preferred_username || request?.user?.email;
if (!username) {
return false;
}
const user = this.authService.findWithUsername(username);
const user = await this.authService.findWithUsername(username);
if (!user) {
return false;
}
Expand Down
115 changes: 3 additions & 112 deletions src/auth/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,50 +2,31 @@ import { Repository } from 'typeorm';
import {
ConflictException,
ForbiddenException,
Injectable,
InternalServerErrorException,
Logger,
UnauthorizedException,
} from '@nestjs/common';
import * as bcrypt from 'bcrypt';
import { AuthCredentialsDto } from './dto/auth-credentials.dto';
import { User } from 'src/entities/user.entity';
import { CreateUserDto } from './dto/create-user.dto';
import { ResetPasswordDto } from './dto/reset-password.dto';
import { EditUserDto } from './dto/edit-user.dto';
import { ChangePasswordDto } from './dto/change-password.dto';
import { getArrayIfNeeded } from 'src/utilities/tools';
import { Notif } from 'src/entities/notification.entity';
import { stringifyMap, validLanguage } from 'src/utilities/creation';
import sgMail = require('@sendgrid/mail');
import { randomBytes } from 'crypto';
import { Validation } from './dto/user-validation.dto';
import { resetPassword, welcome } from 'src/utilities/emails';
import { welcome } from 'src/utilities/emails';
import { CustomRepository } from 'src/utilities/typeorm-ex.decorator';

@CustomRepository(User)
export class UserRepository extends Repository<User> {
private logger = new Logger('AuthService');
private sgmail = sgMail.setApiKey(process.env.SENDGRID_KEY);
async signUp(createUserDto: CreateUserDto): Promise<User> {
const {
username,
password,
language,
directSharers,
languages,
displayLanguage,
} = createUserDto;
const validationToken = randomBytes(20).toString('hex');
const { username, language, directSharers, languages, displayLanguage } =
createUserDto;
const user = this.create();
user.username = username;
user.salt = await bcrypt.genSalt();
user.password = await this.hashPassword(password, user.salt);
user.resetPasswordToken = '';
user.resetPasswordExpires = '';
user.language = language;
user.displayLanguage = displayLanguage;
user.validationToken = validationToken;
const voices = validLanguage(languages);
user.languages = stringifyMap(voices);
if (directSharers) {
Expand Down Expand Up @@ -128,26 +109,6 @@ export class UserRepository extends Repository<User> {
}
}

async validateUserPassword(
authCredentialsDto: AuthCredentialsDto,
): Promise<Validation> {
const { username, password } = authCredentialsDto;
const user = await this.findOne({
where: {
username,
},
});
if (user && (await user.validatePassword(password))) {
return new Validation(user.username, user.validationToken);
} else {
return null;
}
}

private async hashPassword(password: string, salt: string): Promise<string> {
return bcrypt.hash(password, salt);
}

async pushRoot(user: User, root: number): Promise<void> {
if (user.root) {
throw new ForbiddenException(
Expand All @@ -164,48 +125,6 @@ export class UserRepository extends Repository<User> {
return;
}

async resetPassword(
resetPasswordDto: ResetPasswordDto,
resetTokenValue: string,
resetTokenExpiration: string,
): Promise<void> {
const { username } = resetPasswordDto;
const user = await this.findOne({
where: {
username,
},
});
if (!user) {
return;
}

user.resetPasswordToken = resetTokenValue;
user.resetPasswordExpires = resetTokenExpiration;

try {
await user.save();
} catch (error) {
throw new InternalServerErrorException(error);
}

try {
sgMail.send({
from: '[email protected]',
to: user.username,
templateId: 'd-d68b41c356ba493eac635229b678744e',
dynamicTemplateData: {
resetPassword: resetPassword[`${user.displayLanguage}`]
? resetPassword[`${user.displayLanguage}`]
: resetPassword.en,
token: resetTokenValue,
},
});
} catch (error) {
throw new Error(error);
}
return;
}

async getUserDetails(user: User): Promise<User> {
delete user.password;
delete user.salt;
Expand All @@ -219,7 +138,6 @@ export class UserRepository extends Repository<User> {
async editUser(user: User, editUserDto: EditUserDto): Promise<User> {
const {
language,
password,
directSharers,
languages,
displayLanguage,
Expand All @@ -229,10 +147,6 @@ export class UserRepository extends Repository<User> {
if (language) {
user.language = language;
}
if (password) {
user.salt = await bcrypt.genSalt();
user.password = await this.hashPassword(password, user.salt);
}
if (directSharers) {
user.directSharers = directSharers;
}
Expand All @@ -256,29 +170,6 @@ export class UserRepository extends Repository<User> {
return this.getUserDetails(user);
}

async changePassword(
changePasswordDto: ChangePasswordDto,
token: string,
): Promise<void> {
const { password } = changePasswordDto;
const user = await this.findOne({ where: { resetPasswordToken: token } });
if (!user) {
return;
}
if (Number(user.resetPasswordExpires) > Date.now()) {
user.salt = await bcrypt.genSalt();
user.password = await this.hashPassword(password, user.salt);
user.resetPasswordToken = '';
user.resetPasswordExpires = '';
try {
await user.save();
} catch (error) {
throw new InternalServerErrorException(error);
}
}
return;
}

async clearNotifications(user: User): Promise<Notif[]> {
user.notifications = [];
try {
Expand Down
Loading

0 comments on commit dd40a74

Please sign in to comment.