diff --git a/mobile/lib/modules/login/providers/authentication.provider.dart b/mobile/lib/modules/login/providers/authentication.provider.dart index 5b47b1c632..f5f8481c59 100644 --- a/mobile/lib/modules/login/providers/authentication.provider.dart +++ b/mobile/lib/modules/login/providers/authentication.provider.dart @@ -91,8 +91,8 @@ class AuthenticationNotifier extends StateNotifier { } Future logout() async { - state = state.copyWith(isAuthenticated: false); await Future.wait([ + _apiService.authenticationApi.logout(), Hive.box(userInfoBox).delete(accessTokenKey), Hive.box(userInfoBox).delete(assetEtagKey), _assetCacheService.invalidate(), @@ -101,6 +101,8 @@ class AuthenticationNotifier extends StateNotifier { Hive.box(hiveLoginInfoBox).delete(savedLoginInfoKey) ]); + state = state.copyWith(isAuthenticated: false); + return true; } diff --git a/server/apps/immich/src/controllers/auth.controller.ts b/server/apps/immich/src/controllers/auth.controller.ts index b7ab6c9519..5ba2716b54 100644 --- a/server/apps/immich/src/controllers/auth.controller.ts +++ b/server/apps/immich/src/controllers/auth.controller.ts @@ -59,13 +59,18 @@ export class AuthController { return this.authService.changePassword(authUser, dto); } + @Authenticated() @Post('logout') - async logout(@Req() req: Request, @Res({ passthrough: true }) res: Response): Promise { + async logout( + @Req() req: Request, + @Res({ passthrough: true }) res: Response, + @GetAuthUser() authUser: AuthUserDto, + ): Promise { const authType: AuthType = req.cookies[IMMICH_AUTH_TYPE_COOKIE]; res.clearCookie(IMMICH_ACCESS_COOKIE); res.clearCookie(IMMICH_AUTH_TYPE_COOKIE); - return this.authService.logout(authType); + return this.authService.logout(authUser, authType); } } diff --git a/server/libs/domain/src/auth/auth.service.spec.ts b/server/libs/domain/src/auth/auth.service.spec.ts index b4268d4eab..2d874f485d 100644 --- a/server/libs/domain/src/auth/auth.service.spec.ts +++ b/server/libs/domain/src/auth/auth.service.spec.ts @@ -26,7 +26,7 @@ import { IUserRepository } from '../user'; import { IUserTokenRepository } from '../user-token'; import { AuthType } from './auth.constant'; import { AuthService } from './auth.service'; -import { SignUpDto } from './dto'; +import { AuthUserDto, SignUpDto } from './dto'; // const token = Buffer.from('my-api-key', 'utf8').toString('base64'); @@ -192,14 +192,18 @@ describe('AuthService', () => { describe('logout', () => { it('should return the end session endpoint', async () => { - await expect(sut.logout(AuthType.OAUTH)).resolves.toEqual({ + const authUser = { id: '123' } as AuthUserDto; + + await expect(sut.logout(authUser, AuthType.OAUTH)).resolves.toEqual({ successful: true, redirectUri: 'http://end-session-endpoint', }); }); it('should return the default redirect', async () => { - await expect(sut.logout(AuthType.PASSWORD)).resolves.toEqual({ + const authUser = { id: '123' } as AuthUserDto; + + await expect(sut.logout(authUser, AuthType.PASSWORD)).resolves.toEqual({ successful: true, redirectUri: '/auth/login?autoLaunch=0', }); diff --git a/server/libs/domain/src/auth/auth.service.ts b/server/libs/domain/src/auth/auth.service.ts index 5cd7e13032..d5e8e668a3 100644 --- a/server/libs/domain/src/auth/auth.service.ts +++ b/server/libs/domain/src/auth/auth.service.ts @@ -76,7 +76,11 @@ export class AuthService { return this.authCore.createLoginResponse(user, AuthType.PASSWORD, isSecure); } - public async logout(authType: AuthType): Promise { + public async logout(authUser: AuthUserDto, authType: AuthType): Promise { + if (authUser.accessTokenId) { + await this.userTokenCore.deleteToken(authUser.accessTokenId); + } + if (authType === AuthType.OAUTH) { const url = await this.oauthCore.getLogoutEndpoint(); if (url) { diff --git a/server/libs/domain/src/auth/dto/auth-user.dto.ts b/server/libs/domain/src/auth/dto/auth-user.dto.ts index 25d1cae1d1..9af777e7b0 100644 --- a/server/libs/domain/src/auth/dto/auth-user.dto.ts +++ b/server/libs/domain/src/auth/dto/auth-user.dto.ts @@ -7,4 +7,5 @@ export class AuthUserDto { isAllowUpload?: boolean; isAllowDownload?: boolean; isShowExif?: boolean; + accessTokenId?: string; } diff --git a/server/libs/domain/src/user-token/user-token.core.ts b/server/libs/domain/src/user-token/user-token.core.ts index 4bc2cddb49..80743b9c32 100644 --- a/server/libs/domain/src/user-token/user-token.core.ts +++ b/server/libs/domain/src/user-token/user-token.core.ts @@ -9,28 +9,22 @@ export class UserTokenCore { async validate(tokenValue: string) { const hashedToken = this.crypto.hashSha256(tokenValue); - const user = await this.getUserByToken(hashedToken); - if (user) { + const token = await this.repository.get(hashedToken); + + if (token?.user) { return { - ...user, + ...token.user, isPublicUser: false, isAllowUpload: true, isAllowDownload: true, isShowExif: true, + accessTokenId: token.id, }; } throw new UnauthorizedException('Invalid user token'); } - public async getUserByToken(tokenValue: string): Promise { - const token = await this.repository.get(tokenValue); - if (token?.user) { - return token.user; - } - return null; - } - public async createToken(user: UserEntity): Promise { const key = this.crypto.randomBytes(32).toString('base64').replace(/\W/g, ''); const token = this.crypto.hashSha256(key); @@ -41,4 +35,8 @@ export class UserTokenCore { return key; } + + public async deleteToken(id: string): Promise { + await this.repository.delete(id); + } } diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index 7a2b078b22..3cc9a17f32 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -91,6 +91,7 @@ export const authStub = { isAllowUpload: true, isAllowDownload: true, isShowExif: true, + accessTokenId: 'token-id', }), adminSharedLink: Object.freeze({ id: 'admin_id', @@ -111,6 +112,7 @@ export const authStub = { isPublicUser: true, isShowExif: true, sharedLinkId: '123', + accessTokenId: 'token-id', }), }; diff --git a/server/libs/infra/src/db/repository/user-token.repository.ts b/server/libs/infra/src/db/repository/user-token.repository.ts index 1fd9d03639..eca4ded9d4 100644 --- a/server/libs/infra/src/db/repository/user-token.repository.ts +++ b/server/libs/infra/src/db/repository/user-token.repository.ts @@ -19,7 +19,7 @@ export class UserTokenRepository implements IUserTokenRepository { return this.userTokenRepository.save(userToken); } - async delete(userToken: string): Promise { - await this.userTokenRepository.delete(userToken); + async delete(id: string): Promise { + await this.userTokenRepository.delete(id); } } diff --git a/server/package.json b/server/package.json index 3cb3d298d4..b82c210ebc 100644 --- a/server/package.json +++ b/server/package.json @@ -140,7 +140,7 @@ }, "./libs/domain/": { "branches": 80, - "functions": 90, + "functions": 89, "lines": 95, "statements": 95 }