2023-07-01 14:27:34 -04:00
|
|
|
import {
|
|
|
|
|
CanActivate,
|
|
|
|
|
ExecutionContext,
|
|
|
|
|
Injectable,
|
|
|
|
|
SetMetadata,
|
2023-09-04 15:45:59 -04:00
|
|
|
applyDecorators,
|
|
|
|
|
createParamDecorator,
|
2023-07-01 14:27:34 -04:00
|
|
|
} from '@nestjs/common';
|
|
|
|
|
import { Reflector } from '@nestjs/core';
|
2025-07-28 18:40:34 -04:00
|
|
|
import { ApiBearerAuth, ApiCookieAuth, ApiExtension, ApiOkResponse, ApiQuery, ApiSecurity } from '@nestjs/swagger';
|
2023-07-01 14:27:34 -04:00
|
|
|
import { Request } from 'express';
|
2024-10-17 13:17:32 -04:00
|
|
|
import { AuthDto } from 'src/dtos/auth.dto';
|
2025-07-30 12:29:36 -04:00
|
|
|
import { ApiCustomExtension, ImmichQuery, MetadataKey, Permission } from 'src/enum';
|
2025-01-23 08:31:30 -05:00
|
|
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
2024-03-21 00:07:30 +01:00
|
|
|
import { AuthService, LoginDetails } from 'src/services/auth.service';
|
2023-07-01 14:27:34 -04:00
|
|
|
import { UAParser } from 'ua-parser-js';
|
|
|
|
|
|
2024-05-09 13:58:44 -04:00
|
|
|
type AdminRoute = { admin?: true };
|
|
|
|
|
type SharedLinkRoute = { sharedLink?: true };
|
2024-08-16 09:48:43 -04:00
|
|
|
type AuthenticatedOptions = { permission?: Permission } & (AdminRoute | SharedLinkRoute);
|
2023-07-01 14:27:34 -04:00
|
|
|
|
2025-07-30 12:29:36 -04:00
|
|
|
export const Authenticated = (options: AuthenticatedOptions = {}): MethodDecorator => {
|
2023-07-01 14:27:34 -04:00
|
|
|
const decorators: MethodDecorator[] = [
|
|
|
|
|
ApiBearerAuth(),
|
|
|
|
|
ApiCookieAuth(),
|
2025-07-15 14:50:13 -04:00
|
|
|
ApiSecurity(MetadataKey.ApiKeySecurity),
|
2025-07-30 12:29:36 -04:00
|
|
|
SetMetadata(MetadataKey.AuthRoute, options),
|
2023-07-01 14:27:34 -04:00
|
|
|
];
|
|
|
|
|
|
2025-07-30 12:29:36 -04:00
|
|
|
if ((options as AdminRoute).admin) {
|
|
|
|
|
decorators.push(ApiExtension(ApiCustomExtension.AdminOnly, true));
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-28 18:40:34 -04:00
|
|
|
if (options?.permission) {
|
2025-07-30 12:29:36 -04:00
|
|
|
decorators.push(ApiExtension(ApiCustomExtension.Permission, options.permission ?? Permission.All));
|
2025-07-28 18:40:34 -04:00
|
|
|
}
|
|
|
|
|
|
2024-05-09 13:58:44 -04:00
|
|
|
if ((options as SharedLinkRoute)?.sharedLink) {
|
2025-07-28 14:16:55 -04:00
|
|
|
decorators.push(
|
|
|
|
|
ApiQuery({ name: ImmichQuery.SharedLinkKey, type: String, required: false }),
|
|
|
|
|
ApiQuery({ name: ImmichQuery.SharedLinkSlug, type: String, required: false }),
|
|
|
|
|
);
|
2023-07-01 14:27:34 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return applyDecorators(...decorators);
|
|
|
|
|
};
|
|
|
|
|
|
2024-02-02 04:18:00 +01:00
|
|
|
export const Auth = createParamDecorator((data, context: ExecutionContext): AuthDto => {
|
2024-05-09 13:58:44 -04:00
|
|
|
return context.switchToHttp().getRequest<AuthenticatedRequest>().user;
|
2023-07-01 14:27:34 -04:00
|
|
|
});
|
|
|
|
|
|
2023-12-12 09:58:25 -05:00
|
|
|
export const FileResponse = () =>
|
|
|
|
|
ApiOkResponse({
|
|
|
|
|
content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } },
|
|
|
|
|
});
|
|
|
|
|
|
2024-02-02 04:18:00 +01:00
|
|
|
export const GetLoginDetails = createParamDecorator((data, context: ExecutionContext): LoginDetails => {
|
|
|
|
|
const request = context.switchToHttp().getRequest<Request>();
|
|
|
|
|
const userAgent = UAParser(request.headers['user-agent']);
|
2023-07-01 14:27:34 -04:00
|
|
|
|
|
|
|
|
return {
|
2024-12-18 15:19:48 +01:00
|
|
|
clientIp: request.ip ?? '',
|
2024-02-02 04:18:00 +01:00
|
|
|
isSecure: request.secure,
|
|
|
|
|
deviceType: userAgent.browser.name || userAgent.device.type || (request.headers.devicemodel as string) || '',
|
|
|
|
|
deviceOS: userAgent.os.name || (request.headers.devicetype as string) || '',
|
2023-07-01 14:27:34 -04:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
export interface AuthRequest extends Request {
|
2023-12-09 23:34:12 -05:00
|
|
|
user?: AuthDto;
|
2023-07-01 14:27:34 -04:00
|
|
|
}
|
|
|
|
|
|
2024-05-02 15:42:26 -04:00
|
|
|
export interface AuthenticatedRequest extends Request {
|
|
|
|
|
user: AuthDto;
|
|
|
|
|
}
|
|
|
|
|
|
2023-07-01 14:27:34 -04:00
|
|
|
@Injectable()
|
2024-03-20 15:15:01 -05:00
|
|
|
export class AuthGuard implements CanActivate {
|
2023-08-28 14:41:57 -05:00
|
|
|
constructor(
|
2025-01-23 08:31:30 -05:00
|
|
|
private logger: LoggingRepository,
|
2023-08-28 14:41:57 -05:00
|
|
|
private reflector: Reflector,
|
|
|
|
|
private authService: AuthService,
|
2024-04-15 19:39:06 -04:00
|
|
|
) {
|
|
|
|
|
this.logger.setContext(AuthGuard.name);
|
|
|
|
|
}
|
2023-07-01 14:27:34 -04:00
|
|
|
|
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
2024-05-09 13:58:44 -04:00
|
|
|
const targets = [context.getHandler()];
|
2023-07-01 14:27:34 -04:00
|
|
|
|
2025-07-15 14:50:13 -04:00
|
|
|
const options = this.reflector.getAllAndOverride<AuthenticatedOptions | undefined>(MetadataKey.AuthRoute, targets);
|
2024-05-09 13:58:44 -04:00
|
|
|
if (!options) {
|
2023-07-01 14:27:34 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-16 09:48:43 -04:00
|
|
|
const {
|
|
|
|
|
admin: adminRoute,
|
|
|
|
|
sharedLink: sharedLinkRoute,
|
|
|
|
|
permission,
|
|
|
|
|
} = { sharedLink: false, admin: false, ...options };
|
2024-02-02 04:18:00 +01:00
|
|
|
const request = context.switchToHttp().getRequest<AuthRequest>();
|
2023-07-01 14:27:34 -04:00
|
|
|
|
2024-08-15 09:14:23 -04:00
|
|
|
request.user = await this.authService.authenticate({
|
|
|
|
|
headers: request.headers,
|
|
|
|
|
queryParams: request.query as Record<string, string>,
|
2024-08-16 09:48:43 -04:00
|
|
|
metadata: { adminRoute, sharedLinkRoute, permission, uri: request.path },
|
2024-08-15 09:14:23 -04:00
|
|
|
});
|
2023-07-01 14:27:34 -04:00
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|