You've already forked immich
							
							
				mirror of
				https://github.com/immich-app/immich.git
				synced 2025-10-31 00:18:28 +02:00 
			
		
		
		
	refactor(server): auth route metadata (#9344)
This commit is contained in:
		| @@ -5806,15 +5806,6 @@ | ||||
|           } | ||||
|         }, | ||||
|         "security": [ | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
|           { | ||||
|             "cookie": [] | ||||
|           }, | ||||
|           { | ||||
|             "api_key": [] | ||||
|           }, | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
| @@ -5942,15 +5933,6 @@ | ||||
|           } | ||||
|         }, | ||||
|         "security": [ | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
|           { | ||||
|             "cookie": [] | ||||
|           }, | ||||
|           { | ||||
|             "api_key": [] | ||||
|           }, | ||||
|           { | ||||
|             "bearer": [] | ||||
|           }, | ||||
|   | ||||
| @@ -15,21 +15,23 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Activity') | ||||
| @Controller('activity') | ||||
| @Authenticated() | ||||
| export class ActivityController { | ||||
|   constructor(private service: ActivityService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getActivities(@Auth() auth: AuthDto, @Query() dto: ActivitySearchDto): Promise<ActivityResponseDto[]> { | ||||
|     return this.service.getAll(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get('statistics') | ||||
|   @Authenticated() | ||||
|   getActivityStatistics(@Auth() auth: AuthDto, @Query() dto: ActivityDto): Promise<ActivityStatisticsResponseDto> { | ||||
|     return this.service.getStatistics(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   async createActivity( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: ActivityCreateDto, | ||||
| @@ -44,6 +46,7 @@ export class ActivityController { | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteActivity(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.delete(auth, id); | ||||
|   } | ||||
|   | ||||
| @@ -12,32 +12,34 @@ import { | ||||
| } from 'src/dtos/album.dto'; | ||||
| import { BulkIdResponseDto, BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { AlbumService } from 'src/services/album.service'; | ||||
| import { ParseMeUUIDPipe, UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Album') | ||||
| @Controller('album') | ||||
| @Authenticated() | ||||
| export class AlbumController { | ||||
|   constructor(private service: AlbumService) {} | ||||
|  | ||||
|   @Get('count') | ||||
|   @Authenticated() | ||||
|   getAlbumCount(@Auth() auth: AuthDto): Promise<AlbumCountResponseDto> { | ||||
|     return this.service.getCount(auth); | ||||
|   } | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getAllAlbums(@Auth() auth: AuthDto, @Query() query: GetAlbumsDto): Promise<AlbumResponseDto[]> { | ||||
|     return this.service.getAll(auth, query); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createAlbum(@Auth() auth: AuthDto, @Body() dto: CreateAlbumDto): Promise<AlbumResponseDto> { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   @Get(':id') | ||||
|   getAlbumInfo( | ||||
|     @Auth() auth: AuthDto, | ||||
| @@ -48,6 +50,7 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Patch(':id') | ||||
|   @Authenticated() | ||||
|   updateAlbumInfo( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -57,12 +60,13 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @Authenticated() | ||||
|   deleteAlbum(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto) { | ||||
|     return this.service.delete(auth, id); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Put(':id/assets') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   addAssetsToAlbum( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -72,6 +76,7 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id/assets') | ||||
|   @Authenticated() | ||||
|   removeAssetFromAlbum( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: BulkIdsDto, | ||||
| @@ -81,6 +86,7 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Put(':id/users') | ||||
|   @Authenticated() | ||||
|   addUsersToAlbum( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -90,6 +96,7 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Put(':id/user/:userId') | ||||
|   @Authenticated() | ||||
|   updateAlbumUser( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -100,6 +107,7 @@ export class AlbumController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id/user/:userId') | ||||
|   @Authenticated() | ||||
|   removeUserFromAlbum( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
|   | ||||
| @@ -8,26 +8,29 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('API Key') | ||||
| @Controller('api-key') | ||||
| @Authenticated() | ||||
| export class APIKeyController { | ||||
|   constructor(private service: APIKeyService) {} | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createApiKey(@Auth() auth: AuthDto, @Body() dto: APIKeyCreateDto): Promise<APIKeyCreateResponseDto> { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getApiKeys(@Auth() auth: AuthDto): Promise<APIKeyResponseDto[]> { | ||||
|     return this.service.getAll(auth); | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated() | ||||
|   getApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<APIKeyResponseDto> { | ||||
|     return this.service.getById(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   updateApiKey( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -37,6 +40,7 @@ export class APIKeyController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @Authenticated() | ||||
|   deleteApiKey(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.delete(auth, id); | ||||
|   } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| import { Controller, Get, Header } from '@nestjs/common'; | ||||
| import { ApiExcludeEndpoint } from '@nestjs/swagger'; | ||||
| import { PublicRoute } from 'src/middleware/auth.guard'; | ||||
| import { SystemConfigService } from 'src/services/system-config.service'; | ||||
|  | ||||
| @Controller() | ||||
| @@ -18,7 +17,6 @@ export class AppController { | ||||
|   } | ||||
|  | ||||
|   @ApiExcludeEndpoint() | ||||
|   @PublicRoute() | ||||
|   @Get('custom.css') | ||||
|   @Header('Content-Type', 'text/css') | ||||
|   getCustomCss() { | ||||
|   | ||||
| @@ -31,7 +31,7 @@ import { | ||||
| } from 'src/dtos/asset-v1.dto'; | ||||
| import { AuthDto, ImmichHeader } from 'src/dtos/auth.dto'; | ||||
| import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor'; | ||||
| import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; | ||||
| import { FileUploadInterceptor, ImmichFile, Route, mapToUploadFile } from 'src/middleware/file-upload.interceptor'; | ||||
| import { AssetServiceV1 } from 'src/services/asset-v1.service'; | ||||
| import { sendFile } from 'src/utils/file'; | ||||
| @@ -45,11 +45,9 @@ interface UploadFiles { | ||||
|  | ||||
| @ApiTags('Asset') | ||||
| @Controller(Route.ASSET) | ||||
| @Authenticated() | ||||
| export class AssetControllerV1 { | ||||
|   constructor(private service: AssetServiceV1) {} | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Post('upload') | ||||
|   @UseInterceptors(AssetUploadInterceptor, FileUploadInterceptor) | ||||
|   @ApiConsumes('multipart/form-data') | ||||
| @@ -58,10 +56,8 @@ export class AssetControllerV1 { | ||||
|     description: 'sha1 checksum that can be used for duplicate detection before the file is uploaded', | ||||
|     required: false, | ||||
|   }) | ||||
|   @ApiBody({ | ||||
|     description: 'Asset Upload Information', | ||||
|     type: CreateAssetDto, | ||||
|   }) | ||||
|   @ApiBody({ description: 'Asset Upload Information', type: CreateAssetDto }) | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   async uploadFile( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles, | ||||
| @@ -89,9 +85,9 @@ export class AssetControllerV1 { | ||||
|     return responseDto; | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Get('/file/:id') | ||||
|   @FileResponse() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   async serveFile( | ||||
|     @Res() res: Response, | ||||
|     @Next() next: NextFunction, | ||||
| @@ -102,9 +98,9 @@ export class AssetControllerV1 { | ||||
|     await sendFile(res, next, () => this.service.serveFile(auth, id, dto)); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Get('/thumbnail/:id') | ||||
|   @FileResponse() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   async getAssetThumbnail( | ||||
|     @Res() res: Response, | ||||
|     @Next() next: NextFunction, | ||||
| @@ -125,6 +121,7 @@ export class AssetControllerV1 { | ||||
|     required: false, | ||||
|     schema: { type: 'string' }, | ||||
|   }) | ||||
|   @Authenticated() | ||||
|   getAllAssets(@Auth() auth: AuthDto, @Query() dto: AssetSearchDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getAllAssets(auth, dto); | ||||
|   } | ||||
| @@ -134,6 +131,7 @@ export class AssetControllerV1 { | ||||
|    */ | ||||
|   @Post('/exist') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   checkExistingAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: CheckExistingAssetsDto, | ||||
| @@ -146,6 +144,7 @@ export class AssetControllerV1 { | ||||
|    */ | ||||
|   @Post('/bulk-upload-check') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   checkBulkUpload( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: AssetBulkUploadCheckDto, | ||||
|   | ||||
| @@ -14,28 +14,30 @@ import { | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| import { MapMarkerDto, MapMarkerResponseDto, MemoryLaneDto } from 'src/dtos/search.dto'; | ||||
| import { UpdateStackParentDto } from 'src/dtos/stack.dto'; | ||||
| import { Auth, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { Route } from 'src/middleware/file-upload.interceptor'; | ||||
| import { AssetService } from 'src/services/asset.service'; | ||||
| import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Asset') | ||||
| @Controller(Route.ASSET) | ||||
| @Authenticated() | ||||
| export class AssetController { | ||||
|   constructor(private service: AssetService) {} | ||||
|  | ||||
|   @Get('map-marker') | ||||
|   @Authenticated() | ||||
|   getMapMarkers(@Auth() auth: AuthDto, @Query() options: MapMarkerDto): Promise<MapMarkerResponseDto[]> { | ||||
|     return this.service.getMapMarkers(auth, options); | ||||
|   } | ||||
|  | ||||
|   @Get('memory-lane') | ||||
|   @Authenticated() | ||||
|   getMemoryLane(@Auth() auth: AuthDto, @Query() dto: MemoryLaneDto): Promise<MemoryLaneResponseDto[]> { | ||||
|     return this.service.getMemoryLane(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get('random') | ||||
|   @Authenticated() | ||||
|   getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getRandom(auth, dto.count ?? 1); | ||||
|   } | ||||
| @@ -44,46 +46,53 @@ export class AssetController { | ||||
|    * Get all asset of a device that are in the database, ID only. | ||||
|    */ | ||||
|   @Get('/device/:deviceId') | ||||
|   @Authenticated() | ||||
|   getAllUserAssetsByDeviceId(@Auth() auth: AuthDto, @Param() { deviceId }: DeviceIdDto) { | ||||
|     return this.service.getUserAssetsByDeviceId(auth, deviceId); | ||||
|   } | ||||
|  | ||||
|   @Get('statistics') | ||||
|   @Authenticated() | ||||
|   getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> { | ||||
|     return this.service.getStatistics(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Post('jobs') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   runAssetJobs(@Auth() auth: AuthDto, @Body() dto: AssetJobsDto): Promise<void> { | ||||
|     return this.service.run(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Put() | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> { | ||||
|     return this.service.updateAll(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Delete() | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise<void> { | ||||
|     return this.service.deleteAll(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Put('stack/parent') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   updateStackParent(@Auth() auth: AuthDto, @Body() dto: UpdateStackParentDto): Promise<void> { | ||||
|     return this.service.updateStackParent(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Get(':id') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> { | ||||
|     return this.service.get(auth, id) as Promise<AssetResponseDto>; | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   updateAsset( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
|   | ||||
| @@ -7,11 +7,11 @@ import { AuditService } from 'src/services/audit.service'; | ||||
|  | ||||
| @ApiTags('Audit') | ||||
| @Controller('audit') | ||||
| @Authenticated() | ||||
| export class AuditController { | ||||
|   constructor(private service: AuditService) {} | ||||
|  | ||||
|   @Get('deletes') | ||||
|   @Authenticated() | ||||
|   getAuditDeletes(@Auth() auth: AuthDto, @Query() dto: AuditDeletesDto): Promise<AuditDeletesResponseDto> { | ||||
|     return this.service.getDeletes(auth, dto); | ||||
|   } | ||||
|   | ||||
| @@ -13,17 +13,15 @@ import { | ||||
|   ValidateAccessTokenResponseDto, | ||||
| } from 'src/dtos/auth.dto'; | ||||
| import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; | ||||
| import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; | ||||
| import { AuthService, LoginDetails } from 'src/services/auth.service'; | ||||
| import { respondWithCookie, respondWithoutCookie } from 'src/utils/response'; | ||||
|  | ||||
| @ApiTags('Authentication') | ||||
| @Controller('auth') | ||||
| @Authenticated() | ||||
| export class AuthController { | ||||
|   constructor(private service: AuthService) {} | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Post('login') | ||||
|   async login( | ||||
|     @Body() loginCredential: LoginCredentialDto, | ||||
| @@ -41,7 +39,6 @@ export class AuthController { | ||||
|     }); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Post('admin-sign-up') | ||||
|   signUpAdmin(@Body() dto: SignUpDto): Promise<UserResponseDto> { | ||||
|     return this.service.adminSignUp(dto); | ||||
| @@ -49,18 +46,21 @@ export class AuthController { | ||||
|  | ||||
|   @Post('validateToken') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   validateAccessToken(): ValidateAccessTokenResponseDto { | ||||
|     return { authStatus: true }; | ||||
|   } | ||||
|  | ||||
|   @Post('change-password') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserResponseDto> { | ||||
|     return this.service.changePassword(auth, dto).then(mapUser); | ||||
|   } | ||||
|  | ||||
|   @Post('logout') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   async logout( | ||||
|     @Req() request: Request, | ||||
|     @Res({ passthrough: true }) res: Response, | ||||
|   | ||||
| @@ -4,35 +4,34 @@ import { NextFunction, Response } from 'express'; | ||||
| import { AssetIdsDto } from 'src/dtos/asset.dto'; | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| import { DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto'; | ||||
| import { Auth, Authenticated, FileResponse, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; | ||||
| import { DownloadService } from 'src/services/download.service'; | ||||
| import { asStreamableFile, sendFile } from 'src/utils/file'; | ||||
| import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Download') | ||||
| @Controller('download') | ||||
| @Authenticated() | ||||
| export class DownloadController { | ||||
|   constructor(private service: DownloadService) {} | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Post('info') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> { | ||||
|     return this.service.getDownloadInfo(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Post('archive') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @FileResponse() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> { | ||||
|     return this.service.downloadArchive(auth, dto).then(asStreamableFile); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Post('asset/:id') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @FileResponse() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   async downloadFile( | ||||
|     @Res() res: Response, | ||||
|     @Next() next: NextFunction, | ||||
|   | ||||
| @@ -8,16 +8,17 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Face') | ||||
| @Controller('face') | ||||
| @Authenticated() | ||||
| export class FaceController { | ||||
|   constructor(private service: PersonService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getFaces(@Auth() auth: AuthDto, @Query() dto: FaceDto): Promise<AssetFaceResponseDto[]> { | ||||
|     return this.service.getFacesById(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   reassignFacesById( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
|   | ||||
| @@ -1,29 +1,28 @@ | ||||
| import { Body, Controller, Get, Post } from '@nestjs/common'; | ||||
| import { ApiTags } from '@nestjs/swagger'; | ||||
| import { FileChecksumDto, FileChecksumResponseDto, FileReportDto, FileReportFixDto } from 'src/dtos/audit.dto'; | ||||
| import { AdminRoute, Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { AuditService } from 'src/services/audit.service'; | ||||
|  | ||||
| @ApiTags('File Report') | ||||
| @Controller('report') | ||||
| @Authenticated() | ||||
| export class ReportController { | ||||
|   constructor(private service: AuditService) {} | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Get() | ||||
|   @Authenticated({ admin: true }) | ||||
|   getAuditFiles(): Promise<FileReportDto> { | ||||
|     return this.service.getFileReport(); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Post('/checksum') | ||||
|   @Post('checksum') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getFileChecksums(@Body() dto: FileChecksumDto): Promise<FileChecksumResponseDto[]> { | ||||
|     return this.service.getChecksums(dto); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Post('/fix') | ||||
|   @Post('fix') | ||||
|   @Authenticated({ admin: true }) | ||||
|   fixAuditFiles(@Body() dto: FileReportFixDto): Promise<void> { | ||||
|     return this.service.fixItems(dto.items); | ||||
|   } | ||||
|   | ||||
| @@ -6,16 +6,17 @@ import { JobService } from 'src/services/job.service'; | ||||
|  | ||||
| @ApiTags('Job') | ||||
| @Controller('jobs') | ||||
| @Authenticated({ admin: true }) | ||||
| export class JobController { | ||||
|   constructor(private service: JobService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated({ admin: true }) | ||||
|   getAllJobsStatus(): Promise<AllJobStatusResponseDto> { | ||||
|     return this.service.getAllJobsStatus(); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated({ admin: true }) | ||||
|   sendJobCommand(@Param() { id }: JobIdParamDto, @Body() dto: JobCommandDto): Promise<JobStatusDto> { | ||||
|     return this.service.handleCommand(id, dto); | ||||
|   } | ||||
|   | ||||
| @@ -10,39 +10,42 @@ import { | ||||
|   ValidateLibraryDto, | ||||
|   ValidateLibraryResponseDto, | ||||
| } from 'src/dtos/library.dto'; | ||||
| import { AdminRoute, Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { LibraryService } from 'src/services/library.service'; | ||||
| import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Library') | ||||
| @Controller('library') | ||||
| @Authenticated() | ||||
| @AdminRoute() | ||||
| export class LibraryController { | ||||
|   constructor(private service: LibraryService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated({ admin: true }) | ||||
|   getAllLibraries(@Query() dto: SearchLibraryDto): Promise<LibraryResponseDto[]> { | ||||
|     return this.service.getAll(dto); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated({ admin: true }) | ||||
|   createLibrary(@Body() dto: CreateLibraryDto): Promise<LibraryResponseDto> { | ||||
|     return this.service.create(dto); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated({ admin: true }) | ||||
|   updateLibrary(@Param() { id }: UUIDParamDto, @Body() dto: UpdateLibraryDto): Promise<LibraryResponseDto> { | ||||
|     return this.service.update(id, dto); | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getLibrary(@Param() { id }: UUIDParamDto): Promise<LibraryResponseDto> { | ||||
|     return this.service.get(id); | ||||
|   } | ||||
|  | ||||
|   @Post(':id/validate') | ||||
|   @HttpCode(200) | ||||
|   @Authenticated({ admin: true }) | ||||
|   // TODO: change endpoint to validate current settings instead | ||||
|   validate(@Param() { id }: UUIDParamDto, @Body() dto: ValidateLibraryDto): Promise<ValidateLibraryResponseDto> { | ||||
|     return this.service.validate(id, dto); | ||||
| @@ -50,23 +53,27 @@ export class LibraryController { | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated({ admin: true }) | ||||
|   deleteLibrary(@Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.delete(id); | ||||
|   } | ||||
|  | ||||
|   @Get(':id/statistics') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getLibraryStatistics(@Param() { id }: UUIDParamDto): Promise<LibraryStatsResponseDto> { | ||||
|     return this.service.getStatistics(id); | ||||
|   } | ||||
|  | ||||
|   @Post(':id/scan') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated({ admin: true }) | ||||
|   scanLibrary(@Param() { id }: UUIDParamDto, @Body() dto: ScanLibraryDto) { | ||||
|     return this.service.queueScan(id, dto); | ||||
|   } | ||||
|  | ||||
|   @Post(':id/removeOffline') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated({ admin: true }) | ||||
|   removeOfflineFiles(@Param() { id }: UUIDParamDto) { | ||||
|     return this.service.queueRemoveOffline(id); | ||||
|   } | ||||
|   | ||||
| @@ -9,26 +9,29 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Memory') | ||||
| @Controller('memories') | ||||
| @Authenticated() | ||||
| export class MemoryController { | ||||
|   constructor(private service: MemoryService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   searchMemories(@Auth() auth: AuthDto): Promise<MemoryResponseDto[]> { | ||||
|     return this.service.search(auth); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createMemory(@Auth() auth: AuthDto, @Body() dto: MemoryCreateDto): Promise<MemoryResponseDto> { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated() | ||||
|   getMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<MemoryResponseDto> { | ||||
|     return this.service.get(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   updateMemory( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -39,11 +42,13 @@ export class MemoryController { | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteMemory(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.remove(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id/assets') | ||||
|   @Authenticated() | ||||
|   addMemoryAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -54,6 +59,7 @@ export class MemoryController { | ||||
|  | ||||
|   @Delete(':id/assets') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   removeMemoryAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: BulkIdsDto, | ||||
|   | ||||
| @@ -11,17 +11,15 @@ import { | ||||
|   OAuthConfigDto, | ||||
| } from 'src/dtos/auth.dto'; | ||||
| import { UserResponseDto } from 'src/dtos/user.dto'; | ||||
| import { Auth, Authenticated, GetLoginDetails, PublicRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; | ||||
| import { AuthService, LoginDetails } from 'src/services/auth.service'; | ||||
| import { respondWithCookie } from 'src/utils/response'; | ||||
|  | ||||
| @ApiTags('OAuth') | ||||
| @Controller('oauth') | ||||
| @Authenticated() | ||||
| export class OAuthController { | ||||
|   constructor(private service: AuthService) {} | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('mobile-redirect') | ||||
|   @Redirect() | ||||
|   redirectOAuthToMobile(@Req() request: Request) { | ||||
| @@ -31,13 +29,11 @@ export class OAuthController { | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Post('authorize') | ||||
|   startOAuth(@Body() dto: OAuthConfigDto): Promise<OAuthAuthorizeResponseDto> { | ||||
|     return this.service.authorize(dto); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Post('callback') | ||||
|   async finishOAuth( | ||||
|     @Res({ passthrough: true }) res: Response, | ||||
| @@ -56,11 +52,13 @@ export class OAuthController { | ||||
|   } | ||||
|  | ||||
|   @Post('link') | ||||
|   @Authenticated() | ||||
|   linkOAuthAccount(@Auth() auth: AuthDto, @Body() dto: OAuthCallbackDto): Promise<UserResponseDto> { | ||||
|     return this.service.link(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Post('unlink') | ||||
|   @Authenticated() | ||||
|   unlinkOAuthAccount(@Auth() auth: AuthDto): Promise<UserResponseDto> { | ||||
|     return this.service.unlink(auth); | ||||
|   } | ||||
|   | ||||
| @@ -9,23 +9,25 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Partner') | ||||
| @Controller('partner') | ||||
| @Authenticated() | ||||
| export class PartnerController { | ||||
|   constructor(private service: PartnerService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @ApiQuery({ name: 'direction', type: 'string', enum: PartnerDirection, required: true }) | ||||
|   @Authenticated() | ||||
|   // TODO: remove 'direction' and convert to full query dto | ||||
|   getPartners(@Auth() auth: AuthDto, @Query('direction') direction: PartnerDirection): Promise<PartnerResponseDto[]> { | ||||
|     return this.service.getAll(auth, direction); | ||||
|   } | ||||
|  | ||||
|   @Post(':id') | ||||
|   @Authenticated() | ||||
|   createPartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PartnerResponseDto> { | ||||
|     return this.service.create(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   updatePartner( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -35,6 +37,7 @@ export class PartnerController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @Authenticated() | ||||
|   removePartner(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.remove(auth, id); | ||||
|   } | ||||
|   | ||||
| @@ -22,31 +22,35 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Person') | ||||
| @Controller('person') | ||||
| @Authenticated() | ||||
| export class PersonController { | ||||
|   constructor(private service: PersonService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getAllPeople(@Auth() auth: AuthDto, @Query() withHidden: PersonSearchDto): Promise<PeopleResponseDto> { | ||||
|     return this.service.getAll(auth, withHidden); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createPerson(@Auth() auth: AuthDto, @Body() dto: PersonCreateDto): Promise<PersonResponseDto> { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Put() | ||||
|   @Authenticated() | ||||
|   updatePeople(@Auth() auth: AuthDto, @Body() dto: PeopleUpdateDto): Promise<BulkIdResponseDto[]> { | ||||
|     return this.service.updateAll(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated() | ||||
|   getPerson(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonResponseDto> { | ||||
|     return this.service.getById(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id') | ||||
|   @Authenticated() | ||||
|   updatePerson( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -56,12 +60,14 @@ export class PersonController { | ||||
|   } | ||||
|  | ||||
|   @Get(':id/statistics') | ||||
|   @Authenticated() | ||||
|   getPersonStatistics(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<PersonStatisticsResponseDto> { | ||||
|     return this.service.getStatistics(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Get(':id/thumbnail') | ||||
|   @FileResponse() | ||||
|   @Authenticated() | ||||
|   async getPersonThumbnail( | ||||
|     @Res() res: Response, | ||||
|     @Next() next: NextFunction, | ||||
| @@ -72,11 +78,13 @@ export class PersonController { | ||||
|   } | ||||
|  | ||||
|   @Get(':id/assets') | ||||
|   @Authenticated() | ||||
|   getPersonAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getAssets(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id/reassign') | ||||
|   @Authenticated() | ||||
|   reassignFaces( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -86,6 +94,7 @@ export class PersonController { | ||||
|   } | ||||
|  | ||||
|   @Post(':id/merge') | ||||
|   @Authenticated() | ||||
|   mergePerson( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
|   | ||||
| @@ -18,43 +18,49 @@ import { SearchService } from 'src/services/search.service'; | ||||
|  | ||||
| @ApiTags('Search') | ||||
| @Controller('search') | ||||
| @Authenticated() | ||||
| export class SearchController { | ||||
|   constructor(private service: SearchService) {} | ||||
|  | ||||
|   @Post('metadata') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   searchMetadata(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise<SearchResponseDto> { | ||||
|     return this.service.searchMetadata(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Post('smart') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise<SearchResponseDto> { | ||||
|     return this.service.searchSmart(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get('explore') | ||||
|   @Authenticated() | ||||
|   getExploreData(@Auth() auth: AuthDto): Promise<SearchExploreResponseDto[]> { | ||||
|     return this.service.getExploreData(auth) as Promise<SearchExploreResponseDto[]>; | ||||
|   } | ||||
|  | ||||
|   @Get('person') | ||||
|   @Authenticated() | ||||
|   searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> { | ||||
|     return this.service.searchPerson(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get('places') | ||||
|   @Authenticated() | ||||
|   searchPlaces(@Query() dto: SearchPlacesDto): Promise<PlacesResponseDto[]> { | ||||
|     return this.service.searchPlaces(dto); | ||||
|   } | ||||
|  | ||||
|   @Get('cities') | ||||
|   @Authenticated() | ||||
|   getAssetsByCity(@Auth() auth: AuthDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getAssetsByCity(auth); | ||||
|   } | ||||
|  | ||||
|   @Get('suggestions') | ||||
|   @Authenticated() | ||||
|   getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise<string[]> { | ||||
|     return this.service.getSearchSuggestions(auth, dto); | ||||
|   } | ||||
|   | ||||
| @@ -10,57 +10,51 @@ import { | ||||
|   ServerThemeDto, | ||||
|   ServerVersionResponseDto, | ||||
| } from 'src/dtos/server-info.dto'; | ||||
| import { AdminRoute, Authenticated, PublicRoute } from 'src/middleware/auth.guard'; | ||||
| import { Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { ServerInfoService } from 'src/services/server-info.service'; | ||||
|  | ||||
| @ApiTags('Server Info') | ||||
| @Controller('server-info') | ||||
| @Authenticated() | ||||
| export class ServerInfoController { | ||||
|   constructor(private service: ServerInfoService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getServerInfo(): Promise<ServerInfoResponseDto> { | ||||
|     return this.service.getInfo(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('ping') | ||||
|   pingServer(): ServerPingResponse { | ||||
|     return this.service.ping(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('version') | ||||
|   getServerVersion(): ServerVersionResponseDto { | ||||
|     return this.service.getVersion(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('features') | ||||
|   getServerFeatures(): Promise<ServerFeaturesDto> { | ||||
|     return this.service.getFeatures(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('theme') | ||||
|   getTheme(): Promise<ServerThemeDto> { | ||||
|     return this.service.getTheme(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('config') | ||||
|   getServerConfig(): Promise<ServerConfigDto> { | ||||
|     return this.service.getConfig(); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Authenticated({ admin: true }) | ||||
|   @Get('statistics') | ||||
|   getServerStatistics(): Promise<ServerStatsResponseDto> { | ||||
|     return this.service.getStatistics(); | ||||
|   } | ||||
|  | ||||
|   @PublicRoute() | ||||
|   @Get('media-types') | ||||
|   getSupportedMediaTypes(): ServerMediaTypesResponseDto { | ||||
|     return this.service.getSupportedMediaTypes(); | ||||
|   | ||||
| @@ -8,23 +8,25 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Sessions') | ||||
| @Controller('sessions') | ||||
| @Authenticated() | ||||
| export class SessionController { | ||||
|   constructor(private service: SessionService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getSessions(@Auth() auth: AuthDto): Promise<SessionResponseDto[]> { | ||||
|     return this.service.getAll(auth); | ||||
|   } | ||||
|  | ||||
|   @Delete() | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteAllSessions(@Auth() auth: AuthDto): Promise<void> { | ||||
|     return this.service.deleteAll(auth); | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteSession(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.delete(auth, id); | ||||
|   } | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import { | ||||
|   SharedLinkPasswordDto, | ||||
|   SharedLinkResponseDto, | ||||
| } from 'src/dtos/shared-link.dto'; | ||||
| import { Auth, Authenticated, GetLoginDetails, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard'; | ||||
| import { LoginDetails } from 'src/services/auth.service'; | ||||
| import { SharedLinkService } from 'src/services/shared-link.service'; | ||||
| import { respondWithCookie } from 'src/utils/response'; | ||||
| @@ -18,17 +18,17 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Shared Link') | ||||
| @Controller('shared-link') | ||||
| @Authenticated() | ||||
| export class SharedLinkController { | ||||
|   constructor(private service: SharedLinkService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getAllSharedLinks(@Auth() auth: AuthDto): Promise<SharedLinkResponseDto[]> { | ||||
|     return this.service.getAll(auth); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Get('me') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   async getMySharedLink( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Query() dto: SharedLinkPasswordDto, | ||||
| @@ -48,16 +48,19 @@ export class SharedLinkController { | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated() | ||||
|   getSharedLinkById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<SharedLinkResponseDto> { | ||||
|     return this.service.get(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createSharedLink(@Auth() auth: AuthDto, @Body() dto: SharedLinkCreateDto) { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Patch(':id') | ||||
|   @Authenticated() | ||||
|   updateSharedLink( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -67,12 +70,13 @@ export class SharedLinkController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @Authenticated() | ||||
|   removeSharedLink(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.remove(auth, id); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Put(':id/assets') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   addSharedLinkAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -81,8 +85,8 @@ export class SharedLinkController { | ||||
|     return this.service.addAssets(auth, id, dto); | ||||
|   } | ||||
|  | ||||
|   @SharedLinkRoute() | ||||
|   @Delete(':id/assets') | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   removeSharedLinkAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
|   | ||||
| @@ -8,18 +8,19 @@ import { SyncService } from 'src/services/sync.service'; | ||||
|  | ||||
| @ApiTags('Sync') | ||||
| @Controller('sync') | ||||
| @Authenticated() | ||||
| export class SyncController { | ||||
|   constructor(private service: SyncService) {} | ||||
|  | ||||
|   @Post('full-sync') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   getFullSyncForUser(@Auth() auth: AuthDto, @Body() dto: AssetFullSyncDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getFullSync(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Post('delta-sync') | ||||
|   @HttpCode(HttpStatus.OK) | ||||
|   @Authenticated() | ||||
|   getDeltaSync(@Auth() auth: AuthDto, @Body() dto: AssetDeltaSyncDto): Promise<AssetDeltaSyncResponseDto> { | ||||
|     return this.service.getDeltaSync(auth, dto); | ||||
|   } | ||||
|   | ||||
| @@ -1,37 +1,39 @@ | ||||
| import { Body, Controller, Get, Put, Query } from '@nestjs/common'; | ||||
| import { ApiTags } from '@nestjs/swagger'; | ||||
| import { MapThemeDto, SystemConfigDto, SystemConfigTemplateStorageOptionDto } from 'src/dtos/system-config.dto'; | ||||
| import { AdminRoute, Authenticated, SharedLinkRoute } from 'src/middleware/auth.guard'; | ||||
| import { Authenticated } from 'src/middleware/auth.guard'; | ||||
| import { SystemConfigService } from 'src/services/system-config.service'; | ||||
|  | ||||
| @ApiTags('System Config') | ||||
| @Controller('system-config') | ||||
| @Authenticated({ admin: true }) | ||||
| export class SystemConfigController { | ||||
|   constructor(private service: SystemConfigService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated({ admin: true }) | ||||
|   getConfig(): Promise<SystemConfigDto> { | ||||
|     return this.service.getConfig(); | ||||
|   } | ||||
|  | ||||
|   @Get('defaults') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getConfigDefaults(): SystemConfigDto { | ||||
|     return this.service.getDefaults(); | ||||
|   } | ||||
|  | ||||
|   @Put() | ||||
|   @Authenticated({ admin: true }) | ||||
|   updateConfig(@Body() dto: SystemConfigDto): Promise<SystemConfigDto> { | ||||
|     return this.service.updateConfig(dto); | ||||
|   } | ||||
|  | ||||
|   @Get('storage-template-options') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { | ||||
|     return this.service.getStorageTemplateOptions(); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute(false) | ||||
|   @SharedLinkRoute() | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   @Get('map/style.json') | ||||
|   getMapStyle(@Query() dto: MapThemeDto) { | ||||
|     return this.service.getMapStyle(dto.theme); | ||||
|   | ||||
| @@ -6,22 +6,24 @@ import { SystemMetadataService } from 'src/services/system-metadata.service'; | ||||
|  | ||||
| @ApiTags('System Metadata') | ||||
| @Controller('system-metadata') | ||||
| @Authenticated({ admin: true }) | ||||
| export class SystemMetadataController { | ||||
|   constructor(private service: SystemMetadataService) {} | ||||
|  | ||||
|   @Get('admin-onboarding') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getAdminOnboarding(): Promise<AdminOnboardingUpdateDto> { | ||||
|     return this.service.getAdminOnboarding(); | ||||
|   } | ||||
|  | ||||
|   @Post('admin-onboarding') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated({ admin: true }) | ||||
|   updateAdminOnboarding(@Body() dto: AdminOnboardingUpdateDto): Promise<void> { | ||||
|     return this.service.updateAdminOnboarding(dto); | ||||
|   } | ||||
|  | ||||
|   @Get('reverse-geocoding-state') | ||||
|   @Authenticated({ admin: true }) | ||||
|   getReverseGeocodingState(): Promise<ReverseGeocodingStateResponseDto> { | ||||
|     return this.service.getReverseGeocodingState(); | ||||
|   } | ||||
|   | ||||
| @@ -11,41 +11,47 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('Tag') | ||||
| @Controller('tag') | ||||
| @Authenticated() | ||||
| export class TagController { | ||||
|   constructor(private service: TagService) {} | ||||
|  | ||||
|   @Post() | ||||
|   @Authenticated() | ||||
|   createTag(@Auth() auth: AuthDto, @Body() dto: CreateTagDto): Promise<TagResponseDto> { | ||||
|     return this.service.create(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getAllTags(@Auth() auth: AuthDto): Promise<TagResponseDto[]> { | ||||
|     return this.service.getAll(auth); | ||||
|   } | ||||
|  | ||||
|   @Get(':id') | ||||
|   @Authenticated() | ||||
|   getTagById(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<TagResponseDto> { | ||||
|     return this.service.getById(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Patch(':id') | ||||
|   @Authenticated() | ||||
|   updateTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto, @Body() dto: UpdateTagDto): Promise<TagResponseDto> { | ||||
|     return this.service.update(auth, id, dto); | ||||
|   } | ||||
|  | ||||
|   @Delete(':id') | ||||
|   @Authenticated() | ||||
|   deleteTag(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> { | ||||
|     return this.service.remove(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Get(':id/assets') | ||||
|   @Authenticated() | ||||
|   getTagAssets(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getAssets(auth, id); | ||||
|   } | ||||
|  | ||||
|   @Put(':id/assets') | ||||
|   @Authenticated() | ||||
|   tagAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -55,6 +61,7 @@ export class TagController { | ||||
|   } | ||||
|  | ||||
|   @Delete(':id/assets') | ||||
|   @Authenticated() | ||||
|   untagAssets( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Body() dto: AssetIdsDto, | ||||
|   | ||||
| @@ -8,17 +8,16 @@ import { TimelineService } from 'src/services/timeline.service'; | ||||
|  | ||||
| @ApiTags('Timeline') | ||||
| @Controller('timeline') | ||||
| @Authenticated() | ||||
| export class TimelineController { | ||||
|   constructor(private service: TimelineService) {} | ||||
|  | ||||
|   @Authenticated({ isShared: true }) | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   @Get('buckets') | ||||
|   getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> { | ||||
|     return this.service.getTimeBuckets(auth, dto); | ||||
|   } | ||||
|  | ||||
|   @Authenticated({ isShared: true }) | ||||
|   @Authenticated({ sharedLink: true }) | ||||
|   @Get('bucket') | ||||
|   getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> { | ||||
|     return this.service.getTimeBucket(auth, dto) as Promise<AssetResponseDto[]>; | ||||
|   | ||||
| @@ -7,24 +7,26 @@ import { TrashService } from 'src/services/trash.service'; | ||||
|  | ||||
| @ApiTags('Trash') | ||||
| @Controller('trash') | ||||
| @Authenticated() | ||||
| export class TrashController { | ||||
|   constructor(private service: TrashService) {} | ||||
|  | ||||
|   @Post('empty') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   emptyTrash(@Auth() auth: AuthDto): Promise<void> { | ||||
|     return this.service.empty(auth); | ||||
|   } | ||||
|  | ||||
|   @Post('restore') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   restoreTrash(@Auth() auth: AuthDto): Promise<void> { | ||||
|     return this.service.restore(auth); | ||||
|   } | ||||
|  | ||||
|   @Post('restore/assets') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   restoreAssets(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> { | ||||
|     return this.service.restoreAssets(auth, dto); | ||||
|   } | ||||
|   | ||||
| @@ -19,7 +19,7 @@ import { NextFunction, Response } from 'express'; | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto'; | ||||
| import { CreateUserDto, DeleteUserDto, UpdateUserDto, UserResponseDto } from 'src/dtos/user.dto'; | ||||
| import { AdminRoute, Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; | ||||
| import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard'; | ||||
| import { FileUploadInterceptor, Route } from 'src/middleware/file-upload.interceptor'; | ||||
| import { UserService } from 'src/services/user.service'; | ||||
| import { sendFile } from 'src/utils/file'; | ||||
| @@ -27,39 +27,42 @@ import { UUIDParamDto } from 'src/validation'; | ||||
|  | ||||
| @ApiTags('User') | ||||
| @Controller(Route.USER) | ||||
| @Authenticated() | ||||
| export class UserController { | ||||
|   constructor(private service: UserService) {} | ||||
|  | ||||
|   @Get() | ||||
|   @Authenticated() | ||||
|   getAllUsers(@Auth() auth: AuthDto, @Query('isAll') isAll: boolean): Promise<UserResponseDto[]> { | ||||
|     return this.service.getAll(auth, isAll); | ||||
|   } | ||||
|  | ||||
|   @Get('info/:id') | ||||
|   @Authenticated() | ||||
|   getUserById(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> { | ||||
|     return this.service.get(id); | ||||
|   } | ||||
|  | ||||
|   @Get('me') | ||||
|   @Authenticated() | ||||
|   getMyUserInfo(@Auth() auth: AuthDto): Promise<UserResponseDto> { | ||||
|     return this.service.getMe(auth); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Post() | ||||
|   @Authenticated({ admin: true }) | ||||
|   createUser(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> { | ||||
|     return this.service.create(createUserDto); | ||||
|   } | ||||
|  | ||||
|   @Delete('profile-image') | ||||
|   @HttpCode(HttpStatus.NO_CONTENT) | ||||
|   @Authenticated() | ||||
|   deleteProfileImage(@Auth() auth: AuthDto): Promise<void> { | ||||
|     return this.service.deleteProfileImage(auth); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Delete(':id') | ||||
|   @Authenticated({ admin: true }) | ||||
|   deleteUser( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @Param() { id }: UUIDParamDto, | ||||
| @@ -68,14 +71,15 @@ export class UserController { | ||||
|     return this.service.delete(auth, id, dto); | ||||
|   } | ||||
|  | ||||
|   @AdminRoute() | ||||
|   @Post(':id/restore') | ||||
|   @Authenticated({ admin: true }) | ||||
|   restoreUser(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<UserResponseDto> { | ||||
|     return this.service.restore(auth, id); | ||||
|   } | ||||
|  | ||||
|   // TODO: replace with @Put(':id') | ||||
|   @Put() | ||||
|   @Authenticated() | ||||
|   updateUser(@Auth() auth: AuthDto, @Body() updateUserDto: UpdateUserDto): Promise<UserResponseDto> { | ||||
|     return this.service.update(auth, updateUserDto); | ||||
|   } | ||||
| @@ -84,6 +88,7 @@ export class UserController { | ||||
|   @ApiConsumes('multipart/form-data') | ||||
|   @ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto }) | ||||
|   @Post('profile-image') | ||||
|   @Authenticated() | ||||
|   createProfileImage( | ||||
|     @Auth() auth: AuthDto, | ||||
|     @UploadedFile() fileInfo: Express.Multer.File, | ||||
| @@ -93,6 +98,7 @@ export class UserController { | ||||
|  | ||||
|   @Get('profile-image/:id') | ||||
|   @FileResponse() | ||||
|   @Authenticated() | ||||
|   async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) { | ||||
|     await sendFile(res, next, () => this.service.getProfileImage(id)); | ||||
|   } | ||||
|   | ||||
| @@ -17,10 +17,14 @@ export enum ImmichHeader { | ||||
|   API_KEY = 'x-api-key', | ||||
|   USER_TOKEN = 'x-immich-user-token', | ||||
|   SESSION_TOKEN = 'x-immich-session-token', | ||||
|   SHARED_LINK_TOKEN = 'x-immich-share-key', | ||||
|   SHARED_LINK_KEY = 'x-immich-share-key', | ||||
|   CHECKSUM = 'x-immich-checksum', | ||||
| } | ||||
|  | ||||
| export enum ImmichQuery { | ||||
|   SHARED_LINK_KEY = 'key', | ||||
| } | ||||
|  | ||||
| export type CookieResponse = { | ||||
|   isSecure: boolean; | ||||
|   values: Array<{ key: ImmichCookie; value: string }>; | ||||
|   | ||||
| @@ -10,7 +10,7 @@ import { | ||||
| import { Reflector } from '@nestjs/core'; | ||||
| import { ApiBearerAuth, ApiCookieAuth, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger'; | ||||
| import { Request } from 'express'; | ||||
| import { AuthDto } from 'src/dtos/auth.dto'; | ||||
| import { AuthDto, ImmichQuery } from 'src/dtos/auth.dto'; | ||||
| import { ILoggerRepository } from 'src/interfaces/logger.interface'; | ||||
| import { AuthService, LoginDetails } from 'src/services/auth.service'; | ||||
| import { UAParser } from 'ua-parser-js'; | ||||
| @@ -19,42 +19,30 @@ export enum Metadata { | ||||
|   AUTH_ROUTE = 'auth_route', | ||||
|   ADMIN_ROUTE = 'admin_route', | ||||
|   SHARED_ROUTE = 'shared_route', | ||||
|   PUBLIC_SECURITY = 'public_security', | ||||
|   API_KEY_SECURITY = 'api_key', | ||||
| } | ||||
|  | ||||
| export interface AuthenticatedOptions { | ||||
|   admin?: true; | ||||
|   isShared?: true; | ||||
| } | ||||
| type AdminRoute = { admin?: true }; | ||||
| type SharedLinkRoute = { sharedLink?: true }; | ||||
| type AuthenticatedOptions = AdminRoute | SharedLinkRoute; | ||||
|  | ||||
| export const Authenticated = (options: AuthenticatedOptions = {}) => { | ||||
| export const Authenticated = (options?: AuthenticatedOptions): MethodDecorator => { | ||||
|   const decorators: MethodDecorator[] = [ | ||||
|     ApiBearerAuth(), | ||||
|     ApiCookieAuth(), | ||||
|     ApiSecurity(Metadata.API_KEY_SECURITY), | ||||
|     SetMetadata(Metadata.AUTH_ROUTE, true), | ||||
|     SetMetadata(Metadata.AUTH_ROUTE, options || {}), | ||||
|   ]; | ||||
|  | ||||
|   if (options.admin) { | ||||
|     decorators.push(AdminRoute()); | ||||
|   } | ||||
|  | ||||
|   if (options.isShared) { | ||||
|     decorators.push(SharedLinkRoute()); | ||||
|   if ((options as SharedLinkRoute)?.sharedLink) { | ||||
|     decorators.push(ApiQuery({ name: ImmichQuery.SHARED_LINK_KEY, type: String, required: false })); | ||||
|   } | ||||
|  | ||||
|   return applyDecorators(...decorators); | ||||
| }; | ||||
|  | ||||
| export const PublicRoute = () => | ||||
|   applyDecorators(SetMetadata(Metadata.AUTH_ROUTE, false), ApiSecurity(Metadata.PUBLIC_SECURITY)); | ||||
| export const SharedLinkRoute = () => | ||||
|   applyDecorators(SetMetadata(Metadata.SHARED_ROUTE, true), ApiQuery({ name: 'key', type: String, required: false })); | ||||
| export const AdminRoute = (value = true) => SetMetadata(Metadata.ADMIN_ROUTE, value); | ||||
|  | ||||
| export const Auth = createParamDecorator((data, context: ExecutionContext): AuthDto => { | ||||
|   return context.switchToHttp().getRequest<{ user: AuthDto }>().user; | ||||
|   return context.switchToHttp().getRequest<AuthenticatedRequest>().user; | ||||
| }); | ||||
|  | ||||
| export const FileResponse = () => | ||||
| @@ -93,25 +81,22 @@ export class AuthGuard implements CanActivate { | ||||
|   } | ||||
|  | ||||
|   async canActivate(context: ExecutionContext): Promise<boolean> { | ||||
|     const targets = [context.getHandler(), context.getClass()]; | ||||
|     const targets = [context.getHandler()]; | ||||
|  | ||||
|     const isAuthRoute = this.reflector.getAllAndOverride(Metadata.AUTH_ROUTE, targets); | ||||
|     const isAdminRoute = this.reflector.getAllAndOverride(Metadata.ADMIN_ROUTE, targets); | ||||
|     const isSharedRoute = this.reflector.getAllAndOverride(Metadata.SHARED_ROUTE, targets); | ||||
|  | ||||
|     if (!isAuthRoute) { | ||||
|     const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(Metadata.AUTH_ROUTE, targets); | ||||
|     if (!options) { | ||||
|       return true; | ||||
|     } | ||||
|  | ||||
|     const request = context.switchToHttp().getRequest<AuthRequest>(); | ||||
|  | ||||
|     const authDto = await this.authService.validate(request.headers, request.query as Record<string, string>); | ||||
|     if (authDto.sharedLink && !isSharedRoute) { | ||||
|     if (authDto.sharedLink && !(options as SharedLinkRoute).sharedLink) { | ||||
|       this.logger.warn(`Denied access to non-shared route: ${request.path}`); | ||||
|       return false; | ||||
|     } | ||||
|  | ||||
|     if (isAdminRoute && !authDto.user.isAdmin) { | ||||
|     if (!authDto.user.isAdmin && (options as AdminRoute).admin) { | ||||
|       this.logger.warn(`Denied access to admin only route: ${request.path}`); | ||||
|       return false; | ||||
|     } | ||||
|   | ||||
| @@ -149,7 +149,7 @@ export class AuthService { | ||||
|   } | ||||
|  | ||||
|   async validate(headers: IncomingHttpHeaders, params: Record<string, string>): Promise<AuthDto> { | ||||
|     const shareKey = (headers[ImmichHeader.SHARED_LINK_TOKEN] || params.key) as string; | ||||
|     const shareKey = (headers[ImmichHeader.SHARED_LINK_KEY] || params.key) as string; | ||||
|     const session = (headers[ImmichHeader.USER_TOKEN] || | ||||
|       headers[ImmichHeader.SESSION_TOKEN] || | ||||
|       params.sessionKey || | ||||
|   | ||||
| @@ -103,10 +103,6 @@ const patchOpenAPI = (document: OpenAPIObject) => { | ||||
|         continue; | ||||
|       } | ||||
|  | ||||
|       if ((operation.security || []).some((item) => !!item[Metadata.PUBLIC_SECURITY])) { | ||||
|         delete operation.security; | ||||
|       } | ||||
|  | ||||
|       if (operation.summary === '') { | ||||
|         delete operation.summary; | ||||
|       } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user