From ffe397247efcf989385f12e57889a3348c650efc Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Sun, 28 May 2023 12:30:01 -0400 Subject: [PATCH] refactor(server): auth decorator (#2588) --- .../src/api-v1/album/album.controller.ts | 14 +++---- .../src/api-v1/asset/asset.controller.ts | 38 ++++++------------- .../immich/src/api-v1/tag/tag.controller.ts | 2 +- .../immich/src/controllers/auth.controller.ts | 11 ++---- .../src/controllers/oauth.controller.ts | 8 ++-- .../src/controllers/partner.controller.ts | 4 +- .../src/controllers/server-info.controller.ts | 8 ++-- .../src/controllers/shared-link.controller.ts | 9 ++--- .../immich/src/controllers/user.controller.ts | 16 +++----- .../src/decorators/authenticated.decorator.ts | 18 +++++++-- .../immich/src/utils/patch-open-api.util.ts | 5 +++ 11 files changed, 62 insertions(+), 71 deletions(-) diff --git a/server/apps/immich/src/api-v1/album/album.controller.ts b/server/apps/immich/src/api-v1/album/album.controller.ts index df4366944e..874435910f 100644 --- a/server/apps/immich/src/api-v1/album/album.controller.ts +++ b/server/apps/immich/src/api-v1/album/album.controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Post, Body, Param, Delete, Put, Query, Response } from '@nestjs/common'; import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe'; import { AlbumService } from './album.service'; -import { Authenticated } from '../../decorators/authenticated.decorator'; +import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator'; import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; import { AddAssetsDto } from './dto/add-assets.dto'; import { AddUsersDto } from './dto/add-users.dto'; @@ -32,24 +32,23 @@ const handleDownload = (download: DownloadArchive, res: Res) => { @ApiTags('Album') @Controller('album') +@Authenticated() @UseValidation() export class AlbumController { constructor(private readonly service: AlbumService) {} - @Authenticated() @Get('count-by-user-id') getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.getCountByUserId(authUser); } - @Authenticated() @Put(':id/users') addUsersToAlbum(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto, @Body() dto: AddUsersDto) { // TODO: Handle nonexistent sharedUserIds. return this.service.addUsers(authUser, id, dto); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Put(':id/assets') addAssetsToAlbum( @GetAuthUser() authUser: AuthUserDto, @@ -61,13 +60,12 @@ export class AlbumController { return this.service.addAssets(authUser, id, dto); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get(':id') getAlbumInfo(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto) { return this.service.get(authUser, id); } - @Authenticated() @Delete(':id/assets') removeAssetFromAlbum( @GetAuthUser() authUser: AuthUserDto, @@ -77,7 +75,6 @@ export class AlbumController { return this.service.removeAssets(authUser, id, dto); } - @Authenticated() @Delete(':id/user/:userId') removeUserFromAlbum( @GetAuthUser() authUser: AuthUserDto, @@ -87,7 +84,7 @@ export class AlbumController { return this.service.removeUser(authUser, id, userId); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get(':id/download') @ApiOkResponse({ content: { 'application/zip': { schema: { type: 'string', format: 'binary' } } } }) downloadArchive( @@ -100,7 +97,6 @@ export class AlbumController { return this.service.downloadArchive(authUser, id, dto).then((download) => handleDownload(download, res)); } - @Authenticated() @Post('create-shared-link') createAlbumSharedLink(@GetAuthUser() authUser: AuthUserDto, @Body() dto: CreateAlbumSharedLinkDto) { return this.service.createSharedLink(authUser, dto); diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 6e31d8fd4e..a042e980ec 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -19,7 +19,7 @@ import { StreamableFile, ParseFilePipe, } from '@nestjs/common'; -import { Authenticated } from '../../decorators/authenticated.decorator'; +import { Authenticated, SharedLinkRoute } from '../../decorators/authenticated.decorator'; import { AssetService } from './asset.service'; import { FileFieldsInterceptor } from '@nestjs/platform-express'; import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; @@ -68,10 +68,11 @@ function asStreamableFile({ stream, type, length }: ImmichReadStream) { @ApiTags('Asset') @Controller('asset') +@Authenticated() export class AssetController { constructor(private assetService: AssetService) {} - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Post('upload') @UseInterceptors( FileFieldsInterceptor( @@ -116,7 +117,7 @@ export class AssetController { return responseDto; } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('/download/:assetId') @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) async downloadFile( @@ -127,7 +128,7 @@ export class AssetController { return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Post('/download-files') @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) async downloadFiles( @@ -148,7 +149,7 @@ export class AssetController { /** * Current this is not used in any UI element */ - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('/download-library') @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) async downloadLibrary( @@ -165,7 +166,7 @@ export class AssetController { return stream; } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('/file/:assetId') @Header('Cache-Control', 'max-age=31536000') @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) @@ -180,7 +181,7 @@ export class AssetController { return this.assetService.serveFile(authUser, assetId, query, res, headers); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('/thumbnail/:assetId') @Header('Cache-Control', 'max-age=31536000') @ApiOkResponse({ content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } } }) @@ -195,25 +196,21 @@ export class AssetController { return this.assetService.getAssetThumbnail(assetId, query, res, headers); } - @Authenticated() @Get('/curated-objects') async getCuratedObjects(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getCuratedObject(authUser); } - @Authenticated() @Get('/curated-locations') async getCuratedLocations(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getCuratedLocation(authUser); } - @Authenticated() @Get('/search-terms') async getAssetSearchTerms(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getAssetSearchTerm(authUser); } - @Authenticated() @Post('/search') async searchAsset( @GetAuthUser() authUser: AuthUserDto, @@ -222,7 +219,6 @@ export class AssetController { return this.assetService.searchAsset(authUser, searchAssetDto); } - @Authenticated() @Post('/count-by-time-bucket') async getAssetCountByTimeBucket( @GetAuthUser() authUser: AuthUserDto, @@ -231,13 +227,11 @@ export class AssetController { return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto); } - @Authenticated() @Get('/count-by-user-id') async getAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getAssetCountByUserId(authUser); } - @Authenticated() @Get('/stat/archive') async getArchivedAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise { return this.assetService.getArchivedAssetCountByUserId(authUser); @@ -245,7 +239,6 @@ export class AssetController { /** * Get all AssetEntity belong to the user */ - @Authenticated() @Get('/') @ApiHeader({ name: 'if-none-match', @@ -260,7 +253,6 @@ export class AssetController { return this.assetService.getAllAssets(authUser, dto); } - @Authenticated() @Post('/time-bucket') async getAssetByTimeBucket( @GetAuthUser() authUser: AuthUserDto, @@ -272,7 +264,6 @@ export class AssetController { /** * Get all asset of a device that are in the database, ID only. */ - @Authenticated() @Get('/:deviceId') async getUserAssetsByDeviceId(@GetAuthUser() authUser: AuthUserDto, @Param() { deviceId }: DeviceIdDto) { return await this.assetService.getUserAssetsByDeviceId(authUser, deviceId); @@ -281,7 +272,7 @@ export class AssetController { /** * Get a single asset's information */ - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('/assetById/:assetId') async getAssetById( @GetAuthUser() authUser: AuthUserDto, @@ -294,7 +285,6 @@ export class AssetController { /** * Update an asset */ - @Authenticated() @Put('/:assetId') async updateAsset( @GetAuthUser() authUser: AuthUserDto, @@ -305,7 +295,6 @@ export class AssetController { return await this.assetService.updateAsset(authUser, assetId, dto); } - @Authenticated() @Delete('/') async deleteAsset( @GetAuthUser() authUser: AuthUserDto, @@ -318,7 +307,7 @@ export class AssetController { /** * Check duplicated asset before uploading - for Web upload used */ - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Post('/check') @HttpCode(200) async checkDuplicateAsset( @@ -331,7 +320,6 @@ export class AssetController { /** * Checks if multiple assets exist on the server and returns all existing - used by background backup */ - @Authenticated() @Post('/exist') @HttpCode(200) async checkExistingAssets( @@ -344,7 +332,6 @@ export class AssetController { /** * Checks if assets exist by checksums */ - @Authenticated() @Post('/bulk-upload-check') @HttpCode(200) bulkUploadCheck( @@ -354,7 +341,6 @@ export class AssetController { return this.assetService.bulkUploadCheck(authUser, dto); } - @Authenticated() @Post('/shared-link') async createAssetsSharedLink( @GetAuthUser() authUser: AuthUserDto, @@ -363,7 +349,7 @@ export class AssetController { return await this.assetService.createAssetsSharedLink(authUser, dto); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Patch('/shared-link/add') async addAssetsToSharedLink( @GetAuthUser() authUser: AuthUserDto, @@ -372,7 +358,7 @@ export class AssetController { return await this.assetService.addAssetsToSharedLink(authUser, dto); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Patch('/shared-link/remove') async removeAssetsFromSharedLink( @GetAuthUser() authUser: AuthUserDto, diff --git a/server/apps/immich/src/api-v1/tag/tag.controller.ts b/server/apps/immich/src/api-v1/tag/tag.controller.ts index d769b157aa..df12513df7 100644 --- a/server/apps/immich/src/api-v1/tag/tag.controller.ts +++ b/server/apps/immich/src/api-v1/tag/tag.controller.ts @@ -8,9 +8,9 @@ import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; import { mapTag, TagResponseDto } from '@app/domain'; import { UUIDParamDto } from '../../controllers/dto/uuid-param.dto'; -@Authenticated() @ApiTags('Tag') @Controller('tag') +@Authenticated() export class TagController { constructor(private readonly tagService: TagService) {} diff --git a/server/apps/immich/src/controllers/auth.controller.ts b/server/apps/immich/src/controllers/auth.controller.ts index 6642911d30..4208357159 100644 --- a/server/apps/immich/src/controllers/auth.controller.ts +++ b/server/apps/immich/src/controllers/auth.controller.ts @@ -19,16 +19,18 @@ import { Body, Controller, Delete, Get, Param, Post, Req, Res } from '@nestjs/co import { ApiBadRequestResponse, ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { GetAuthUser, GetLoginDetails } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; +import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Authentication') @Controller('auth') +@Authenticated() @UseValidation() export class AuthController { constructor(private readonly service: AuthService) {} + @PublicRoute() @Post('login') async login( @Body() loginCredential: LoginCredentialDto, @@ -40,43 +42,38 @@ export class AuthController { return response; } + @PublicRoute() @Post('admin-sign-up') @ApiBadRequestResponse({ description: 'The server already has an admin' }) adminSignUp(@Body() signUpCredential: SignUpDto): Promise { return this.service.adminSignUp(signUpCredential); } - @Authenticated() @Get('devices') getAuthDevices(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.getDevices(authUser); } - @Authenticated() @Delete('devices') logoutAuthDevices(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.logoutDevices(authUser); } - @Authenticated() @Delete('devices/:id') logoutAuthDevice(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.logoutDevice(authUser, id); } - @Authenticated() @Post('validateToken') validateAccessToken(): ValidateAccessTokenResponseDto { return { authStatus: true }; } - @Authenticated() @Post('change-password') changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise { return this.service.changePassword(authUser, dto); } - @Authenticated() @Post('logout') logout( @Req() req: Request, diff --git a/server/apps/immich/src/controllers/oauth.controller.ts b/server/apps/immich/src/controllers/oauth.controller.ts index 0a65e36417..b85b419ef7 100644 --- a/server/apps/immich/src/controllers/oauth.controller.ts +++ b/server/apps/immich/src/controllers/oauth.controller.ts @@ -12,15 +12,17 @@ import { Body, Controller, Get, HttpStatus, Post, Redirect, Req, Res } from '@ne import { ApiTags } from '@nestjs/swagger'; import { Request, Response } from 'express'; import { GetAuthUser, GetLoginDetails } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; +import { Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('OAuth') @Controller('oauth') +@Authenticated() @UseValidation() export class OAuthController { constructor(private service: OAuthService) {} + @PublicRoute() @Get('mobile-redirect') @Redirect() mobileRedirect(@Req() req: Request) { @@ -30,11 +32,13 @@ export class OAuthController { }; } + @PublicRoute() @Post('config') generateConfig(@Body() dto: OAuthConfigDto): Promise { return this.service.generateConfig(dto); } + @PublicRoute() @Post('callback') async callback( @Res({ passthrough: true }) res: Response, @@ -46,13 +50,11 @@ export class OAuthController { return response; } - @Authenticated() @Post('link') link(@GetAuthUser() authUser: AuthUserDto, @Body() dto: OAuthCallbackDto): Promise { return this.service.link(authUser, dto); } - @Authenticated() @Post('unlink') unlink(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.unlink(authUser); diff --git a/server/apps/immich/src/controllers/partner.controller.ts b/server/apps/immich/src/controllers/partner.controller.ts index e26d16dfec..b2f9639b9b 100644 --- a/server/apps/immich/src/controllers/partner.controller.ts +++ b/server/apps/immich/src/controllers/partner.controller.ts @@ -8,11 +8,11 @@ import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('Partner') @Controller('partner') +@Authenticated() @UseValidation() export class PartnerController { constructor(private service: PartnerService) {} - @Authenticated() @Get() @ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true }) getPartners( @@ -22,13 +22,11 @@ export class PartnerController { return this.service.getAll(authUser, direction); } - @Authenticated() @Post(':id') createPartner(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.create(authUser, id); } - @Authenticated() @Delete(':id') removePartner(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(authUser, id); diff --git a/server/apps/immich/src/controllers/server-info.controller.ts b/server/apps/immich/src/controllers/server-info.controller.ts index 86a05525ca..7819e2bd36 100644 --- a/server/apps/immich/src/controllers/server-info.controller.ts +++ b/server/apps/immich/src/controllers/server-info.controller.ts @@ -7,32 +7,34 @@ import { } from '@app/domain'; import { Controller, Get } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Authenticated } from '../decorators/authenticated.decorator'; +import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; @ApiTags('Server Info') @Controller('server-info') +@Authenticated() @UseValidation() export class ServerInfoController { constructor(private service: ServerInfoService) {} - @Authenticated() @Get() getServerInfo(): Promise { return this.service.getInfo(); } + @PublicRoute() @Get('/ping') pingServer(): ServerPingResponse { return this.service.ping(); } + @PublicRoute() @Get('/version') getServerVersion(): ServerVersionReponseDto { return this.service.getVersion(); } - @Authenticated({ admin: true }) + @AdminRoute() @Get('/stats') getStats(): Promise { return this.service.getStats(); diff --git a/server/apps/immich/src/controllers/shared-link.controller.ts b/server/apps/immich/src/controllers/shared-link.controller.ts index 9590132a66..40a483bbec 100644 --- a/server/apps/immich/src/controllers/shared-link.controller.ts +++ b/server/apps/immich/src/controllers/shared-link.controller.ts @@ -2,29 +2,28 @@ import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } f import { Body, Controller, Delete, Get, Param, Patch } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; -import { Authenticated } from '../decorators/authenticated.decorator'; +import { Authenticated, SharedLinkRoute } from '../decorators/authenticated.decorator'; import { UseValidation } from '../decorators/use-validation.decorator'; import { UUIDParamDto } from './dto/uuid-param.dto'; @ApiTags('share') @Controller('share') +@Authenticated() @UseValidation() export class SharedLinkController { constructor(private readonly service: ShareService) {} - @Authenticated() @Get() getAllSharedLinks(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.getAll(authUser); } - @Authenticated({ isShared: true }) + @SharedLinkRoute() @Get('me') getMySharedLink(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.getMine(authUser); } - @Authenticated() @Get(':id') getSharedLinkById( @GetAuthUser() authUser: AuthUserDto, @@ -33,13 +32,11 @@ export class SharedLinkController { return this.service.getById(authUser, id, true); } - @Authenticated() @Delete(':id') removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param() { id }: UUIDParamDto): Promise { return this.service.remove(authUser, id); } - @Authenticated() @Patch(':id') editSharedLink( @GetAuthUser() authUser: AuthUserDto, diff --git a/server/apps/immich/src/controllers/user.controller.ts b/server/apps/immich/src/controllers/user.controller.ts index c1da0427aa..ad280eeaf6 100644 --- a/server/apps/immich/src/controllers/user.controller.ts +++ b/server/apps/immich/src/controllers/user.controller.ts @@ -14,7 +14,7 @@ import { Header, } from '@nestjs/common'; import { UserService } from '@app/domain'; -import { Authenticated } from '../decorators/authenticated.decorator'; +import { AdminRoute, Authenticated, PublicRoute } from '../decorators/authenticated.decorator'; import { AuthUserDto, GetAuthUser } from '../decorators/auth-user.decorator'; import { CreateUserDto } from '@app/domain'; import { UpdateUserDto } from '@app/domain'; @@ -32,58 +32,55 @@ import { UserIdDto } from '@app/domain/user/dto/user-id.dto'; @ApiTags('User') @Controller('user') +@Authenticated() @UseValidation() export class UserController { constructor(private service: UserService) {} - @Authenticated() @Get() getAllUsers(@GetAuthUser() authUser: AuthUserDto, @Query('isAll') isAll: boolean): Promise { return this.service.getAllUsers(authUser, isAll); } - @Authenticated() @Get('/info/:userId') getUserById(@Param() { userId }: UserIdDto): Promise { return this.service.getUserById(userId); } - @Authenticated() @Get('me') getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise { return this.service.getUserInfo(authUser); } - @Authenticated({ admin: true }) + @AdminRoute() @Post() createUser(@Body() createUserDto: CreateUserDto): Promise { return this.service.createUser(createUserDto); } + @PublicRoute() @Get('/count') getUserCount(@Query() dto: UserCountDto): Promise { return this.service.getUserCount(dto); } - @Authenticated({ admin: true }) + @AdminRoute() @Delete('/:userId') deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise { return this.service.deleteUser(authUser, userId); } - @Authenticated({ admin: true }) + @AdminRoute() @Post('/:userId/restore') restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param() { userId }: UserIdDto): Promise { return this.service.restoreUser(authUser, userId); } - @Authenticated() @Put() updateUser(@GetAuthUser() authUser: AuthUserDto, @Body() updateUserDto: UpdateUserDto): Promise { return this.service.updateUser(authUser, updateUserDto); } - @Authenticated() @UseInterceptors(FileInterceptor('file', profileImageUploadOption)) @ApiConsumes('multipart/form-data') @ApiBody({ @@ -98,7 +95,6 @@ export class UserController { return this.service.createProfileImage(authUser, fileInfo); } - @Authenticated() @Get('/profile-image/:userId') @Header('Cache-Control', 'max-age=600') async getProfileImage(@Param() { userId }: UserIdDto, @Response({ passthrough: true }) res: Res): Promise { diff --git a/server/apps/immich/src/decorators/authenticated.decorator.ts b/server/apps/immich/src/decorators/authenticated.decorator.ts index 5101db73cd..68a01787c0 100644 --- a/server/apps/immich/src/decorators/authenticated.decorator.ts +++ b/server/apps/immich/src/decorators/authenticated.decorator.ts @@ -11,8 +11,16 @@ export enum Metadata { AUTH_ROUTE = 'auth_route', ADMIN_ROUTE = 'admin_route', SHARED_ROUTE = 'shared_route', + PUBLIC_SECURITY = 'public_security', } +const adminDecorator = SetMetadata(Metadata.ADMIN_ROUTE, true); + +const sharedLinkDecorators = [ + SetMetadata(Metadata.SHARED_ROUTE, true), + ApiQuery({ name: 'key', type: String, required: false }), +]; + export const Authenticated = (options: AuthenticatedOptions = {}) => { const decorators: MethodDecorator[] = [ ApiBearerAuth(), @@ -22,13 +30,17 @@ export const Authenticated = (options: AuthenticatedOptions = {}) => { ]; if (options.admin) { - decorators.push(SetMetadata(Metadata.ADMIN_ROUTE, true)); + decorators.push(adminDecorator); } if (options.isShared) { - decorators.push(SetMetadata(Metadata.SHARED_ROUTE, true)); - decorators.push(ApiQuery({ name: 'key', type: String, required: false })); + decorators.push(...sharedLinkDecorators); } return applyDecorators(...decorators); }; + +export const PublicRoute = () => + applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY)); +export const SharedLinkRoute = () => applyDecorators(...sharedLinkDecorators); +export const AdminRoute = () => adminDecorator; diff --git a/server/apps/immich/src/utils/patch-open-api.util.ts b/server/apps/immich/src/utils/patch-open-api.util.ts index 1b8c7882bd..6c56c2b627 100644 --- a/server/apps/immich/src/utils/patch-open-api.util.ts +++ b/server/apps/immich/src/utils/patch-open-api.util.ts @@ -1,4 +1,5 @@ import { OpenAPIObject } from '@nestjs/swagger'; +import { Metadata } from '../decorators/authenticated.decorator'; export function patchOpenAPI(document: OpenAPIObject) { for (const path of Object.values(document.paths)) { @@ -18,6 +19,10 @@ export function patchOpenAPI(document: OpenAPIObject) { continue; } + if ((operation.security || []).find((item) => !!item[Metadata.PUBLIC_SECURITY])) { + delete operation.security; + } + if (operation.summary === '') { delete operation.summary; }