diff --git a/backend/src/admin/admin.module.ts b/backend/src/admin/admin.module.ts index 778d6c9f93cd6b53bde1189bf6fd7fa6abe9961f..15c3c09e35bb3f35790544a6b6a107b34f50c59f 100644 --- a/backend/src/admin/admin.module.ts +++ b/backend/src/admin/admin.module.ts @@ -3,10 +3,12 @@ import { AdminService } from './admin.service'; import { AdminController } from './admin.controller'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Admin } from './entities/admin.entity'; +import { Town } from 'src/town/entities/town.entity'; @Module({ controllers: [AdminController], providers: [AdminService], - imports: [TypeOrmModule.forFeature([Admin])], + imports: [TypeOrmModule.forFeature([Admin, Town])], + exports: [AdminService], }) export class AdminModule {} diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index 3ec4fcc242f63a3d9a187351bdce34fe240928c1..ff36b7a517077667961be925e3d021cf3956a347 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -3,12 +3,17 @@ import { CreateAdminDto } from './dto/create-admin.dto'; import { Admin } from './entities/admin.entity'; import { Repository } from 'typeorm'; import { InjectRepository } from '@nestjs/typeorm'; +import { Town } from 'src/town/entities/town.entity'; @Injectable() export class AdminService { - constructor(@InjectRepository(Admin) private adminRepository: Repository) {} + constructor( + @InjectRepository(Admin) private adminRepository: Repository, + @InjectRepository(Town) private townRepository: Repository, + ) {} async create(createAdminDto: CreateAdminDto) { - await this.adminRepository.insert(createAdminDto); + const town = await this.townRepository.findOneByOrFail({ townId: createAdminDto.idTown }); + await this.adminRepository.insert({ ...createAdminDto, idTown: town }); } async findOne(email: string): Promise { diff --git a/backend/src/admin/entities/admin.entity.ts b/backend/src/admin/entities/admin.entity.ts index ec777819bb5748bc8a7153166cd2f407b18d9a48..e5f847db9bad4bbcb2da7218da9435b339da50f3 100644 --- a/backend/src/admin/entities/admin.entity.ts +++ b/backend/src/admin/entities/admin.entity.ts @@ -9,8 +9,8 @@ export class Admin { email: string; @JoinColumn({ name: 'idTown' }) - @ManyToOne(() => Town, { nullable: true }) - idTown: number; + @ManyToOne(() => Town, (town) => town.townId, { nullable: true, eager: true }) + idTown: Town; @Column() name: string; diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index a03fa800ce56b16ce04d9273729d6fb83d537d70..719f3f5cb991ae60a161be3c97ad8f290530859c 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -17,9 +17,8 @@ import { join } from 'path'; import { Town } from './town/entities/town.entity'; import { TownModule } from './town/town.module'; import { TownTraduction } from './town/entities/town-traduction.entity'; -import { AuthModule } from './shared/service/auth.module'; import { APP_GUARD } from '@nestjs/core'; -import { AuthGuard } from './auth/admin/auth.guard'; +import { AuthAdminGuard } from './auth/admin/authAdmin.guard'; import { PlaceModule } from './place/place.module'; import { Place } from './place/entities/place.entity'; import { PointOfInterestModule } from './pointOfInterest/PointOfInterest.module'; @@ -59,7 +58,6 @@ import { PlaceTraduction } from './place/entities/place-traduction.entity'; StateModule, DatabaseSeederModule, TownModule, - AuthModule, PlaceModule, ServeStaticModule.forRoot({ rootPath: join(__dirname, '..', 'static'), @@ -67,7 +65,7 @@ import { PlaceTraduction } from './place/entities/place-traduction.entity'; PointOfInterestModule, ], controllers: [AppController], - providers: [AppService, DatabaseSeederModule, { provide: APP_GUARD, useClass: AuthGuard }], + providers: [AppService, DatabaseSeederModule, { provide: APP_GUARD, useClass: AuthAdminGuard }], exports: [TypeOrmModule], }) export class AppModule {} diff --git a/backend/src/auth/admin/auth.guard.ts b/backend/src/auth/admin/authAdmin.guard.ts similarity index 56% rename from backend/src/auth/admin/auth.guard.ts rename to backend/src/auth/admin/authAdmin.guard.ts index 067114b29f2767c02e2ddc51c9f45ed21b0c5da0..0eb5b6712b79431e9ae8fefdcb1146d31b577ae2 100644 --- a/backend/src/auth/admin/auth.guard.ts +++ b/backend/src/auth/admin/authAdmin.guard.ts @@ -1,12 +1,14 @@ import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { AuthService } from 'src/shared/service/auth.service'; import { Roles } from '../role.decorator'; +import { AuthAdminService } from './authAdmin.service'; +import { AdminService } from 'src/admin/admin.service'; @Injectable() -export class AuthGuard implements CanActivate { +export class AuthAdminGuard implements CanActivate { constructor( - private authService: AuthService, + private authAdminService: AuthAdminService, + private adminService: AdminService, private reflector: Reflector, ) {} async canActivate(context: ExecutionContext): Promise { @@ -16,8 +18,10 @@ export class AuthGuard implements CanActivate { let { authorization }: any = request.headers; if (!authorization) throw new UnauthorizedException('session expired! Please sign In'); authorization = authorization.split(' ')[1]; - const role = await this.authService.validateToken(authorization); - if (!requiredRole.includes(role)) throw new UnauthorizedException('Unauthorized access'); + const jwtPayload = await this.authAdminService.validateToken(authorization); + if (!requiredRole.includes(jwtPayload.role)) throw new UnauthorizedException('Unauthorized access'); + const admin = await this.adminService.findOne(jwtPayload.email); + request.admin = { ...admin }; return true; } } diff --git a/backend/src/auth/admin/authAdmin.module.ts b/backend/src/auth/admin/authAdmin.module.ts index 99ab369d1c7e0a304f0771a02016069bf19c81d3..8714d7afcd6fb48ddb869030381bc1d1af6703aa 100644 --- a/backend/src/auth/admin/authAdmin.module.ts +++ b/backend/src/auth/admin/authAdmin.module.ts @@ -3,13 +3,16 @@ import { AuthAdminController } from './authAdmincontroller'; import { EncryptionService } from '../encryption/encryption.service'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Admin } from 'src/admin/entities/admin.entity'; -import { AuthAdminService } from './authAdminservice'; +import { AuthAdminService } from './authAdmin.service'; import { AdminService } from 'src/admin/admin.service'; import { JwtService } from '@nestjs/jwt'; +import { AuthAdminGuard } from './authAdmin.guard'; +import { Town } from 'src/town/entities/town.entity'; @Module({ controllers: [AuthAdminController], - providers: [AuthAdminService, AdminService, JwtService, EncryptionService], - imports: [TypeOrmModule.forFeature([Admin])], + providers: [AuthAdminService, AdminService, JwtService, EncryptionService, AuthAdminGuard], + imports: [TypeOrmModule.forFeature([Admin, Town])], + exports: [AuthAdminService], }) export class AuthAdminModule {} diff --git a/backend/src/auth/admin/authAdminservice.ts b/backend/src/auth/admin/authAdmin.service.ts similarity index 81% rename from backend/src/auth/admin/authAdminservice.ts rename to backend/src/auth/admin/authAdmin.service.ts index f94aa66b610f772fffcfd8722d5853fb25960ba9..69e1760295d3114f16359adc671e838155efcb85 100644 --- a/backend/src/auth/admin/authAdminservice.ts +++ b/backend/src/auth/admin/authAdmin.service.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Injectable, UnauthorizedException } from '@nestjs/common'; import { AdminService } from 'src/admin/admin.service'; import { JwtService } from '@nestjs/jwt'; import { EncryptionService } from '../encryption/encryption.service'; @@ -8,6 +8,7 @@ import { JwtConstants } from 'src/constants/jwt.constants'; 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'; @Injectable() export class AuthAdminService { @@ -48,4 +49,13 @@ export class AuthAdminService { }; return adminSigninResDto; } + + 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'); + } + } } diff --git a/backend/src/auth/admin/authAdmincontroller.ts b/backend/src/auth/admin/authAdmincontroller.ts index ff6b3d098bc8366e04a5047ed45a1a9440144392..6aeadfc99875fa13bcf6e00f1746af5c02c28359 100644 --- a/backend/src/auth/admin/authAdmincontroller.ts +++ b/backend/src/auth/admin/authAdmincontroller.ts @@ -1,5 +1,5 @@ import { Body, Controller, Post } from '@nestjs/common'; -import { AuthAdminService } from './authAdminservice'; +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'; diff --git a/backend/src/auth/admin/interface/customAdminReq.ts b/backend/src/auth/admin/interface/customAdminReq.ts new file mode 100644 index 0000000000000000000000000000000000000000..29a009894d7a87b95223071ffb4bb08561024c72 --- /dev/null +++ b/backend/src/auth/admin/interface/customAdminReq.ts @@ -0,0 +1,6 @@ +import { Request } from 'express'; +import { Admin } from 'src/admin/entities/admin.entity'; + +export interface CustomAdminRequest extends Request { + admin?: Admin; +} diff --git a/backend/src/auth/user/authUserservice.ts b/backend/src/auth/user/authUserservice.ts index 2e5c49a0b9fc304a22a041618d22edac533bb64b..a3122c86a1d964c6c8480126f80c1582f8030c17 100644 --- a/backend/src/auth/user/authUserservice.ts +++ b/backend/src/auth/user/authUserservice.ts @@ -7,6 +7,7 @@ import { User } from 'src/user/entities/user.entity'; 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'; @Injectable() export class AuthUserService { @@ -40,7 +41,7 @@ export class AuthUserService { throw new UnauthorizedException('Invalid credentials'); } const accessToken = await this.jwtService.sign( - { email: user.email, name: user.name, lastName: user.lastName }, + { email: user.email, name: user.name, lastName: user.lastName, role: ALL_ROLES.USER }, { secret: JwtConstants.SECRET }, ); const userSigninResDto: UserSigninResDto = { @@ -49,6 +50,7 @@ export class AuthUserService { name: user.name, lastName: user.lastName, token: accessToken, + role: ALL_ROLES.USER, }; return userSigninResDto; } diff --git a/backend/src/auth/user/dto/user-signin-res.dto.ts b/backend/src/auth/user/dto/user-signin-res.dto.ts index f703702a5cea88e8a012fe3f785ae34023797c82..632d3ee9bd16877e1e55a9ed7b3c5abca2fc2ffc 100644 --- a/backend/src/auth/user/dto/user-signin-res.dto.ts +++ b/backend/src/auth/user/dto/user-signin-res.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty } from '@nestjs/swagger'; +import { ALL_ROLES } from 'src/shared/enum/admin-role.enum'; export class UserSigninResDto { @ApiProperty() @@ -11,4 +12,6 @@ export class UserSigninResDto { lastName: string; @ApiProperty() token: string; + @ApiProperty() + role: ALL_ROLES.USER; } diff --git a/backend/src/database-seeder/database-seeder.module.ts b/backend/src/database-seeder/database-seeder.module.ts index 28c53db57c8323d47ba4b36ab8715aea2f220b02..80f8867dbd0b0a02310f0fb1f16371b3eb4b95e0 100644 --- a/backend/src/database-seeder/database-seeder.module.ts +++ b/backend/src/database-seeder/database-seeder.module.ts @@ -6,7 +6,7 @@ import { State } from 'src/state/entities/state.entity'; import { StateService } from 'src/state/state.service'; import { Town } from 'src/town/entities/town.entity'; import { Admin } from 'src/admin/entities/admin.entity'; -import { AuthAdminService } from 'src/auth/admin/authAdminservice'; +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'; diff --git a/backend/src/database-seeder/database-seeder.service.ts b/backend/src/database-seeder/database-seeder.service.ts index f5cec734d9c349a729b64811e73fec129650c5f7..6e678340c3280fdc139385a93b1452a69162b2fa 100644 --- a/backend/src/database-seeder/database-seeder.service.ts +++ b/backend/src/database-seeder/database-seeder.service.ts @@ -7,7 +7,7 @@ import * as data from './states.json'; import { CreateAdminDto } from 'src/admin/dto/create-admin.dto'; import { ADMIN_ROLE } from 'src/shared/enum/admin-role.enum'; import { UserStatus } from 'src/shared/enum/user-status.enum'; -import { AuthAdminService } from 'src/auth/admin/authAdminservice'; +import { AuthAdminService } from 'src/auth/admin/authAdmin.service'; import { TownService } from 'src/town/town.service'; import { CreateTownDto } from 'src/town/dto/create-town.dto'; import { PointOfInterestService } from 'src/pointOfInterest/PointOfInterest.service'; @@ -43,7 +43,7 @@ export class DatabaseSeederService implements OnModuleInit { }; const createAdmin: CreateAdminDto = { email: 'admin@gmail.com', - idTown: null, + idTown: 1, password: '123', name: 'Admin', lastName: 'admin', diff --git a/backend/src/place/place.controller.ts b/backend/src/place/place.controller.ts index c221715ca7744d5cb644bcd4c16f924f171b5417..3a2eedb0149304b3d61906ad4d012cc651171bd3 100644 --- a/backend/src/place/place.controller.ts +++ b/backend/src/place/place.controller.ts @@ -1,12 +1,24 @@ -import { Controller, Get, Post, Body, Param, UseInterceptors, UploadedFile, Query } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Body, + Param, + UseInterceptors, + UploadedFile, + Query, + UnauthorizedException, + Req, +} from '@nestjs/common'; import { PlaceService } from './place.service'; import { CreatePlaceDateTradDto } from './dto/create-place-date.dto'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; import { Roles } from 'src/auth/role.decorator'; -import { ADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; +import { ALL_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 { CustomAdminRequest } from 'src/auth/admin/interface/customAdminReq'; @Controller('place') @ApiTags('Place') @@ -15,13 +27,24 @@ export class PlaceController { @ApiBody({ type: CreatePlaceDateTradDto }) @ApiConsumes('multipart/form-data') - @Roles(ADMIN_ROLES) + @Roles([ALL_ROLES.ADMIN]) @ApiBearerAuth('jwt') @Post() @UseInterceptors(fileInterceptor('image', 'static/places/', ['.jpg', '.jpeg', '.png'])) - async create(@UploadedFile(new FileValidationPipe()) file, @Body() createPlaceDto: CreatePlaceDateTradDto) { - createPlaceDto.image = file; - return await this.placeService.create(createPlaceDto); + async create( + @UploadedFile(new FileValidationPipe()) file, + @Body() createPlaceDto: CreatePlaceDateTradDto, + @Req() req: CustomAdminRequest, + ) { + try { + if (req.admin.idTown.townId != createPlaceDto.idTown) { + throw new UnauthorizedException('This is not your assigned town'); + } + createPlaceDto.image = file; + return await this.placeService.create(createPlaceDto); + } catch (e) { + throw e; + } } @ApiQuery({ name: 'lang', type: String }) diff --git a/backend/src/shared/enum/admin-role.enum.ts b/backend/src/shared/enum/admin-role.enum.ts index e398a9352b7fc76153d7eb7a07dea95287a198ad..291f9b1a2e7c08c67190a10b23a2f8e6bf9183f8 100644 --- a/backend/src/shared/enum/admin-role.enum.ts +++ b/backend/src/shared/enum/admin-role.enum.ts @@ -3,5 +3,13 @@ export enum ADMIN_ROLE { SUPERADMIN = 'superadmin', } +export const USER_ROLE = 'user'; + +export enum ALL_ROLES { + SUPER_ADMIN = ADMIN_ROLE.SUPERADMIN, + ADMIN = ADMIN_ROLE.ADMIN, + USER = USER_ROLE, +} + export const ADMIN_ROLES = [ADMIN_ROLE.ADMIN, ADMIN_ROLE.SUPERADMIN]; export const SUPERADMIN_ROLES = [ADMIN_ROLE.SUPERADMIN]; diff --git a/backend/src/shared/service/auth.module.ts b/backend/src/shared/service/auth.module.ts deleted file mode 100644 index 02d7b024d67588a8a4bf3998bded0d9058a16e26..0000000000000000000000000000000000000000 --- a/backend/src/shared/service/auth.module.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Module } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { AuthService } from './auth.service'; - -@Module({ - controllers: [], - providers: [JwtService, AuthService], - imports: [], - exports: [AuthService], -}) -export class AuthModule {} diff --git a/backend/src/shared/service/auth.service.ts b/backend/src/shared/service/auth.service.ts deleted file mode 100644 index 72d992887109e64125762da90ba62030a080804a..0000000000000000000000000000000000000000 --- a/backend/src/shared/service/auth.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Injectable, UnauthorizedException } from '@nestjs/common'; -import { JwtService } from '@nestjs/jwt'; -import { JwtConstants } from 'src/constants/jwt.constants'; -import { PayloadJwtDto } from 'src/shared/dto/payload-jwt.dto'; - -@Injectable() -export class AuthService { - constructor(private jwtService: JwtService) {} - - async validateToken(token: string): Promise { - try { - const payload: PayloadJwtDto = await this.jwtService.verify(token, { secret: JwtConstants.SECRET }); - return payload.role; - } catch (error) { - throw new UnauthorizedException('Invalid token'); - } - } -} diff --git a/backend/src/town/town.controller.ts b/backend/src/town/town.controller.ts index 4e986796b87557c411061a5058564c2d7a71f015..79a817372a63276af69a2d42779d5eb8706ee4f2 100644 --- a/backend/src/town/town.controller.ts +++ b/backend/src/town/town.controller.ts @@ -1,12 +1,25 @@ -import { Controller, Get, Post, Param, UseInterceptors, UploadedFile, Body, Query, Patch } from '@nestjs/common'; +import { + Controller, + Get, + Post, + Param, + UseInterceptors, + UploadedFile, + Body, + Query, + Patch, + Req, + UnauthorizedException, +} from '@nestjs/common'; import { TownService } from './town.service'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiParam, ApiQuery, ApiTags } from '@nestjs/swagger'; import { FileValidationPipe } from 'src/shared/pipe/file-validation.pipe'; import { fileInterceptor } from 'src/shared/interceptors/file-save.interceptor'; import { CreateTownDto } from './dto/create-town.dto'; import { Roles } from 'src/auth/role.decorator'; -import { SUPERADMIN_ROLES } from 'src/shared/enum/admin-role.enum'; +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'; @Controller() @ApiTags('Pueblos') export class TownController { @@ -46,7 +59,7 @@ export class TownController { } } - @Roles(SUPERADMIN_ROLES) + @Roles([ALL_ROLES.ADMIN]) @ApiBearerAuth('jwt') @ApiBody({ type: CreateTownReqDto }) @ApiConsumes('multipart/form-data') @@ -56,8 +69,12 @@ export class TownController { @Param('idTown') idTown: number, @UploadedFile(new FileValidationPipe()) file, @Body() createTownReqDto: CreateTownReqDto, + @Req() req: CustomAdminRequest, ) { try { + if (req.admin.idTown.townId != idTown) { + throw new UnauthorizedException('This is not your assigned town'); + } const updateTownDto: CreateTownDto = { name: createTownReqDto.name, imageName: file.filename, @@ -66,7 +83,7 @@ export class TownController { state: createTownReqDto.state, }; await this.townService.update(idTown, updateTownDto); - return { message: 'Town created successfully' }; + return { message: 'Town updated successfully' }; } catch (error) { throw error; }