feat!: more permissions (#20250)

feat: more api key permissions
This commit is contained in:
Jason Rasmussen 2025-07-25 15:25:23 -04:00 committed by GitHub
parent 153bb70f6e
commit 0fdeac0417
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 414 additions and 120 deletions

View file

@ -67,7 +67,7 @@ export class AlbumController {
}
@Put(':id/assets')
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AlbumAssetCreate, sharedLink: true })
addAssetsToAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -77,7 +77,7 @@ export class AlbumController {
}
@Delete(':id/assets')
@Authenticated()
@Authenticated({ permission: Permission.AlbumAssetDelete })
removeAssetFromAlbum(
@Auth() auth: AuthDto,
@Body() dto: BulkIdsDto,
@ -87,7 +87,7 @@ export class AlbumController {
}
@Put(':id/users')
@Authenticated()
@Authenticated({ permission: Permission.AlbumUserCreate })
addUsersToAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -97,7 +97,7 @@ export class AlbumController {
}
@Put(':id/user/:userId')
@Authenticated()
@Authenticated({ permission: Permission.AlbumUserUpdate })
updateAlbumUser(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -108,7 +108,7 @@ export class AlbumController {
}
@Delete(':id/user/:userId')
@Authenticated()
@Authenticated({ permission: Permission.AlbumUserDelete })
removeUserFromAlbum(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,

View file

@ -34,7 +34,7 @@ import {
UploadFieldName,
} from 'src/dtos/asset-media.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { ImmichHeader, RouteKey } from 'src/enum';
import { ImmichHeader, Permission, RouteKey } from 'src/enum';
import { AssetUploadInterceptor } from 'src/middleware/asset-upload.interceptor';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor, getFiles } from 'src/middleware/file-upload.interceptor';
@ -61,7 +61,7 @@ export class AssetMediaController {
required: false,
})
@ApiBody({ description: 'Asset Upload Information', type: AssetMediaCreateDto })
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetUpload, sharedLink: true })
async uploadAsset(
@Auth() auth: AuthDto,
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] })) files: UploadFiles,
@ -80,7 +80,7 @@ export class AssetMediaController {
@Get(':id/original')
@FileResponse()
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetDownload, sharedLink: true })
async downloadAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -101,7 +101,7 @@ export class AssetMediaController {
summary: 'replaceAsset',
description: 'Replace the asset with new file, without changing its id',
})
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetReplace, sharedLink: true })
async replaceAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -120,7 +120,7 @@ export class AssetMediaController {
@Get(':id/thumbnail')
@FileResponse()
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetView, sharedLink: true })
async viewAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -157,7 +157,7 @@ export class AssetMediaController {
@Get(':id/video/playback')
@FileResponse()
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetView, sharedLink: true })
async playAssetVideo(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,

View file

@ -13,7 +13,7 @@ import {
UpdateAssetDto,
} from 'src/dtos/asset.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { RouteKey } from 'src/enum';
import { Permission, RouteKey } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { AssetService } from 'src/services/asset.service';
import { UUIDParamDto } from 'src/validation';
@ -24,7 +24,7 @@ export class AssetController {
constructor(private service: AssetService) {}
@Get('random')
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
@EndpointLifecycle({ deprecatedAt: 'v1.116.0' })
getRandom(@Auth() auth: AuthDto, @Query() dto: RandomAssetsDto): Promise<AssetResponseDto[]> {
return this.service.getRandom(auth, dto.count ?? 1);
@ -44,7 +44,7 @@ export class AssetController {
}
@Get('statistics')
@Authenticated()
@Authenticated({ permission: Permission.AssetStatistics })
getAssetStatistics(@Auth() auth: AuthDto, @Query() dto: AssetStatsDto): Promise<AssetStatsResponseDto> {
return this.service.getStatistics(auth, dto);
}
@ -58,26 +58,26 @@ export class AssetController {
@Put()
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated()
@Authenticated({ permission: Permission.AssetUpdate })
updateAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkUpdateDto): Promise<void> {
return this.service.updateAll(auth, dto);
}
@Delete()
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated()
@Authenticated({ permission: Permission.AssetDelete })
deleteAssets(@Auth() auth: AuthDto, @Body() dto: AssetBulkDeleteDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Get(':id')
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetRead, sharedLink: true })
getAssetInfo(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<AssetResponseDto> {
return this.service.get(auth, id) as Promise<AssetResponseDto>;
}
@Put(':id')
@Authenticated()
@Authenticated({ permission: Permission.AssetUpdate })
updateAsset(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,

View file

@ -16,7 +16,7 @@ import {
ValidateAccessTokenResponseDto,
} from 'src/dtos/auth.dto';
import { UserAdminResponseDto } from 'src/dtos/user.dto';
import { AuthType, ImmichCookie } from 'src/enum';
import { AuthType, ImmichCookie, Permission } from 'src/enum';
import { Auth, Authenticated, GetLoginDetails } from 'src/middleware/auth.guard';
import { AuthService, LoginDetails } from 'src/services/auth.service';
import { respondWithCookie, respondWithoutCookie } from 'src/utils/response';
@ -57,7 +57,7 @@ export class AuthController {
@Post('change-password')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.AuthChangePassword })
changePassword(@Auth() auth: AuthDto, @Body() dto: ChangePasswordDto): Promise<UserAdminResponseDto> {
return this.service.changePassword(auth, dto);
}
@ -87,19 +87,19 @@ export class AuthController {
}
@Post('pin-code')
@Authenticated()
@Authenticated({ permission: Permission.PinCodeCreate })
setupPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeSetupDto): Promise<void> {
return this.service.setupPinCode(auth, dto);
}
@Put('pin-code')
@Authenticated()
@Authenticated({ permission: Permission.PinCodeUpdate })
async changePinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeChangeDto): Promise<void> {
return this.service.changePinCode(auth, dto);
}
@Delete('pin-code')
@Authenticated()
@Authenticated({ permission: Permission.PinCodeDelete })
async resetPinCode(@Auth() auth: AuthDto, @Body() dto: PinCodeResetDto): Promise<void> {
return this.service.resetPinCode(auth, dto);
}

View file

@ -3,6 +3,7 @@ import { ApiTags } from '@nestjs/swagger';
import { AssetIdsDto } from 'src/dtos/asset.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { DownloadInfoDto, DownloadResponseDto } from 'src/dtos/download.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { DownloadService } from 'src/services/download.service';
import { asStreamableFile } from 'src/utils/file';
@ -13,7 +14,7 @@ export class DownloadController {
constructor(private service: DownloadService) {}
@Post('info')
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetDownload, sharedLink: true })
getDownloadInfo(@Auth() auth: AuthDto, @Body() dto: DownloadInfoDto): Promise<DownloadResponseDto> {
return this.service.getDownloadInfo(auth, dto);
}
@ -21,7 +22,7 @@ export class DownloadController {
@Post('archive')
@HttpCode(HttpStatus.OK)
@FileResponse()
@Authenticated({ sharedLink: true })
@Authenticated({ permission: Permission.AssetDownload, sharedLink: true })
downloadArchive(@Auth() auth: AuthDto, @Body() dto: AssetIdsDto): Promise<StreamableFile> {
return this.service.downloadArchive(auth, dto).then(asStreamableFile);
}

View file

@ -3,6 +3,7 @@ import { ApiTags } from '@nestjs/swagger';
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AuthDto } from 'src/dtos/auth.dto';
import { DuplicateResponseDto } from 'src/dtos/duplicate.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { DuplicateService } from 'src/services/duplicate.service';
import { UUIDParamDto } from 'src/validation';
@ -13,19 +14,19 @@ export class DuplicateController {
constructor(private service: DuplicateService) {}
@Get()
@Authenticated()
@Authenticated({ permission: Permission.DuplicateRead })
getAssetDuplicates(@Auth() auth: AuthDto): Promise<DuplicateResponseDto[]> {
return this.service.getDuplicates(auth);
}
@Delete()
@Authenticated()
@Authenticated({ permission: Permission.DuplicateDelete })
deleteDuplicates(@Auth() auth: AuthDto, @Body() dto: BulkIdsDto): Promise<void> {
return this.service.deleteAll(auth, dto);
}
@Delete(':id')
@Authenticated()
@Authenticated({ permission: Permission.DuplicateDelete })
deleteDuplicate(@Auth() auth: AuthDto, @Param() { id }: UUIDParamDto): Promise<void> {
return this.service.delete(auth, id);
}

View file

@ -1,6 +1,7 @@
import { Body, Controller, Get, Param, Post, Put } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { AllJobStatusResponseDto, JobCommandDto, JobCreateDto, JobIdParamDto, JobStatusDto } from 'src/dtos/job.dto';
import { Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { JobService } from 'src/services/job.service';
@ -10,19 +11,19 @@ export class JobController {
constructor(private service: JobService) {}
@Get()
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.JobRead, admin: true })
getAllJobsStatus(): Promise<AllJobStatusResponseDto> {
return this.service.getAllJobsStatus();
}
@Post()
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.JobCreate, admin: true })
createJob(@Body() dto: JobCreateDto): Promise<void> {
return this.service.create(dto);
}
@Put(':id')
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.JobCreate, admin: true })
sendJobCommand(@Param() { id }: JobIdParamDto, @Body() dto: JobCommandDto): Promise<JobStatusDto> {
return this.service.handleCommand(id, dto);
}

View file

@ -32,7 +32,7 @@ export class MemoryController {
}
@Get('statistics')
@Authenticated({ permission: Permission.MemoryRead })
@Authenticated({ permission: Permission.MemoryStatistics })
memoriesStatistics(@Auth() auth: AuthDto, @Query() dto: MemorySearchDto): Promise<MemoryStatisticsResponseDto> {
return this.service.statistics(auth, dto);
}
@ -61,7 +61,7 @@ export class MemoryController {
}
@Put(':id/assets')
@Authenticated()
@Authenticated({ permission: Permission.MemoryAssetCreate })
addMemoryAssets(
@Auth() auth: AuthDto,
@Param() { id }: UUIDParamDto,
@ -72,7 +72,7 @@ export class MemoryController {
@Delete(':id/assets')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.MemoryAssetDelete })
removeMemoryAssets(
@Auth() auth: AuthDto,
@Body() dto: BulkIdsDto,

View file

@ -16,6 +16,7 @@ import {
SmartSearchDto,
StatisticsSearchDto,
} from 'src/dtos/search.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { SearchService } from 'src/services/search.service';
@ -26,58 +27,58 @@ export class SearchController {
@Post('metadata')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
searchAssets(@Auth() auth: AuthDto, @Body() dto: MetadataSearchDto): Promise<SearchResponseDto> {
return this.service.searchMetadata(auth, dto);
}
@Post('statistics')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.AssetStatistics })
searchAssetStatistics(@Auth() auth: AuthDto, @Body() dto: StatisticsSearchDto): Promise<SearchStatisticsResponseDto> {
return this.service.searchStatistics(auth, dto);
}
@Post('random')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
searchRandom(@Auth() auth: AuthDto, @Body() dto: RandomSearchDto): Promise<AssetResponseDto[]> {
return this.service.searchRandom(auth, dto);
}
@Post('smart')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
searchSmart(@Auth() auth: AuthDto, @Body() dto: SmartSearchDto): Promise<SearchResponseDto> {
return this.service.searchSmart(auth, dto);
}
@Get('explore')
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
getExploreData(@Auth() auth: AuthDto): Promise<SearchExploreResponseDto[]> {
return this.service.getExploreData(auth);
}
@Get('person')
@Authenticated()
@Authenticated({ permission: Permission.PersonRead })
searchPerson(@Auth() auth: AuthDto, @Query() dto: SearchPeopleDto): Promise<PersonResponseDto[]> {
return this.service.searchPerson(auth, dto);
}
@Get('places')
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
searchPlaces(@Query() dto: SearchPlacesDto): Promise<PlacesResponseDto[]> {
return this.service.searchPlaces(dto);
}
@Get('cities')
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
getAssetsByCity(@Auth() auth: AuthDto): Promise<AssetResponseDto[]> {
return this.service.getAssetsByCity(auth);
}
@Get('suggestions')
@Authenticated()
@Authenticated({ permission: Permission.AssetRead })
getSearchSuggestions(@Auth() auth: AuthDto, @Query() dto: SearchSuggestionRequestDto): Promise<string[]> {
// TODO fix open api generation to indicate that results can be nullable
return this.service.getSearchSuggestions(auth, dto) as Promise<string[]>;

View file

@ -15,6 +15,7 @@ import {
ServerVersionResponseDto,
} from 'src/dtos/server.dto';
import { VersionCheckStateResponseDto } from 'src/dtos/system-metadata.dto';
import { Permission } from 'src/enum';
import { Authenticated } from 'src/middleware/auth.guard';
import { ServerService } from 'src/services/server.service';
import { SystemMetadataService } from 'src/services/system-metadata.service';
@ -30,19 +31,19 @@ export class ServerController {
) {}
@Get('about')
@Authenticated()
@Authenticated({ permission: Permission.ServerAbout })
getAboutInfo(): Promise<ServerAboutResponseDto> {
return this.service.getAboutInfo();
}
@Get('apk-links')
@Authenticated()
@Authenticated({ permission: Permission.ServerApkLinks })
getApkLinks(): ServerApkLinksDto {
return this.service.getApkLinks();
}
@Get('storage')
@Authenticated()
@Authenticated({ permission: Permission.ServerStorage })
getStorage(): Promise<ServerStorageResponseDto> {
return this.service.getStorage();
}
@ -78,7 +79,7 @@ export class ServerController {
}
@Get('statistics')
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.ServerStatistics, admin: true })
getServerStatistics(): Promise<ServerStatsResponseDto> {
return this.service.getStatistics();
}
@ -88,25 +89,25 @@ export class ServerController {
return this.service.getSupportedMediaTypes();
}
@Get('license')
@Authenticated({ permission: Permission.ServerLicenseRead, admin: true })
@ApiNotFoundResponse()
getServerLicense(): Promise<LicenseResponseDto> {
return this.service.getLicense();
}
@Put('license')
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.ServerLicenseUpdate, admin: true })
setServerLicense(@Body() license: LicenseKeyDto): Promise<LicenseResponseDto> {
return this.service.setLicense(license);
}
@Delete('license')
@Authenticated({ admin: true })
@Authenticated({ permission: Permission.ServerLicenseDelete, admin: true })
deleteServerLicense(): Promise<void> {
return this.service.deleteLicense();
}
@Get('license')
@Authenticated({ admin: true })
@ApiNotFoundResponse()
getServerLicense(): Promise<LicenseResponseDto> {
return this.service.getLicense();
}
@Get('version-check')
@Authenticated()
getVersionCheck(): Promise<VersionCheckStateResponseDto> {

View file

@ -12,6 +12,7 @@ import {
SyncAckSetDto,
SyncStreamDto,
} from 'src/dtos/sync.dto';
import { Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
import { SyncService } from 'src/services/sync.service';
@ -41,7 +42,7 @@ export class SyncController {
@Post('stream')
@Header('Content-Type', 'application/jsonlines+json')
@HttpCode(HttpStatus.OK)
@Authenticated()
@Authenticated({ permission: Permission.SyncStream })
async getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) {
try {
await this.service.stream(auth, res, dto);
@ -52,21 +53,21 @@ export class SyncController {
}
@Get('ack')
@Authenticated()
@Authenticated({ permission: Permission.SyncCheckpointRead })
getSyncAck(@Auth() auth: AuthDto): Promise<SyncAckDto[]> {
return this.service.getAcks(auth);
}
@Post('ack')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated()
@Authenticated({ permission: Permission.SyncCheckpointUpdate })
sendSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckSetDto) {
return this.service.setAcks(auth, dto);
}
@Delete('ack')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated()
@Authenticated({ permission: Permission.SyncCheckpointDelete })
deleteSyncAck(@Auth() auth: AuthDto, @Body() dto: SyncAckDeleteDto) {
return this.service.deleteAcks(auth, dto);
}

View file

@ -21,7 +21,7 @@ import { OnboardingDto, OnboardingResponseDto } from 'src/dtos/onboarding.dto';
import { UserPreferencesResponseDto, UserPreferencesUpdateDto } from 'src/dtos/user-preferences.dto';
import { CreateProfileImageDto, CreateProfileImageResponseDto } from 'src/dtos/user-profile.dto';
import { UserAdminResponseDto, UserResponseDto, UserUpdateMeDto } from 'src/dtos/user.dto';
import { RouteKey } from 'src/enum';
import { Permission, RouteKey } from 'src/enum';
import { Auth, Authenticated, FileResponse } from 'src/middleware/auth.guard';
import { FileUploadInterceptor } from 'src/middleware/file-upload.interceptor';
import { LoggingRepository } from 'src/repositories/logging.repository';
@ -38,31 +38,31 @@ export class UserController {
) {}
@Get()
@Authenticated()
@Authenticated({ permission: Permission.UserRead })
searchUsers(@Auth() auth: AuthDto): Promise<UserResponseDto[]> {
return this.service.search(auth);
}
@Get('me')
@Authenticated()
@Authenticated({ permission: Permission.UserRead })
getMyUser(@Auth() auth: AuthDto): Promise<UserAdminResponseDto> {
return this.service.getMe(auth);
}
@Put('me')
@Authenticated()
@Authenticated({ permission: Permission.UserUpdate })
updateMyUser(@Auth() auth: AuthDto, @Body() dto: UserUpdateMeDto): Promise<UserAdminResponseDto> {
return this.service.updateMe(auth, dto);
}
@Get('me/preferences')
@Authenticated()
@Authenticated({ permission: Permission.UserPreferenceRead })
getMyPreferences(@Auth() auth: AuthDto): Promise<UserPreferencesResponseDto> {
return this.service.getMyPreferences(auth);
}
@Put('me/preferences')
@Authenticated()
@Authenticated({ permission: Permission.UserPreferenceUpdate })
updateMyPreferences(
@Auth() auth: AuthDto,
@Body() dto: UserPreferencesUpdateDto,
@ -71,43 +71,43 @@ export class UserController {
}
@Get('me/license')
@Authenticated()
@Authenticated({ permission: Permission.UserLicenseRead })
getUserLicense(@Auth() auth: AuthDto): Promise<LicenseResponseDto> {
return this.service.getLicense(auth);
}
@Put('me/license')
@Authenticated()
@Authenticated({ permission: Permission.UserLicenseUpdate })
async setUserLicense(@Auth() auth: AuthDto, @Body() license: LicenseKeyDto): Promise<LicenseResponseDto> {
return this.service.setLicense(auth, license);
}
@Delete('me/license')
@Authenticated()
@Authenticated({ permission: Permission.UserLicenseDelete })
async deleteUserLicense(@Auth() auth: AuthDto): Promise<void> {
await this.service.deleteLicense(auth);
}
@Get('me/onboarding')
@Authenticated()
@Authenticated({ permission: Permission.UserOnboardingRead })
getUserOnboarding(@Auth() auth: AuthDto): Promise<OnboardingResponseDto> {
return this.service.getOnboarding(auth);
}
@Put('me/onboarding')
@Authenticated()
@Authenticated({ permission: Permission.UserOnboardingUpdate })
async setUserOnboarding(@Auth() auth: AuthDto, @Body() Onboarding: OnboardingDto): Promise<OnboardingResponseDto> {
return this.service.setOnboarding(auth, Onboarding);
}
@Delete('me/onboarding')
@Authenticated()
@Authenticated({ permission: Permission.UserOnboardingDelete })
async deleteUserOnboarding(@Auth() auth: AuthDto): Promise<void> {
await this.service.deleteOnboarding(auth);
}
@Get(':id')
@Authenticated()
@Authenticated({ permission: Permission.UserRead })
getUser(@Param() { id }: UUIDParamDto): Promise<UserResponseDto> {
return this.service.get(id);
}
@ -116,7 +116,7 @@ export class UserController {
@ApiConsumes('multipart/form-data')
@ApiBody({ description: 'A new avatar for the user', type: CreateProfileImageDto })
@Post('profile-image')
@Authenticated()
@Authenticated({ permission: Permission.UserProfileImageUpdate })
createProfileImage(
@Auth() auth: AuthDto,
@UploadedFile() fileInfo: Express.Multer.File,
@ -126,14 +126,14 @@ export class UserController {
@Delete('profile-image')
@HttpCode(HttpStatus.NO_CONTENT)
@Authenticated()
@Authenticated({ permission: Permission.UserProfileImageDelete })
deleteProfileImage(@Auth() auth: AuthDto): Promise<void> {
return this.service.deleteProfileImage(auth);
}
@Get(':id/profile-image')
@FileResponse()
@Authenticated()
@Authenticated({ permission: Permission.UserProfileImageRead })
async getProfileImage(@Res() res: Response, @Next() next: NextFunction, @Param() { id }: UUIDParamDto) {
await sendFile(res, next, () => this.service.getProfileImage(id), this.logger);
}