diff --git a/server/apps/immich/src/controllers/auth.controller.ts b/server/apps/immich/src/controllers/auth.controller.ts index d244807671..7a6389242e 100644 --- a/server/apps/immich/src/controllers/auth.controller.ts +++ b/server/apps/immich/src/controllers/auth.controller.ts @@ -22,7 +22,7 @@ import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('Authentication') @Controller('auth') export class AuthController { - constructor(private readonly authService: AuthService) {} + constructor(private readonly service: AuthService) {} @Post('login') async login( @@ -31,8 +31,8 @@ export class AuthController { @Req() req: Request, @Res({ passthrough: true }) res: Response, ): Promise { - const { response, cookie } = await this.authService.login(loginCredential, clientIp, req.secure); - res.setHeader('Set-Cookie', cookie); + const { response, cookie } = await this.service.login(loginCredential, clientIp, req.secure); + res.header('Set-Cookie', cookie); return response; } @@ -41,25 +41,24 @@ export class AuthController { adminSignUp( @Body(new ValidationPipe({ transform: true })) signUpCredential: SignUpDto, ): Promise { - return this.authService.adminSignUp(signUpCredential); + return this.service.adminSignUp(signUpCredential); } @Authenticated() @Post('validateToken') - // eslint-disable-next-line @typescript-eslint/no-unused-vars - validateAccessToken(@GetAuthUser() authUser: AuthUserDto): ValidateAccessTokenResponseDto { + validateAccessToken(): ValidateAccessTokenResponseDto { return { authStatus: true }; } @Authenticated() @Post('change-password') - async changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise { - return this.authService.changePassword(authUser, dto); + changePassword(@GetAuthUser() authUser: AuthUserDto, @Body() dto: ChangePasswordDto): Promise { + return this.service.changePassword(authUser, dto); } @Authenticated() @Post('logout') - async logout( + logout( @Req() req: Request, @Res({ passthrough: true }) res: Response, @GetAuthUser() authUser: AuthUserDto, @@ -69,6 +68,6 @@ export class AuthController { res.clearCookie(IMMICH_ACCESS_COOKIE); res.clearCookie(IMMICH_AUTH_TYPE_COOKIE); - return this.authService.logout(authUser, authType); + return this.service.logout(authUser, authType); } } diff --git a/server/apps/immich/src/controllers/device-info.controller.ts b/server/apps/immich/src/controllers/device-info.controller.ts index f651edd2b5..10feea3c2e 100644 --- a/server/apps/immich/src/controllers/device-info.controller.ts +++ b/server/apps/immich/src/controllers/device-info.controller.ts @@ -9,9 +9,9 @@ import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; -@Authenticated() @ApiTags('Device Info') @Controller('device-info') +@Authenticated() export class DeviceInfoController { constructor(private readonly service: DeviceInfoService) {} diff --git a/server/apps/immich/src/controllers/job.controller.ts b/server/apps/immich/src/controllers/job.controller.ts index 942234c9cf..b756d6570c 100644 --- a/server/apps/immich/src/controllers/job.controller.ts +++ b/server/apps/immich/src/controllers/job.controller.ts @@ -3,19 +3,19 @@ import { Body, Controller, Get, Param, Put, ValidationPipe } from '@nestjs/commo import { ApiTags } from '@nestjs/swagger'; import { Authenticated } from '../decorators/authenticated.decorator'; -@Authenticated({ admin: true }) @ApiTags('Job') @Controller('jobs') +@Authenticated({ admin: true }) export class JobController { - constructor(private readonly jobService: JobService) {} + constructor(private service: JobService) {} @Get() getAllJobsStatus(): Promise { - return this.jobService.getAllJobsStatus(); + return this.service.getAllJobsStatus(); } @Put('/:jobId') sendJobCommand(@Param(ValidationPipe) { jobId }: JobIdDto, @Body(ValidationPipe) dto: JobCommandDto): Promise { - return this.jobService.handleCommand(jobId, dto); + return this.service.handleCommand(jobId, dto); } } diff --git a/server/apps/immich/src/controllers/oauth.controller.ts b/server/apps/immich/src/controllers/oauth.controller.ts index b480e7eb23..a88e8ce0f8 100644 --- a/server/apps/immich/src/controllers/oauth.controller.ts +++ b/server/apps/immich/src/controllers/oauth.controller.ts @@ -1,7 +1,6 @@ import { AuthUserDto, LoginResponseDto, - MOBILE_REDIRECT, OAuthCallbackDto, OAuthConfigDto, OAuthConfigResponseDto, @@ -17,43 +16,42 @@ import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('OAuth') @Controller('oauth') export class OAuthController { - constructor(private readonly oauthService: OAuthService) {} + constructor(private service: OAuthService) {} @Get('mobile-redirect') @Redirect() - public mobileRedirect(@Req() req: Request) { - const url = `${MOBILE_REDIRECT}?${req.url.split('?')[1] || ''}`; - return { url, statusCode: HttpStatus.TEMPORARY_REDIRECT }; + mobileRedirect(@Req() req: Request) { + return { + url: this.service.getMobileRedirect(req.url), + statusCode: HttpStatus.TEMPORARY_REDIRECT, + }; } @Post('config') - public generateConfig(@Body(ValidationPipe) dto: OAuthConfigDto): Promise { - return this.oauthService.generateConfig(dto); + generateConfig(@Body(ValidationPipe) dto: OAuthConfigDto): Promise { + return this.service.generateConfig(dto); } @Post('callback') - public async callback( + async callback( @Res({ passthrough: true }) res: Response, @Body(ValidationPipe) dto: OAuthCallbackDto, @Req() req: Request, ): Promise { - const { response, cookie } = await this.oauthService.login(dto, req.secure); - res.setHeader('Set-Cookie', cookie); + const { response, cookie } = await this.service.login(dto, req.secure); + res.header('Set-Cookie', cookie); return response; } @Authenticated() @Post('link') - public async link( - @GetAuthUser() authUser: AuthUserDto, - @Body(ValidationPipe) dto: OAuthCallbackDto, - ): Promise { - return this.oauthService.link(authUser, dto); + link(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) dto: OAuthCallbackDto): Promise { + return this.service.link(authUser, dto); } @Authenticated() @Post('unlink') - public async unlink(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.oauthService.unlink(authUser); + unlink(@GetAuthUser() authUser: AuthUserDto): Promise { + return this.service.unlink(authUser); } } diff --git a/server/apps/immich/src/controllers/search.controller.ts b/server/apps/immich/src/controllers/search.controller.ts index 2c2248c3fc..430f37941c 100644 --- a/server/apps/immich/src/controllers/search.controller.ts +++ b/server/apps/immich/src/controllers/search.controller.ts @@ -12,26 +12,26 @@ import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('Search') -@Authenticated() @Controller('search') +@Authenticated() export class SearchController { - constructor(private readonly searchService: SearchService) {} + constructor(private service: SearchService) {} @Get() - async search( + search( @GetAuthUser() authUser: AuthUserDto, @Query(new ValidationPipe({ transform: true })) dto: SearchDto, ): Promise { - return this.searchService.search(authUser, dto); + return this.service.search(authUser, dto); } @Get('config') getSearchConfig(): SearchConfigResponseDto { - return this.searchService.getConfig(); + return this.service.getConfig(); } @Get('explore') getExploreData(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.searchService.getExploreData(authUser) as Promise; + return this.service.getExploreData(authUser) as Promise; } } diff --git a/server/apps/immich/src/controllers/server-info.controller.ts b/server/apps/immich/src/controllers/server-info.controller.ts index db66da1e46..370673651f 100644 --- a/server/apps/immich/src/controllers/server-info.controller.ts +++ b/server/apps/immich/src/controllers/server-info.controller.ts @@ -12,7 +12,7 @@ import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('Server Info') @Controller('server-info') export class ServerInfoController { - constructor(private readonly service: ServerInfoService) {} + constructor(private service: ServerInfoService) {} @Get() getServerInfo(): Promise { diff --git a/server/apps/immich/src/controllers/share.controller.ts b/server/apps/immich/src/controllers/share.controller.ts index 344a1d6826..17124b16fc 100644 --- a/server/apps/immich/src/controllers/share.controller.ts +++ b/server/apps/immich/src/controllers/share.controller.ts @@ -1,35 +1,36 @@ +import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain'; import { Body, Controller, Delete, Get, Param, Patch, ValidationPipe } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; import { GetAuthUser } from '../decorators/auth-user.decorator'; import { Authenticated } from '../decorators/authenticated.decorator'; -import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain'; @ApiTags('share') @Controller('share') export class ShareController { - constructor(private readonly shareService: ShareService) {} + constructor(private readonly service: ShareService) {} + @Authenticated() @Get() getAllSharedLinks(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.shareService.getAll(authUser); + return this.service.getAll(authUser); } @Authenticated({ isShared: true }) @Get('me') getMySharedLink(@GetAuthUser() authUser: AuthUserDto): Promise { - return this.shareService.getMine(authUser); + return this.service.getMine(authUser); } @Authenticated() @Get(':id') getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { - return this.shareService.getById(authUser, id, true); + return this.service.getById(authUser, id, true); } @Authenticated() @Delete(':id') removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise { - return this.shareService.remove(authUser, id); + return this.service.remove(authUser, id); } @Authenticated() @@ -37,8 +38,8 @@ export class ShareController { editSharedLink( @GetAuthUser() authUser: AuthUserDto, @Param('id') id: string, - @Body(new ValidationPipe()) dto: EditSharedLinkDto, + @Body(ValidationPipe) dto: EditSharedLinkDto, ): Promise { - return this.shareService.edit(authUser, id, dto); + return this.service.edit(authUser, id, dto); } } diff --git a/server/apps/immich/src/controllers/system-config.controller.ts b/server/apps/immich/src/controllers/system-config.controller.ts index 924a40a09f..b37587500b 100644 --- a/server/apps/immich/src/controllers/system-config.controller.ts +++ b/server/apps/immich/src/controllers/system-config.controller.ts @@ -4,28 +4,28 @@ import { ApiTags } from '@nestjs/swagger'; import { Authenticated } from '../decorators/authenticated.decorator'; @ApiTags('System Config') -@Authenticated({ admin: true }) @Controller('system-config') +@Authenticated({ admin: true }) export class SystemConfigController { - constructor(private readonly systemConfigService: SystemConfigService) {} + constructor(private readonly service: SystemConfigService) {} @Get() - public getConfig(): Promise { - return this.systemConfigService.getConfig(); + getConfig(): Promise { + return this.service.getConfig(); } @Get('defaults') - public getDefaults(): SystemConfigDto { - return this.systemConfigService.getDefaults(); + getDefaults(): SystemConfigDto { + return this.service.getDefaults(); } @Put() - public updateConfig(@Body(ValidationPipe) dto: SystemConfigDto): Promise { - return this.systemConfigService.updateConfig(dto); + updateConfig(@Body(ValidationPipe) dto: SystemConfigDto): Promise { + return this.service.updateConfig(dto); } @Get('storage-template-options') - public getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { - return this.systemConfigService.getStorageTemplateOptions(); + getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto { + return this.service.getStorageTemplateOptions(); } } diff --git a/server/apps/immich/src/controllers/user.controller.ts b/server/apps/immich/src/controllers/user.controller.ts index 99817fe3b0..e18fc8dbb4 100644 --- a/server/apps/immich/src/controllers/user.controller.ts +++ b/server/apps/immich/src/controllers/user.controller.ts @@ -33,60 +33,58 @@ import { UserCountDto } from '@app/domain'; @ApiTags('User') @Controller('user') export class UserController { - constructor(private readonly userService: UserService) {} + constructor(private service: UserService) {} @Authenticated() @Get() - async getAllUsers( + getAllUsers( @GetAuthUser() authUser: AuthUserDto, @Query('isAll', ParseBoolPipe) isAll: boolean, ): Promise { - return await this.userService.getAllUsers(authUser, isAll); + return this.service.getAllUsers(authUser, isAll); } @Get('/info/:userId') - async getUserById(@Param('userId') userId: string): Promise { - return await this.userService.getUserById(userId); + getUserById(@Param('userId') userId: string): Promise { + return this.service.getUserById(userId); } @Authenticated() @Get('me') - async getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise { - return await this.userService.getUserInfo(authUser); + getMyUserInfo(@GetAuthUser() authUser: AuthUserDto): Promise { + return this.service.getUserInfo(authUser); } @Authenticated({ admin: true }) @Post() - async createUser( - @Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto, - ): Promise { - return await this.userService.createUser(createUserDto); + createUser(@Body(new ValidationPipe({ transform: true })) createUserDto: CreateUserDto): Promise { + return this.service.createUser(createUserDto); } @Get('/count') - async getUserCount(@Query(new ValidationPipe({ transform: true })) dto: UserCountDto): Promise { - return await this.userService.getUserCount(dto); + getUserCount(@Query(new ValidationPipe({ transform: true })) dto: UserCountDto): Promise { + return this.service.getUserCount(dto); } @Authenticated({ admin: true }) @Delete('/:userId') - async deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { - return await this.userService.deleteUser(authUser, userId); + deleteUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { + return this.service.deleteUser(authUser, userId); } @Authenticated({ admin: true }) @Post('/:userId/restore') - async restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { - return await this.userService.restoreUser(authUser, userId); + restoreUser(@GetAuthUser() authUser: AuthUserDto, @Param('userId') userId: string): Promise { + return this.service.restoreUser(authUser, userId); } @Authenticated() @Put() - async updateUser( + updateUser( @GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) updateUserDto: UpdateUserDto, ): Promise { - return await this.userService.updateUser(authUser, updateUserDto); + return this.service.updateUser(authUser, updateUserDto); } @UseInterceptors(FileInterceptor('file', profileImageUploadOption)) @@ -97,20 +95,18 @@ export class UserController { type: CreateProfileImageDto, }) @Post('/profile-image') - async createProfileImage( + createProfileImage( @GetAuthUser() authUser: AuthUserDto, @UploadedFile() fileInfo: Express.Multer.File, ): Promise { - return await this.userService.createProfileImage(authUser, fileInfo); + return this.service.createProfileImage(authUser, fileInfo); } @Get('/profile-image/:userId') @Header('Cache-Control', 'max-age=600') async getProfileImage(@Param('userId') userId: string, @Response({ passthrough: true }) res: Res): Promise { - const readableStream = await this.userService.getUserProfileImage(userId); - res.set({ - 'Content-Type': 'image/jpeg', - }); + const readableStream = await this.service.getUserProfileImage(userId); + res.header('Content-Type', 'image/jpeg'); return new StreamableFile(readableStream); } } diff --git a/server/libs/domain/src/oauth/oauth.service.spec.ts b/server/libs/domain/src/oauth/oauth.service.spec.ts index d752a34207..f6b4f3e1cc 100644 --- a/server/libs/domain/src/oauth/oauth.service.spec.ts +++ b/server/libs/domain/src/oauth/oauth.service.spec.ts @@ -63,6 +63,16 @@ describe('OAuthService', () => { expect(sut).toBeDefined(); }); + describe('getMobileRedirect', () => { + it('should pass along the query params', () => { + expect(sut.getMobileRedirect('http://immich.app?code=123&state=456')).toEqual('app.immich:/?code=123&state=456'); + }); + + it('should work if called without query params', () => { + expect(sut.getMobileRedirect('http://immich.app')).toEqual('app.immich:/?'); + }); + }); + describe('generateConfig', () => { it('should work when oauth is not configured', async () => { await expect(sut.generateConfig({ redirectUri: 'http://callback' })).resolves.toEqual({ diff --git a/server/libs/domain/src/oauth/oauth.service.ts b/server/libs/domain/src/oauth/oauth.service.ts index df8affc287..1eb8a5c496 100644 --- a/server/libs/domain/src/oauth/oauth.service.ts +++ b/server/libs/domain/src/oauth/oauth.service.ts @@ -1,14 +1,15 @@ import { SystemConfig } from '@app/infra/db/entities'; import { BadRequestException, Inject, Injectable, Logger } from '@nestjs/common'; import { AuthType, AuthUserDto, LoginResponseDto } from '../auth'; -import { ICryptoRepository } from '../crypto'; import { AuthCore } from '../auth/auth.core'; +import { ICryptoRepository } from '../crypto'; import { INITIAL_SYSTEM_CONFIG, ISystemConfigRepository } from '../system-config'; import { IUserRepository, UserCore, UserResponseDto } from '../user'; +import { IUserTokenRepository } from '../user-token'; import { OAuthCallbackDto, OAuthConfigDto } from './dto'; +import { MOBILE_REDIRECT } from './oauth.constants'; import { OAuthCore } from './oauth.core'; import { OAuthConfigResponseDto } from './response-dto'; -import { IUserTokenRepository } from '../user-token'; @Injectable() export class OAuthService { @@ -30,6 +31,10 @@ export class OAuthService { this.oauthCore = new OAuthCore(configRepository, initialConfig); } + getMobileRedirect(url: string) { + return `${MOBILE_REDIRECT}?${url.split('?')[1] || ''}`; + } + generateConfig(dto: OAuthConfigDto): Promise { return this.oauthCore.generateConfig(dto); }