diff --git a/config/default.yml b/config/default.yml index a5adb7c..854bae1 100644 --- a/config/default.yml +++ b/config/default.yml @@ -4,7 +4,7 @@ server: db: type: 'postgres' port: 5432 - database: 'pictalk-keycloak' + database: 'pictalk' #database: "pictalk" jwt: diff --git a/config/development.yml b/config/development.yml index cc5874a..dded7b0 100644 --- a/config/development.yml +++ b/config/development.yml @@ -2,7 +2,7 @@ db: host: 'localhost' # This is the name of the service (see docker-compose.yml from pictalk-frontend) that runs the database username: 'postgres' password: 'postgres' - synchronize: true + synchronize: false jwt: secret: 'WayTooMuchSunScreen' diff --git a/src/app.module.ts b/src/app.module.ts index e4df649..1677b96 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -20,17 +20,13 @@ import { RoleGuard, } from 'nest-keycloak-connect'; import { APP_GUARD } from '@nestjs/core'; +import keycloakConfig from './config/keycloak.config'; @Module({ imports: [ ConfigModule.forRoot(), TypeOrmModule.forRoot(typeOrmConfig), - KeycloakConnectModule.register({ - authServerUrl: 'https://auth.picmind.org', - realm: 'master', - clientId: 'pictalk-api', - secret: 'CWLSdXiTtaRfaVmg1DIDHAfqK67E3HGd', - // Secret key of the client taken from keycloak server - }), + ConfigModule.forRoot({ load: [keycloakConfig] }), + KeycloakConnectModule.register(keycloakConfig()), PictoModule, CollectionModule, AuthModule, diff --git a/src/auth/auth.controller.ts b/src/auth/auth.controller.ts index 04832b6..8e906ef 100644 --- a/src/auth/auth.controller.ts +++ b/src/auth/auth.controller.ts @@ -28,6 +28,9 @@ 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'; +@UseGuards(UserGuard) @Controller('') export class AuthController { private logger = new Logger('AuthController'); @@ -102,14 +105,14 @@ export class AuthController { } @Get('user/details') - getUserDetails(@AuthenticatedUser() user: User): Promise { + getUserDetails(@GetUser() user: User): Promise { this.logger.verbose(`User "${user.username}" is trying to get Details`); return this.authService.getUserDetails(user); } @Put('user/details') async editUser( - @AuthenticatedUser() user: User, + @GetUser() user: User, @Body(ValidationPipe) editUserDto: EditUserDto, ): Promise { if (editUserDto.mailingList) { @@ -148,21 +151,22 @@ export class AuthController { } @Get('/user/root') - async getRoot(@AuthenticatedUser() user: User): Promise { + + async getRoot(@GetUser() user: User): Promise { this.logger.verbose(`User "${user.username}" getting his root`); const root = await this.authService.getRoot(user); return this.collectionService.getCollectionById(root, user); } @Get('/user/sider') - async getSider(@AuthenticatedUser() user: User): Promise { + async getSider(@GetUser() user: User): Promise { this.logger.verbose(`User "${user.username}" getting his root`); const sider = await this.authService.getSider(user); return this.collectionService.getCollectionById(sider, user); } @Get('/user/shared') - async getShared(@AuthenticatedUser() user: User): Promise { + async getShared(@GetUser() user: User): Promise { this.logger.verbose( `User "${user.username}" getting his shared with me Collection`, ); @@ -171,12 +175,12 @@ export class AuthController { } @Get('/user/notification') - async getNotifications(@AuthenticatedUser() user: User): Promise { + async getNotifications(@GetUser() user: User): Promise { return this.authService.getNotifications(user); } @Delete('/user/notification') - async clearNotifications(@AuthenticatedUser() user: User): Promise { + async clearNotifications(@GetUser() user: User): Promise { this.logger.verbose(`User "${user.username}" clearing his notifications`); return this.authService.clearNotifications(user); } diff --git a/src/auth/dto/keycloak.user.dto.ts b/src/auth/dto/keycloak.user.dto.ts new file mode 100644 index 0000000..5e28935 --- /dev/null +++ b/src/auth/dto/keycloak.user.dto.ts @@ -0,0 +1,18 @@ +interface KeycloakUser { + exp: number; + iat: number; + jti: string; + iss: string; + sub: string; + typ: string; + azp: string; + session_state: string; + acr: string; + scope: string; + sid: string; + email_verified: boolean; + preferred_username: string; + given_name: string; + family_name: string; + email: string; +} \ No newline at end of file diff --git a/src/auth/get-user.decorator.ts b/src/auth/get-user.decorator.ts new file mode 100644 index 0000000..6689a93 --- /dev/null +++ b/src/auth/get-user.decorator.ts @@ -0,0 +1,8 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; + +export const GetUser = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request = ctx.switchToHttp().getRequest(); + return request.user; + }, +); \ No newline at end of file diff --git a/src/auth/user.guard.ts b/src/auth/user.guard.ts new file mode 100644 index 0000000..c666ce8 --- /dev/null +++ b/src/auth/user.guard.ts @@ -0,0 +1,17 @@ +import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; +import { Observable } from 'rxjs'; +import { AuthService } from './auth.service'; + +@Injectable() +export class UserGuard implements CanActivate { + constructor(private authService: AuthService) {} + + canActivate( + context: ExecutionContext, + ): boolean | Promise | Observable { + const request = context.switchToHttp().getRequest(); + const user = this.authService.findWithUsername(request.user.username); + request.user = user; + return true; + } +} \ No newline at end of file diff --git a/src/collection/collection.controller.ts b/src/collection/collection.controller.ts index b5244fa..0e90fb0 100644 --- a/src/collection/collection.controller.ts +++ b/src/collection/collection.controller.ts @@ -45,6 +45,9 @@ import { SearchCollectionDto } from './dto/collection.search.public.dto'; import { levelCollectionDto } from './dto/collection.level.dto'; import { MoveToCollectionDto } from './dto/collection.move.dto'; import { AuthenticatedUser, Public, AuthGuard } from 'nest-keycloak-connect'; +import { UserGuard } from 'src/auth/user.guard'; +import { GetUser } from 'src/auth/get-user.decorator'; +@UseGuards(UserGuard) @Controller('collection') export class CollectionController { private logger = new Logger('CollectionController'); @@ -55,7 +58,7 @@ export class CollectionController { @ApiOperation({ summary: 'get a collection that has the provided id' }) getCollectionById( @Param('id', ParseIntPipe) id: number, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { this.logger.verbose( `User "${user.username}" getting Collection with id ${id}`, @@ -120,7 +123,7 @@ export class CollectionController { @Get() @ApiOperation({ summary: 'get all your collection' }) getAllUserCollections( - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { this.logger.verbose(`User "${user.username}" getting all Collection`); return this.collectionService.getAllUserCollections(user); @@ -130,7 +133,7 @@ export class CollectionController { @ApiOperation({ summary: 'copy a collection with its ID' }) async copyCollection( @Body() copyCollectionDto: copyCollectionDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { const fatherCollection = await this.collectionService.getCollectionById( copyCollectionDto.fatherCollectionId, @@ -174,7 +177,7 @@ export class CollectionController { async shareCollectionById( @Param('id', ParseIntPipe) id: number, @Body() multipleShareCollectionDto: multipleShareCollectionDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { let collection: Collection; if (!multipleShareCollectionDto.role) { @@ -203,7 +206,7 @@ export class CollectionController { publishCollectionById( @Param('id', ParseIntPipe) id: number, @Body() publicCollectionDto: publicCollectionDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { if (user.admin === true) { return this.collectionService.publishCollectionById( @@ -232,7 +235,7 @@ export class CollectionController { ) async createCollection( @Body() createCollectionDto: createCollectionDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, @UploadedFile() file: Express.Multer.File, ): Promise { if (!file) { @@ -303,7 +306,7 @@ export class CollectionController { } @Post('/root') - createRoot(@AuthenticatedUser() user: User): Promise { + createRoot(@GetUser() user: User): Promise { this.logger.verbose( `User "${user.username}" Creating 'Root' Collection if needed`, ); @@ -313,7 +316,7 @@ export class CollectionController { @Delete() deleteCollection( @Query(ValidationPipe) deleteCollectionDto: deleteCollectionDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { deleteCollectionDto.collectionId = Number(deleteCollectionDto.collectionId); this.logger.verbose( @@ -336,7 +339,7 @@ export class CollectionController { ) async modifyCollection( @Param('id', ParseIntPipe) id: number, - @AuthenticatedUser() user: User, + @GetUser() user: User, @Body() modifyCollectionDto: modifyCollectionDto, @UploadedFile() file: Express.Multer.File, ): Promise { @@ -374,7 +377,7 @@ export class CollectionController { @UsePipes(ValidationPipe) async moveToCollection( @Param('id', ParseIntPipe) fatherCollectionId: number, - @AuthenticatedUser() user: User, + @GetUser() user: User, @Body() moveToCollectionDto: MoveToCollectionDto, ): Promise { this.logger.verbose( diff --git a/src/config/keycloak.config.ts b/src/config/keycloak.config.ts new file mode 100644 index 0000000..2040e35 --- /dev/null +++ b/src/config/keycloak.config.ts @@ -0,0 +1,7 @@ +export default () => ({ + authServerUrl: + process.env.KEYCLOAK_AUTH_SERVER_URL || 'http://localhost:8080', + realm: process.env.KEYCLOAK_REALM || 'master', + clientId: process.env.KEYCLOAK_CLIENT_ID || 'random', + secret: process.env.KEYCLOAK_SECRET || 'secret', +}); diff --git a/src/feedback/feedback.controller.ts b/src/feedback/feedback.controller.ts index ddc75e5..6100c13 100644 --- a/src/feedback/feedback.controller.ts +++ b/src/feedback/feedback.controller.ts @@ -20,6 +20,9 @@ import { SearchFeedbackDto } from './dto/search.feedback.dto'; import { Feedback } from './entities/feedback.entity'; import { FeedbackService } from './feedback.service'; import { AuthenticatedUser, AuthGuard } from 'nest-keycloak-connect'; +import { UserGuard } from 'src/auth/user.guard'; +import { GetUser } from 'src/auth/get-user.decorator'; +@UseGuards(UserGuard) @Controller('feedback') export class FeedbackController { private logger = new Logger('CollectionController'); @@ -39,7 +42,7 @@ export class FeedbackController { @Get() @UsePipes(ValidationPipe) async getFeedback( - @AuthenticatedUser() user: User, + @GetUser() user: User, @Query() searchFeedbackDto: SearchFeedbackDto, ): Promise<{ feedbacks: Feedback[]; total_count: number }> { if (!user.admin) { @@ -55,7 +58,7 @@ export class FeedbackController { @Put('/:id') @UsePipes(ValidationPipe) async editFeedback( - @AuthenticatedUser() user: User, + @GetUser() user: User, @Param('id', ParseIntPipe) id: number, @Body() editFeedbackDto: EditFeedbackDto, ): Promise { diff --git a/src/picto/picto.controller.ts b/src/picto/picto.controller.ts index afb7768..8d9591a 100644 --- a/src/picto/picto.controller.ts +++ b/src/picto/picto.controller.ts @@ -43,6 +43,9 @@ import { deletePictoDto } from './dto/picto.delete.dto'; import { copyPictoDto } from './dto/picto.copy.dto'; import { Collection } from 'src/entities/collection.entity'; import { AuthenticatedUser, Public, AuthGuard } from 'nest-keycloak-connect'; +import { UserGuard } from 'src/auth/user.guard'; +import { GetUser } from 'src/auth/get-user.decorator'; +@UseGuards(UserGuard) @Controller('picto') export class PictoController { private logger = new Logger('PictosController'); @@ -56,7 +59,7 @@ export class PictoController { @Get('/:id') getPictoById( @Param('id', ParseIntPipe) id: number, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { this.logger.verbose(`User "${user.username}" getting Picto with id ${id}`); return this.pictoService.getPictoById(id, user); @@ -64,7 +67,7 @@ export class PictoController { @Get() @ApiOperation({ summary: 'get all your pictos' }) - getAllUserPictos(@AuthenticatedUser() user: User): Promise { + getAllUserPictos(@GetUser() user: User): Promise { this.logger.verbose(`User "${user.username}" getting all Picto`); return this.pictoService.getAllUserPictos(user); } @@ -77,7 +80,7 @@ export class PictoController { async sharePictoById( @Param('id', ParseIntPipe) id: number, @Body() multipleSharePictoDto: multipleSharePictoDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { let picto: Picto; if (!multipleSharePictoDto.role) { @@ -121,7 +124,7 @@ export class PictoController { ) async createPicto( @Body() createPictoDto: createPictoDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, @UploadedFile() file: Express.Multer.File, ): Promise { if (!file) { @@ -192,7 +195,7 @@ export class PictoController { @Delete() deletePicto( @Query(ValidationPipe) deletePictoDto: deletePictoDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { deletePictoDto.pictoId = Number(deletePictoDto.pictoId); this.logger.verbose( @@ -215,7 +218,7 @@ export class PictoController { ) async modifyPicto( @Param('id', ParseIntPipe) id: number, - @AuthenticatedUser() user: User, + @GetUser() user: User, @Body() modifyPictoDto: modifyPictoDto, @UploadedFile() file: Express.Multer.File, ): Promise { @@ -247,7 +250,7 @@ export class PictoController { @Post('copy') async copyPicto( @Body() copyPictoDto: copyPictoDto, - @AuthenticatedUser() user: User, + @GetUser() user: User, ): Promise { this.logger.verbose( `User "${user.username}" copying Picto with id ${copyPictoDto.pictoId}`,