Files
immich/server/libs/domain/src/share/share.service.spec.ts
Zack Pollard 3f2513a717 feat(server): move authentication to tokens stored in the database (#1381)
* chore: add typeorm commands to npm and set default database config values

* feat: move to server side authentication tokens

* fix: websocket should emit error and disconnect on error thrown by the server

* refactor: rename cookie-auth-strategy to user-auth-strategy

* feat: user tokens and API keys now use SHA256 hash for performance improvements

* test: album e2e test remove unneeded module import

* infra: truncate api key table as old keys will no longer work with new hash algorithm

* fix(server): e2e tests (#1435)

* fix: root module paths

* chore: linting

* chore: rename user-auth to strategy.ts and make validate return AuthUserDto

* fix: we should always send HttpOnly for our auth cookies

* chore: remove now unused crypto functions and jwt dependencies

* fix: return the extra fields for AuthUserDto in auth service validate

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
2023-01-27 14:50:07 -06:00

171 lines
6.8 KiB
TypeScript

import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
import {
authStub,
userEntityStub,
newCryptoRepositoryMock,
newSharedLinkRepositoryMock,
newUserRepositoryMock,
sharedLinkResponseStub,
sharedLinkStub,
} from '../../test';
import { ICryptoRepository } from '../auth';
import { IUserRepository } from '../user';
import { ShareService } from './share.service';
import { ISharedLinkRepository } from './shared-link.repository';
describe(ShareService.name, () => {
let sut: ShareService;
let cryptoMock: jest.Mocked<ICryptoRepository>;
let shareMock: jest.Mocked<ISharedLinkRepository>;
let userMock: jest.Mocked<IUserRepository>;
beforeEach(async () => {
cryptoMock = newCryptoRepositoryMock();
shareMock = newSharedLinkRepositoryMock();
userMock = newUserRepositoryMock();
sut = new ShareService(cryptoMock, shareMock, userMock);
});
it('should work', () => {
expect(sut).toBeDefined();
});
describe('validate', () => {
it('should not accept a non-existant key', async () => {
shareMock.getByKey.mockResolvedValue(null);
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
});
it('should not accept an expired key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
});
it('should not accept a key without a user', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
userMock.get.mockResolvedValue(null);
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
});
it('should accept a valid key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
userMock.get.mockResolvedValue(userEntityStub.admin);
await expect(sut.validate('key')).resolves.toEqual(authStub.adminSharedLink);
});
});
describe('getAll', () => {
it('should return all keys for a user', async () => {
shareMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
await expect(sut.getAll(authStub.user1)).resolves.toEqual([
sharedLinkResponseStub.expired,
sharedLinkResponseStub.valid,
]);
expect(shareMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
});
});
describe('getMine', () => {
it('should only work for a public user', async () => {
await expect(sut.getMine(authStub.admin)).rejects.toBeInstanceOf(ForbiddenException);
expect(shareMock.get).not.toHaveBeenCalled();
});
it('should return the key for the public user (auth dto)', async () => {
const authDto = authStub.adminSharedLink;
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.getMine(authDto)).resolves.toEqual(sharedLinkResponseStub.valid);
expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
});
});
describe('get', () => {
it('should not work on a missing key', async () => {
shareMock.get.mockResolvedValue(null);
await expect(sut.getById(authStub.user1, sharedLinkStub.valid.id, true)).rejects.toBeInstanceOf(
BadRequestException,
);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
expect(shareMock.remove).not.toHaveBeenCalled();
});
it('should get a key by id', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.getById(authStub.user1, sharedLinkStub.valid.id, false)).resolves.toEqual(
sharedLinkResponseStub.valid,
);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
});
it('should include exif', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.readonly);
await expect(sut.getById(authStub.user1, sharedLinkStub.readonly.id, true)).resolves.toEqual(
sharedLinkResponseStub.readonly,
);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.readonly.id);
});
it('should exclude exif', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.readonly);
await expect(sut.getById(authStub.user1, sharedLinkStub.readonly.id, false)).resolves.toEqual(
sharedLinkResponseStub.readonlyNoExif,
);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.readonly.id);
});
});
describe('remove', () => {
it('should not work on a missing key', async () => {
shareMock.get.mockResolvedValue(null);
await expect(sut.remove(authStub.user1, sharedLinkStub.valid.id)).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
expect(shareMock.remove).not.toHaveBeenCalled();
});
it('should remove a key', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
await sut.remove(authStub.user1, sharedLinkStub.valid.id);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
expect(shareMock.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
});
});
describe('getByKey', () => {
it('should not work on a missing key', async () => {
shareMock.getByKey.mockResolvedValue(null);
await expect(sut.getByKey('secret-key')).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.getByKey).toHaveBeenCalledWith('secret-key');
});
it('should find a key', async () => {
shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
await expect(sut.getByKey('secret-key')).resolves.toEqual(sharedLinkResponseStub.valid);
expect(shareMock.getByKey).toHaveBeenCalledWith('secret-key');
});
});
describe('edit', () => {
it('should not work on a missing key', async () => {
shareMock.get.mockResolvedValue(null);
await expect(sut.edit(authStub.user1, sharedLinkStub.valid.id, {})).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
expect(shareMock.save).not.toHaveBeenCalled();
});
it('should edit a key', async () => {
shareMock.get.mockResolvedValue(sharedLinkStub.valid);
shareMock.save.mockResolvedValue(sharedLinkStub.valid);
const dto = { allowDownload: false };
await sut.edit(authStub.user1, sharedLinkStub.valid.id, dto);
// await expect(sut.edit(authStub.user1, sharedLinkStub.valid.id, dto)).rejects.toBeInstanceOf(BadRequestException);
expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
expect(shareMock.save).toHaveBeenCalledWith({
id: sharedLinkStub.valid.id,
userId: authStub.user1.id,
allowDownload: false,
});
});
});
});