diff --git a/backend/src/admin/admin.controller.ts b/backend/src/admin/admin.controller.ts index 072625aac40ed453adac01de5cb8d3e945a78dc4..9fa23ef22c556d6321f7ebd258889610dfb8bfe3 100644 --- a/backend/src/admin/admin.controller.ts +++ b/backend/src/admin/admin.controller.ts @@ -1,15 +1,17 @@ -import { Controller, Get, Req } from '@nestjs/common'; +import { Controller, Get, Req, UseGuards } from '@nestjs/common'; import { AdminService } from './admin.service'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; import { Roles } from 'src/auth/role.decorator'; import { CustomAdminRequest } from 'src/auth/admin/interface/customAdminReq'; +import { AuthAdminGuard } from 'src/auth/admin/authAdmin.guard'; @Controller('') @ApiTags('Admin') export class AdminController { constructor(private readonly adminService: AdminService) {} + @UseGuards(AuthAdminGuard) @Roles(ADMIN_ROLES) @ApiBearerAuth('jwt') @Get('admin/whoami') diff --git a/backend/src/admin/admin.module.ts b/backend/src/admin/admin.module.ts index 15c3c09e35bb3f35790544a6b6a107b34f50c59f..59cc1c9007b2936d80be60b8b761415750395c74 100644 --- a/backend/src/admin/admin.module.ts +++ b/backend/src/admin/admin.module.ts @@ -4,10 +4,13 @@ import { AdminController } from './admin.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Admin } from './entities/admin.entity'; import { Town } from 'src/town/entities/town.entity'; +import { AuthAdminService } from 'src/auth/admin/authAdmin.service'; +import { JwtService } from '@nestjs/jwt'; +import { EncryptionService } from 'src/auth/encryption/encryption.service'; @Module({ controllers: [AdminController], - providers: [AdminService], + providers: [AdminService, AuthAdminService, JwtService, EncryptionService], imports: [TypeOrmModule.forFeature([Admin, Town])], exports: [AdminService], }) diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index ff36b7a517077667961be925e3d021cf3956a347..793c06a5af237597b185aa0d1a401f64e3138dcf 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -23,4 +23,8 @@ export class AdminService { throw new UnauthorizedException('Admin not found'); } } + + async updatePassword(email: string, password: string) { + await this.adminRepository.update({ email }, { password }); + } } diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 719f3f5cb991ae60a161be3c97ad8f290530859c..85d00ffe452f0b8491cb5267222f2d806f385493 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -65,7 +65,7 @@ import { PlaceTraduction } from './place/entities/place-traduction.entity'; PointOfInterestModule, ], controllers: [AppController], - providers: [AppService, DatabaseSeederModule, { provide: APP_GUARD, useClass: AuthAdminGuard }], + providers: [AppService, DatabaseSeederModule], exports: [TypeOrmModule], }) export class AppModule {} diff --git a/backend/src/auth/admin/authAdmin.service.ts b/backend/src/auth/admin/authAdmin.service.ts index 69e1760295d3114f16359adc671e838155efcb85..c11613a44a99bfa590e2159b75c1a17d2302c73e 100644 --- a/backend/src/auth/admin/authAdmin.service.ts +++ b/backend/src/auth/admin/authAdmin.service.ts @@ -9,6 +9,7 @@ import { AdminSigninResDto } from './dto/admin-signin-res.dto'; import { Admin } from 'src/admin/entities/admin.entity'; import { ADMIN_ROLE } from 'src/shared/enum/admin-role.enum'; import { PayloadJwtDto } from 'src/shared/dto/payload-jwt.dto'; +import { UpdatePwdDto } from '../user/dto/update-pwd.dto'; @Injectable() export class AuthAdminService { @@ -58,4 +59,14 @@ export class AuthAdminService { throw new UnauthorizedException('Invalid token'); } } + + async changePassword(email: string, updatePwdDto: UpdatePwdDto): Promise { + const admin: Admin = await this.adminService.findOne(email); + const validPwd: boolean = await this.encryptionService.comparePassword(updatePwdDto.prevPassword, admin.password); + if (!validPwd) { + throw new HttpException('Invalid password', HttpStatus.UNAUTHORIZED); + } + const hashedPwd = await this.encryptionService.hashPassword(updatePwdDto.newPassword); + await this.adminService.updatePassword(email, hashedPwd); + } } diff --git a/backend/src/auth/admin/authAdmincontroller.ts b/backend/src/auth/admin/authAdmincontroller.ts index 6aeadfc99875fa13bcf6e00f1746af5c02c28359..20ebd5a71370fb66eb3a1828b14c7072f0972785 100644 --- a/backend/src/auth/admin/authAdmincontroller.ts +++ b/backend/src/auth/admin/authAdmincontroller.ts @@ -1,11 +1,14 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common'; import { AuthAdminService } from './authAdmin.service'; import { CreateAdminDto } from 'src/admin/dto/create-admin.dto'; import { LoginAdminDto } from 'src/auth/admin/dto/login-admin.dto'; import { ApiBearerAuth, ApiBody, ApiCreatedResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AdminSigninResDto } from './dto/admin-signin-res.dto'; import { Roles } from '../role.decorator'; -import { ADMIN_ROLE, SUPERADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; +import { ADMIN_ROLE, ADMIN_ROLES, SUPERADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; +import { AuthAdminGuard } from './authAdmin.guard'; +import { UpdatePwdDto } from '../user/dto/update-pwd.dto'; +import { CustomAdminRequest } from './interface/customAdminReq'; @Controller() @ApiTags('Create admin account and sign in as admin') @@ -22,6 +25,7 @@ export class AuthAdminController { }, }, }) + @UseGuards(AuthAdminGuard) @Roles(SUPERADMIN_ROLES) @Post('admin/signup') @ApiBearerAuth('jwt') @@ -48,4 +52,13 @@ export class AuthAdminController { throw e; } } + + @UseGuards(AuthAdminGuard) + @Roles(ADMIN_ROLES) + @Post('admin/change-password') + @ApiBearerAuth('jwt') + async changePassword(@Req() req: CustomAdminRequest, @Body() updatePwdDto: UpdatePwdDto) { + const email = req.admin.email; + return this.authAdminService.changePassword(email, updatePwdDto); + } } diff --git a/backend/src/auth/user/authUser.guard.ts b/backend/src/auth/user/authUser.guard.ts new file mode 100644 index 0000000000000000000000000000000000000000..d8f0b97f1efe8324bc3ed50501160a87c4ce019a --- /dev/null +++ b/backend/src/auth/user/authUser.guard.ts @@ -0,0 +1,24 @@ +import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; +import { UserService } from 'src/user/user.service'; +import { AuthUserService } from './authUserservice'; + +@Injectable() +export class AuthUserGuard implements CanActivate { + constructor( + private authUserService: AuthUserService, + private userService: UserService, + ) {} + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + let { authorization }: any = request.headers; + if (!authorization) throw new UnauthorizedException('session expired! Please sign In'); + authorization = authorization.split(' ')[1]; + const jwtPayload = await this.authUserService.validateToken(authorization); + if (!jwtPayload) throw new UnauthorizedException('session expired! Please sign In'); + + const user = await this.userService.findOne(jwtPayload.email); + if (!user) throw new UnauthorizedException('session expired! Please sign In'); + request.user = { ...user }; + return true; + } +} diff --git a/backend/src/auth/user/authUsercontroller.ts b/backend/src/auth/user/authUsercontroller.ts index f82a1147551ef652141d4fe480996bff8ed3ec2c..8febe05f74cabc2903975780f0a86f2200aa91f9 100644 --- a/backend/src/auth/user/authUsercontroller.ts +++ b/backend/src/auth/user/authUsercontroller.ts @@ -1,9 +1,12 @@ -import { Body, Controller, Post } from '@nestjs/common'; +import { Body, Controller, Patch, Post, Req, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiBody, ApiCreatedResponse, ApiTags, ApiUnauthorizedResponse } from '@nestjs/swagger'; import { AuthUserService } from './authUserservice'; import { CreateUserDto } from 'src/user/dto/create-user.dto'; import { LoginUserDto } from './dto/login-user.dto'; import { UserSigninResDto } from './dto/user-signin-res.dto'; +import { AuthUserGuard } from './authUser.guard'; +import { CustomUserRequest } from './interface/customUserReq'; +import { UpdatePwdDto } from './dto/update-pwd.dto'; @Controller('') @ApiTags('Create user account and sign in as user') @@ -38,4 +41,11 @@ export class AuthUserController { throw e; } } + + @UseGuards(AuthUserGuard) + @ApiBearerAuth('jwt') + @Patch('user/change-password') + async changePassword(@Req() req: CustomUserRequest, @Body() updatePwdDto: UpdatePwdDto) { + return this.authUserService.changePassword(req.user.email, updatePwdDto); + } } diff --git a/backend/src/auth/user/authUserservice.ts b/backend/src/auth/user/authUserservice.ts index a3122c86a1d964c6c8480126f80c1582f8030c17..b3b5db3b6624006fb37004b19a5c4fcab6a938d8 100644 --- a/backend/src/auth/user/authUserservice.ts +++ b/backend/src/auth/user/authUserservice.ts @@ -8,6 +8,8 @@ import { UserService } from 'src/user/user.service'; import { CreateUserDto } from 'src/user/dto/create-user.dto'; import { LoginUserDto } from './dto/login-user.dto'; import { ALL_ROLES } from 'src/shared/enum/admin-role.enum'; +import { PayloadJwtDto } from 'src/shared/dto/payload-jwt.dto'; +import { UpdatePwdDto } from './dto/update-pwd.dto'; @Injectable() export class AuthUserService { @@ -54,4 +56,24 @@ export class AuthUserService { }; return userSigninResDto; } + + async validateToken(token: string): Promise { + try { + const payload: PayloadJwtDto = await this.jwtService.verify(token, { secret: JwtConstants.SECRET }); + return payload; + } catch (error) { + throw new UnauthorizedException('Invalid token'); + } + } + + async changePassword(email: string, updatePwdDto: UpdatePwdDto) { + const user: User = await this.userService.findOne(email); + const prevPwdHashed = user.password; + const validPwd: boolean = await this.encryptionService.comparePassword(updatePwdDto.prevPassword, prevPwdHashed); + + if (!validPwd) throw new UnauthorizedException('Invalid password'); + + const newPwdHashed = await this.encryptionService.hashPassword(updatePwdDto.newPassword); + await this.userService.updatePassword(email, newPwdHashed); + } } diff --git a/backend/src/auth/user/dto/update-pwd.dto.ts b/backend/src/auth/user/dto/update-pwd.dto.ts new file mode 100644 index 0000000000000000000000000000000000000000..afcb4141cfddc6a24caa88c3b6587e74c0906a3f --- /dev/null +++ b/backend/src/auth/user/dto/update-pwd.dto.ts @@ -0,0 +1,8 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdatePwdDto { + @ApiProperty() + prevPassword: string; + @ApiProperty() + newPassword: string; +} diff --git a/backend/src/auth/user/interface/customUserReq.ts b/backend/src/auth/user/interface/customUserReq.ts new file mode 100644 index 0000000000000000000000000000000000000000..bccd04dd241fa232dac7ed7b8a3a3bb81d001634 --- /dev/null +++ b/backend/src/auth/user/interface/customUserReq.ts @@ -0,0 +1,6 @@ +import { Request } from 'express'; +import { User } from 'src/user/entities/user.entity'; + +export interface CustomUserRequest extends Request { + user?: User; +} diff --git a/backend/src/place/place.controller.ts b/backend/src/place/place.controller.ts index 82e5edeb26dd8e5be9092ba697916dde577b6ca6..4e26dcfbca14e25f4b97b9d59443990c1dcd0705 100644 --- a/backend/src/place/place.controller.ts +++ b/backend/src/place/place.controller.ts @@ -10,6 +10,7 @@ import { UnauthorizedException, Req, Patch, + UseGuards, } from '@nestjs/common'; import { PlaceService } from './place.service'; import { CreatePlaceDateTradDto } from './dto/create-place-date.dto'; @@ -21,6 +22,7 @@ import { FileValidationPipe } from 'src/shared/pipe/file-validation.pipe'; import { LANGUAGES } from 'src/shared/enum/languages.enum'; import { CustomAdminRequest } from 'src/auth/admin/interface/customAdminReq'; import { UpdatePlaceReqDto } from './dto/update-place.req.dto'; +import { AuthAdminGuard } from 'src/auth/admin/authAdmin.guard'; @Controller('place') @ApiTags('Place') @@ -29,6 +31,7 @@ export class PlaceController { @ApiBody({ type: CreatePlaceDateTradDto }) @ApiConsumes('multipart/form-data') + @UseGuards(AuthAdminGuard) @Roles([ALL_ROLES.ADMIN]) @ApiBearerAuth('jwt') @Post() @@ -67,6 +70,7 @@ export class PlaceController { } @Patch(':idPlace') + @UseGuards(AuthAdminGuard) @Roles([ALL_ROLES.ADMIN]) @ApiBearerAuth('jwt') @ApiConsumes('multipart/form-data') diff --git a/backend/src/place/place.module.ts b/backend/src/place/place.module.ts index d32cf55e3f1aea5b09d2fc8d9a209a25d59f3fdf..dd524b38ef68f75822090abc0baf306984eeee58 100644 --- a/backend/src/place/place.module.ts +++ b/backend/src/place/place.module.ts @@ -10,12 +10,26 @@ import { TownService } from 'src/town/town.service'; import { TownTraduction } from 'src/town/entities/town-traduction.entity'; import { StateService } from 'src/state/state.service'; import { State } from 'src/state/entities/state.entity'; +import { AuthAdminService } from 'src/auth/admin/authAdmin.service'; +import { AdminService } from 'src/admin/admin.service'; +import { JwtService } from '@nestjs/jwt'; +import { EncryptionService } from 'src/auth/encryption/encryption.service'; +import { Admin } from 'src/admin/entities/admin.entity'; @Module({ controllers: [PlaceController], - providers: [PlaceService, TownService, StateService], + providers: [ + PlaceService, + TownService, + StateService, + AuthAdminService, + AdminService, + JwtService, + EncryptionService, + TownService, + ], imports: [ - TypeOrmModule.forFeature([Place, AvailableDate, PlaceTraduction, Town, TownTraduction, State]), + TypeOrmModule.forFeature([Place, AvailableDate, PlaceTraduction, Town, TownTraduction, State, Admin]), TypeOrmModule, ], }) diff --git a/backend/src/pointOfInterest/PointOfInterest.controller.ts b/backend/src/pointOfInterest/PointOfInterest.controller.ts index 57c26d37a4661ca0ce301e5c5a484ce19c31ab9d..62ac1bdda13ebe740aa8557c88a4a1ee338d47b2 100644 --- a/backend/src/pointOfInterest/PointOfInterest.controller.ts +++ b/backend/src/pointOfInterest/PointOfInterest.controller.ts @@ -8,6 +8,7 @@ import { UploadedFile, Query, StreamableFile, + UseGuards, } from '@nestjs/common'; import { PointOfInterestService } from './PointOfInterest.service'; import { CreatePointAndTradDto } from './dto/create-pointAndTraduction.dto'; @@ -17,6 +18,7 @@ import { ADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; import { fileInterceptor } from 'src/shared/interceptors/file-save.interceptor'; import { FileValidationPipe } from 'src/shared/pipe/file-validation.pipe'; import { LANGUAGES } from 'src/shared/enum/languages.enum'; +import { AuthAdminGuard } from 'src/auth/admin/authAdmin.guard'; @Controller('') @ApiTags('Point of interest') @@ -24,6 +26,7 @@ export class PointOfInterestController { constructor(private readonly pointService: PointOfInterestService) {} @ApiConsumes('multipart/form-data') + @UseGuards(AuthAdminGuard) @Roles(ADMIN_ROLES) @ApiBearerAuth('jwt') @Post('point') diff --git a/backend/src/pointOfInterest/PointOfInterest.module.ts b/backend/src/pointOfInterest/PointOfInterest.module.ts index c92230258af1ca75f112f6a7febed2bbe223904b..aa78034b1c9aae1bd06baaf714ec5c86dc5db0eb 100644 --- a/backend/src/pointOfInterest/PointOfInterest.module.ts +++ b/backend/src/pointOfInterest/PointOfInterest.module.ts @@ -9,12 +9,25 @@ import { Place } from 'src/place/entities/place.entity'; import { AvailableDate } from 'src/place/entities/available-date.entity'; import { PlaceTraduction } from 'src/place/entities/place-traduction.entity'; import { Town } from 'src/town/entities/town.entity'; +import { AuthAdminService } from 'src/auth/admin/authAdmin.service'; +import { JwtService } from '@nestjs/jwt'; +import { EncryptionService } from 'src/auth/encryption/encryption.service'; +import { AdminService } from 'src/admin/admin.service'; +import { Admin } from 'src/admin/entities/admin.entity'; @Module({ controllers: [PointOfInterestController], - providers: [PointOfInterestService, PlaceService], + providers: [PointOfInterestService, PlaceService, AdminService, AuthAdminService, JwtService, EncryptionService], imports: [ - TypeOrmModule.forFeature([PointOfInterest, PointOfInterestTraduction, Place, AvailableDate, PlaceTraduction, Town]), + TypeOrmModule.forFeature([ + PointOfInterest, + PointOfInterestTraduction, + Place, + AvailableDate, + PlaceTraduction, + Town, + Admin, + ]), ], }) export class PointOfInterestModule {} diff --git a/backend/src/town/town.controller.ts b/backend/src/town/town.controller.ts index 34a17eef9a5113a72b514c4822980af719f3b571..f2abda25525f270f332290ed9ef24ba0db6b1885 100644 --- a/backend/src/town/town.controller.ts +++ b/backend/src/town/town.controller.ts @@ -10,6 +10,7 @@ import { Patch, Req, UnauthorizedException, + UseGuards, } from '@nestjs/common'; import { TownService } from './town.service'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; @@ -20,11 +21,13 @@ import { Roles } from 'src/auth/role.decorator'; import { ALL_ROLES, SUPERADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; import { CreateTownReqDto } from './dto/createTownReq.dto'; import { CustomAdminRequest } from 'src/auth/admin/interface/customAdminReq'; +import { AuthAdminGuard } from 'src/auth/admin/authAdmin.guard'; @Controller() @ApiTags('Pueblos') export class TownController { constructor(private readonly townService: TownService) {} + @UseGuards(AuthAdminGuard) @Roles(SUPERADMIN_ROLES) @ApiBearerAuth('jwt') @ApiBody({ type: CreateTownReqDto }) @@ -59,6 +62,7 @@ export class TownController { } } + @UseGuards(AuthAdminGuard) @Roles([ALL_ROLES.ADMIN]) @ApiBearerAuth('jwt') @ApiBody({ type: CreateTownReqDto }) diff --git a/backend/src/town/town.module.ts b/backend/src/town/town.module.ts index 3bcef4a42bd75212908304ccb38fcc2fa24250c5..401bac47b4a71849a97ce3ae00c633cb95c19c3e 100644 --- a/backend/src/town/town.module.ts +++ b/backend/src/town/town.module.ts @@ -6,11 +6,16 @@ import { Town } from './entities/town.entity'; import { StateService } from 'src/state/state.service'; import { State } from 'src/state/entities/state.entity'; import { TownTraduction } from './entities/town-traduction.entity'; +import { AuthAdminService } from 'src/auth/admin/authAdmin.service'; +import { AdminService } from 'src/admin/admin.service'; +import { JwtService } from '@nestjs/jwt'; +import { EncryptionService } from 'src/auth/encryption/encryption.service'; +import { Admin } from 'src/admin/entities/admin.entity'; @Module({ controllers: [TownController], - providers: [TownService, StateService, TypeOrmModule], - imports: [TypeOrmModule.forFeature([Town, State, TownTraduction]), TypeOrmModule], + providers: [TownService, StateService, TypeOrmModule, AuthAdminService, AdminService, JwtService, EncryptionService], + imports: [TypeOrmModule.forFeature([Town, State, TownTraduction, Admin]), TypeOrmModule], exports: [TownService], }) export class TownModule {} diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index 561a0be72a8654c5f35b5ff43361995a50d6e69a..e22805e27f2fd4d69eb318b4ff751eb47ba2f1cc 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -24,4 +24,8 @@ export class UserService { if (user) return true; else return false; } + + async updatePassword(email: string, password: string) { + await this.userRepository.update({ email }, { password }); + } }