|
@@ -1,7 +1,7 @@
|
|
import { SystemConfig } from '@app/database/entities/system-config.entity';
|
|
import { SystemConfig } from '@app/database/entities/system-config.entity';
|
|
import { UserEntity } from '@app/database/entities/user.entity';
|
|
import { UserEntity } from '@app/database/entities/user.entity';
|
|
import { ImmichConfigService } from '@app/immich-config';
|
|
import { ImmichConfigService } from '@app/immich-config';
|
|
-import { BadRequestException } from '@nestjs/common';
|
|
|
|
|
|
+import { BadRequestException, Logger } from '@nestjs/common';
|
|
import { generators, Issuer } from 'openid-client';
|
|
import { generators, Issuer } from 'openid-client';
|
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
|
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
|
import { ImmichJwtService } from '../../modules/immich-jwt/immich-jwt.service';
|
|
@@ -32,6 +32,21 @@ const loginResponse = {
|
|
userEmail: 'user@immich.com,',
|
|
userEmail: 'user@immich.com,',
|
|
} as LoginResponseDto;
|
|
} as LoginResponseDto;
|
|
|
|
|
|
|
|
+jest.mock('@nestjs/common', () => {
|
|
|
|
+ return {
|
|
|
|
+ ...jest.requireActual('@nestjs/common'),
|
|
|
|
+ Logger: function MockLogger() {
|
|
|
|
+ Object.assign(this as Logger, {
|
|
|
|
+ verbose: jest.fn(),
|
|
|
|
+ debug: jest.fn(),
|
|
|
|
+ log: jest.fn(),
|
|
|
|
+ warn: jest.fn(),
|
|
|
|
+ error: jest.fn(),
|
|
|
|
+ });
|
|
|
|
+ },
|
|
|
|
+ };
|
|
|
|
+});
|
|
|
|
+
|
|
describe('OAuthService', () => {
|
|
describe('OAuthService', () => {
|
|
let sut: OAuthService;
|
|
let sut: OAuthService;
|
|
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
|
let userRepositoryMock: jest.Mocked<IUserRepository>;
|
|
@@ -109,9 +124,9 @@ describe('OAuthService', () => {
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
- describe('callback', () => {
|
|
|
|
|
|
+ describe('login', () => {
|
|
it('should throw an error if OAuth is not enabled', async () => {
|
|
it('should throw an error if OAuth is not enabled', async () => {
|
|
- await expect(sut.callback(authUser, { url: '' })).rejects.toBeInstanceOf(BadRequestException);
|
|
|
|
|
|
+ await expect(sut.login({ url: '' })).rejects.toBeInstanceOf(BadRequestException);
|
|
});
|
|
});
|
|
|
|
|
|
it('should not allow auto registering', async () => {
|
|
it('should not allow auto registering', async () => {
|
|
@@ -122,10 +137,8 @@ describe('OAuthService', () => {
|
|
},
|
|
},
|
|
} as SystemConfig);
|
|
} as SystemConfig);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
- jest.spyOn(sut['logger'], 'debug').mockImplementation(() => null);
|
|
|
|
- jest.spyOn(sut['logger'], 'warn').mockImplementation(() => null);
|
|
|
|
userRepositoryMock.getByEmail.mockResolvedValue(null);
|
|
userRepositoryMock.getByEmail.mockResolvedValue(null);
|
|
- await expect(sut.callback(authUser, { url: 'http://immich/auth/login?code=abc123' })).rejects.toBeInstanceOf(
|
|
|
|
|
|
+ await expect(sut.login({ url: 'http://immich/auth/login?code=abc123' })).rejects.toBeInstanceOf(
|
|
BadRequestException,
|
|
BadRequestException,
|
|
);
|
|
);
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(1);
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(1);
|
|
@@ -139,15 +152,11 @@ describe('OAuthService', () => {
|
|
},
|
|
},
|
|
} as SystemConfig);
|
|
} as SystemConfig);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
- jest.spyOn(sut['logger'], 'debug').mockImplementation(() => null);
|
|
|
|
- jest.spyOn(sut['logger'], 'warn').mockImplementation(() => null);
|
|
|
|
userRepositoryMock.getByEmail.mockResolvedValue(user);
|
|
userRepositoryMock.getByEmail.mockResolvedValue(user);
|
|
userRepositoryMock.update.mockResolvedValue(user);
|
|
userRepositoryMock.update.mockResolvedValue(user);
|
|
immichJwtServiceMock.createLoginResponse.mockResolvedValue(loginResponse);
|
|
immichJwtServiceMock.createLoginResponse.mockResolvedValue(loginResponse);
|
|
|
|
|
|
- await expect(sut.callback(authUser, { url: 'http://immich/auth/login?code=abc123' })).resolves.toEqual(
|
|
|
|
- loginResponse,
|
|
|
|
- );
|
|
|
|
|
|
+ await expect(sut.login({ url: 'http://immich/auth/login?code=abc123' })).resolves.toEqual(loginResponse);
|
|
|
|
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(1);
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(1);
|
|
expect(userRepositoryMock.update).toHaveBeenCalledWith(user.id, { oauthId: sub });
|
|
expect(userRepositoryMock.update).toHaveBeenCalledWith(user.id, { oauthId: sub });
|
|
@@ -161,16 +170,12 @@ describe('OAuthService', () => {
|
|
},
|
|
},
|
|
} as SystemConfig);
|
|
} as SystemConfig);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
sut = new OAuthService(immichJwtServiceMock, immichConfigServiceMock, userRepositoryMock);
|
|
- jest.spyOn(sut['logger'], 'debug').mockImplementation(() => null);
|
|
|
|
- jest.spyOn(sut['logger'], 'log').mockImplementation(() => null);
|
|
|
|
userRepositoryMock.getByEmail.mockResolvedValue(null);
|
|
userRepositoryMock.getByEmail.mockResolvedValue(null);
|
|
userRepositoryMock.getAdmin.mockResolvedValue(user);
|
|
userRepositoryMock.getAdmin.mockResolvedValue(user);
|
|
userRepositoryMock.create.mockResolvedValue(user);
|
|
userRepositoryMock.create.mockResolvedValue(user);
|
|
immichJwtServiceMock.createLoginResponse.mockResolvedValue(loginResponse);
|
|
immichJwtServiceMock.createLoginResponse.mockResolvedValue(loginResponse);
|
|
|
|
|
|
- await expect(sut.callback(authUser, { url: 'http://immich/auth/login?code=abc123' })).resolves.toEqual(
|
|
|
|
- loginResponse,
|
|
|
|
- );
|
|
|
|
|
|
+ await expect(sut.login({ url: 'http://immich/auth/login?code=abc123' })).resolves.toEqual(loginResponse);
|
|
|
|
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(2); // second call is for domain check before create
|
|
expect(userRepositoryMock.getByEmail).toHaveBeenCalledTimes(2); // second call is for domain check before create
|
|
expect(userRepositoryMock.create).toHaveBeenCalledTimes(1);
|
|
expect(userRepositoryMock.create).toHaveBeenCalledTimes(1);
|
|
@@ -178,6 +183,57 @@ describe('OAuthService', () => {
|
|
});
|
|
});
|
|
});
|
|
});
|
|
|
|
|
|
|
|
+ describe('link', () => {
|
|
|
|
+ it('should link an account', async () => {
|
|
|
|
+ immichConfigServiceMock.getConfig.mockResolvedValue({
|
|
|
|
+ oauth: {
|
|
|
|
+ enabled: true,
|
|
|
|
+ autoRegister: true,
|
|
|
|
+ },
|
|
|
|
+ } as SystemConfig);
|
|
|
|
+
|
|
|
|
+ userRepositoryMock.update.mockResolvedValue(user);
|
|
|
|
+
|
|
|
|
+ await sut.link(authUser, { url: 'http://immich/user-settings?code=abc123' });
|
|
|
|
+
|
|
|
|
+ expect(userRepositoryMock.update).toHaveBeenCalledWith(authUser.id, { oauthId: sub });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ it('should not link an already linked oauth.sub', async () => {
|
|
|
|
+ immichConfigServiceMock.getConfig.mockResolvedValue({
|
|
|
|
+ oauth: {
|
|
|
|
+ enabled: true,
|
|
|
|
+ autoRegister: true,
|
|
|
|
+ },
|
|
|
|
+ } as SystemConfig);
|
|
|
|
+
|
|
|
|
+ userRepositoryMock.getByOAuthId.mockResolvedValue({ id: 'other-user' } as UserEntity);
|
|
|
|
+
|
|
|
|
+ await expect(sut.link(authUser, { url: 'http://immich/user-settings?code=abc123' })).rejects.toBeInstanceOf(
|
|
|
|
+ BadRequestException,
|
|
|
|
+ );
|
|
|
|
+
|
|
|
|
+ expect(userRepositoryMock.update).not.toHaveBeenCalled();
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ describe('unlink', () => {
|
|
|
|
+ it('should unlink an account', async () => {
|
|
|
|
+ immichConfigServiceMock.getConfig.mockResolvedValue({
|
|
|
|
+ oauth: {
|
|
|
|
+ enabled: true,
|
|
|
|
+ autoRegister: true,
|
|
|
|
+ },
|
|
|
|
+ } as SystemConfig);
|
|
|
|
+
|
|
|
|
+ userRepositoryMock.update.mockResolvedValue(user);
|
|
|
|
+
|
|
|
|
+ await sut.unlink(authUser);
|
|
|
|
+
|
|
|
|
+ expect(userRepositoryMock.update).toHaveBeenCalledWith(authUser.id, { oauthId: '' });
|
|
|
|
+ });
|
|
|
|
+ });
|
|
|
|
+
|
|
describe('getLogoutEndpoint', () => {
|
|
describe('getLogoutEndpoint', () => {
|
|
it('should return null if OAuth is not configured', async () => {
|
|
it('should return null if OAuth is not configured', async () => {
|
|
await expect(sut.getLogoutEndpoint()).resolves.toBeNull();
|
|
await expect(sut.getLogoutEndpoint()).resolves.toBeNull();
|