mirror of
https://github.com/immich-app/immich.git
synced 2024-12-26 10:50:29 +02:00
fix(server): auth strategies (#1459)
* fix(server): auth strategies * chore: tests
This commit is contained in:
parent
5939d79057
commit
414893a687
@ -15,7 +15,7 @@ export class APIKeyStrategy extends PassportStrategy(Strategy, API_KEY_STRATEGY)
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
validate(token: string): Promise<AuthUserDto> {
|
validate(token: string): Promise<AuthUserDto | null> {
|
||||||
return this.apiKeyService.validate(token);
|
return this.apiKeyService.validate(token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ export class PublicShareStrategy extends PassportStrategy(Strategy, PUBLIC_SHARE
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(key: string): Promise<AuthUserDto> {
|
validate(key: string): Promise<AuthUserDto | null> {
|
||||||
return this.shareService.validate(key);
|
return this.shareService.validate(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,24 +1,18 @@
|
|||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { AuthService, AuthUserDto } from '@app/domain';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { AuthService, AuthUserDto, UserService } from '@app/domain';
|
|
||||||
import { Strategy } from 'passport-custom';
|
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
|
import { Strategy } from 'passport-custom';
|
||||||
|
|
||||||
export const AUTH_COOKIE_STRATEGY = 'auth-cookie';
|
export const AUTH_COOKIE_STRATEGY = 'auth-cookie';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserAuthStrategy extends PassportStrategy(Strategy, AUTH_COOKIE_STRATEGY) {
|
export class UserAuthStrategy extends PassportStrategy(Strategy, AUTH_COOKIE_STRATEGY) {
|
||||||
constructor(private userService: UserService, private authService: AuthService) {
|
constructor(private authService: AuthService) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(request: Request): Promise<AuthUserDto> {
|
validate(request: Request): Promise<AuthUserDto | null> {
|
||||||
const authUser = await this.authService.validate(request.headers);
|
return this.authService.validate(request.headers);
|
||||||
|
|
||||||
if (!authUser) {
|
|
||||||
throw new UnauthorizedException('Incorrect token provided');
|
|
||||||
}
|
|
||||||
|
|
||||||
return authUser;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { APIKeyEntity } from '@app/infra/db/entities';
|
import { APIKeyEntity } from '@app/infra/db/entities';
|
||||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { authStub, userEntityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
import { authStub, userEntityStub, newCryptoRepositoryMock, newKeyRepositoryMock } from '../../test';
|
||||||
import { ICryptoRepository } from '../auth';
|
import { ICryptoRepository } from '../auth';
|
||||||
import { IKeyRepository } from './api-key.repository';
|
import { IKeyRepository } from './api-key.repository';
|
||||||
@ -124,7 +124,7 @@ describe(APIKeyService.name, () => {
|
|||||||
it('should throw an error for an invalid id', async () => {
|
it('should throw an error for an invalid id', async () => {
|
||||||
keyMock.getKey.mockResolvedValue(null);
|
keyMock.getKey.mockResolvedValue(null);
|
||||||
|
|
||||||
await expect(sut.validate(token)).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate(token)).resolves.toBeNull();
|
||||||
|
|
||||||
expect(keyMock.getKey).toHaveBeenCalledWith('bXktYXBpLWtleQ== (hashed)');
|
expect(keyMock.getKey).toHaveBeenCalledWith('bXktYXBpLWtleQ== (hashed)');
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, Inject, Injectable, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||||
import { IKeyRepository } from './api-key.repository';
|
import { IKeyRepository } from './api-key.repository';
|
||||||
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
import { APIKeyCreateDto } from './dto/api-key-create.dto';
|
||||||
@ -56,7 +56,7 @@ export class APIKeyService {
|
|||||||
return keys.map(mapKey);
|
return keys.map(mapKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(token: string): Promise<AuthUserDto> {
|
async validate(token: string): Promise<AuthUserDto | null> {
|
||||||
const hashedToken = this.crypto.hashSha256(token);
|
const hashedToken = this.crypto.hashSha256(token);
|
||||||
const keyEntity = await this.repository.getKey(hashedToken);
|
const keyEntity = await this.repository.getKey(hashedToken);
|
||||||
if (keyEntity?.user) {
|
if (keyEntity?.user) {
|
||||||
@ -71,6 +71,6 @@ export class APIKeyService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnauthorizedException('Invalid API Key');
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,7 +229,7 @@ describe('AuthService', () => {
|
|||||||
describe('validate - api request', () => {
|
describe('validate - api request', () => {
|
||||||
it('should throw if no user is found', async () => {
|
it('should throw if no user is found', async () => {
|
||||||
userMock.get.mockResolvedValue(null);
|
userMock.get.mockResolvedValue(null);
|
||||||
await expect(sut.validate({ email: 'a', userId: 'test' })).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate({ email: 'a', userId: 'test' })).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return an auth dto', async () => {
|
it('should return an auth dto', async () => {
|
||||||
|
@ -115,10 +115,10 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async validate(headers: IncomingHttpHeaders): Promise<AuthUserDto> {
|
public async validate(headers: IncomingHttpHeaders): Promise<AuthUserDto | null> {
|
||||||
const tokenValue = this.extractTokenFromHeader(headers);
|
const tokenValue = this.extractTokenFromHeader(headers);
|
||||||
if (!tokenValue) {
|
if (!tokenValue) {
|
||||||
throw new UnauthorizedException('No access token provided in request');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
|
const hashedToken = this.cryptoRepository.hashSha256(tokenValue);
|
||||||
@ -133,7 +133,7 @@ export class AuthService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new UnauthorizedException('Invalid access token provided');
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
extractTokenFromHeader(headers: IncomingHttpHeaders) {
|
extractTokenFromHeader(headers: IncomingHttpHeaders) {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
|
import { BadRequestException, ForbiddenException } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
authStub,
|
authStub,
|
||||||
userEntityStub,
|
userEntityStub,
|
||||||
@ -34,18 +34,18 @@ describe(ShareService.name, () => {
|
|||||||
describe('validate', () => {
|
describe('validate', () => {
|
||||||
it('should not accept a non-existant key', async () => {
|
it('should not accept a non-existant key', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(null);
|
shareMock.getByKey.mockResolvedValue(null);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not accept an expired key', async () => {
|
it('should not accept an expired key', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not accept a key without a user', async () => {
|
it('should not accept a key without a user', async () => {
|
||||||
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
|
||||||
userMock.get.mockResolvedValue(null);
|
userMock.get.mockResolvedValue(null);
|
||||||
await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
|
await expect(sut.validate('key')).resolves.toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should accept a valid key', async () => {
|
it('should accept a valid key', async () => {
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { BadRequestException, ForbiddenException, Inject, Injectable, Logger } from '@nestjs/common';
|
||||||
BadRequestException,
|
|
||||||
ForbiddenException,
|
|
||||||
Inject,
|
|
||||||
Injectable,
|
|
||||||
Logger,
|
|
||||||
UnauthorizedException,
|
|
||||||
} from '@nestjs/common';
|
|
||||||
import { AuthUserDto, ICryptoRepository } from '../auth';
|
import { AuthUserDto, ICryptoRepository } from '../auth';
|
||||||
import { IUserRepository, UserCore } from '../user';
|
import { IUserRepository, UserCore } from '../user';
|
||||||
import { EditSharedLinkDto } from './dto';
|
import { EditSharedLinkDto } from './dto';
|
||||||
@ -28,7 +21,7 @@ export class ShareService {
|
|||||||
this.userCore = new UserCore(userRepository, cryptoRepository);
|
this.userCore = new UserCore(userRepository, cryptoRepository);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(key: string): Promise<AuthUserDto> {
|
async validate(key: string): Promise<AuthUserDto | null> {
|
||||||
const link = await this.shareCore.getByKey(key);
|
const link = await this.shareCore.getByKey(key);
|
||||||
if (link) {
|
if (link) {
|
||||||
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
|
if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
|
||||||
@ -47,7 +40,7 @@ export class ShareService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new UnauthorizedException();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
|
||||||
|
Loading…
Reference in New Issue
Block a user