You've already forked immich
mirror of
https://github.com/immich-app/immich.git
synced 2025-08-09 23:17:29 +02:00
Added Cookie Authentication (#360)
* Added Cookie Authentication * Fixed issue with bearer is in lower case * Fixed bearer to Bearer to conform with standard
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { Body, Controller, Post, UseGuards, ValidationPipe } from '@nestjs/common';
|
||||
import { Body, Controller, Post, Res, UseGuards, ValidationPipe } from '@nestjs/common';
|
||||
import {
|
||||
ApiBadRequestResponse,
|
||||
ApiBearerAuth,
|
||||
@@ -15,15 +15,27 @@ import { LoginResponseDto } from './response-dto/login-response.dto';
|
||||
import { SignUpDto } from './dto/sign-up.dto';
|
||||
import { AdminSignupResponseDto } from './response-dto/admin-signup-response.dto';
|
||||
import { ValidateAccessTokenResponseDto } from './response-dto/validate-asset-token-response.dto,';
|
||||
|
||||
import { Response } from 'express';
|
||||
@ApiTags('Authentication')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('/login')
|
||||
async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto): Promise<LoginResponseDto> {
|
||||
return await this.authService.login(loginCredential);
|
||||
async login(
|
||||
@Body(ValidationPipe) loginCredential: LoginCredentialDto,
|
||||
@Res() response: Response,
|
||||
): Promise<LoginResponseDto> {
|
||||
const loginResponse = await this.authService.login(loginCredential);
|
||||
|
||||
// Set Cookies
|
||||
const accessTokenCookie = this.authService.getCookieWithJwtToken(loginResponse);
|
||||
const isAuthCookie = `immich_is_authenticated=true; Path=/; Max-Age=${7 * 24 * 3600}`;
|
||||
|
||||
response.setHeader('Set-Cookie', [accessTokenCookie, isAuthCookie]);
|
||||
response.send(loginResponse);
|
||||
|
||||
return loginResponse;
|
||||
}
|
||||
|
||||
@Post('/admin-sign-up')
|
||||
|
@@ -63,6 +63,12 @@ export class AuthService {
|
||||
return mapLoginResponse(validatedUser, accessToken);
|
||||
}
|
||||
|
||||
public getCookieWithJwtToken(authLoginInfo: LoginResponseDto) {
|
||||
const maxAge = 7 * 24 * 3600; // 7 days
|
||||
return `immich_access_token=${authLoginInfo.accessToken}; HttpOnly; Path=/; Max-Age=${maxAge}`;
|
||||
}
|
||||
|
||||
// !TODO: refactor this method to use the userService createUser method
|
||||
public async adminSignUp(signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
|
||||
const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } });
|
||||
|
||||
|
@@ -3,5 +3,5 @@ import { jwtSecret } from '../constants/jwt.constant';
|
||||
|
||||
export const jwtConfig: JwtModuleOptions = {
|
||||
secret: jwtSecret,
|
||||
signOptions: { expiresIn: '36500d' },
|
||||
signOptions: { expiresIn: '7d' },
|
||||
};
|
||||
|
@@ -3,6 +3,7 @@ import { Logger } from '@nestjs/common';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
|
||||
import cookieParser from 'cookie-parser';
|
||||
import { writeFileSync } from 'fs';
|
||||
import path from 'path';
|
||||
import { AppModule } from './app.module';
|
||||
@@ -12,7 +13,7 @@ async function bootstrap() {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||
|
||||
app.set('trust proxy');
|
||||
|
||||
app.use(cookieParser());
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
app.enableCors();
|
||||
}
|
||||
@@ -25,7 +26,7 @@ async function bootstrap() {
|
||||
.setVersion('1.17.0')
|
||||
.addBearerAuth({
|
||||
type: 'http',
|
||||
scheme: 'bearer',
|
||||
scheme: 'Bearer',
|
||||
bearerFormat: 'JWT',
|
||||
name: 'JWT',
|
||||
description: 'Enter JWT token',
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import { Request } from 'express';
|
||||
import { JwtPayloadDto } from '../../api-v1/auth/dto/jwt-payload.dto';
|
||||
import { jwtSecret } from '../../constants/jwt.constant';
|
||||
|
||||
@@ -33,4 +34,24 @@ export class ImmichJwtService {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public extractJwtFromHeader(req: Request) {
|
||||
if (
|
||||
req.headers.authorization &&
|
||||
(req.headers.authorization.split(' ')[0] === 'Bearer' || req.headers.authorization.split(' ')[0] === 'bearer')
|
||||
) {
|
||||
const accessToken = req.headers.authorization.split(' ')[1];
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public extractJwtFromCookie(req: Request) {
|
||||
if (req.cookies?.immich_access_token) {
|
||||
return req.cookies.immich_access_token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -6,15 +6,21 @@ import { Repository } from 'typeorm';
|
||||
import { JwtPayloadDto } from '../../../api-v1/auth/dto/jwt-payload.dto';
|
||||
import { UserEntity } from '@app/database/entities/user.entity';
|
||||
import { jwtSecret } from '../../../constants/jwt.constant';
|
||||
import { ImmichJwtService } from '../immich-jwt.service';
|
||||
|
||||
@Injectable()
|
||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
constructor(
|
||||
@InjectRepository(UserEntity)
|
||||
private usersRepository: Repository<UserEntity>,
|
||||
|
||||
private immichJwtService: ImmichJwtService,
|
||||
) {
|
||||
super({
|
||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||
jwtFromRequest: ExtractJwt.fromExtractors([
|
||||
immichJwtService.extractJwtFromHeader,
|
||||
immichJwtService.extractJwtFromCookie,
|
||||
]),
|
||||
ignoreExpiration: false,
|
||||
secretOrKey: jwtSecret,
|
||||
});
|
||||
|
Reference in New Issue
Block a user