mirror of
https://github.com/immich-app/immich.git
synced 2024-12-22 01:47:08 +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:
parent
c028c7db4e
commit
be3e3e5d7e
@ -25,6 +25,6 @@ class ApiService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setAccessToken(String accessToken) {
|
setAccessToken(String accessToken) {
|
||||||
_apiClient.addDefaultHeader('Authorization', 'bearer $accessToken');
|
_apiClient.addDefaultHeader('Authorization', 'Bearer $accessToken');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Body, Controller, Post, UseGuards, ValidationPipe } from '@nestjs/common';
|
import { Body, Controller, Post, Res, UseGuards, ValidationPipe } from '@nestjs/common';
|
||||||
import {
|
import {
|
||||||
ApiBadRequestResponse,
|
ApiBadRequestResponse,
|
||||||
ApiBearerAuth,
|
ApiBearerAuth,
|
||||||
@ -15,15 +15,27 @@ import { LoginResponseDto } from './response-dto/login-response.dto';
|
|||||||
import { SignUpDto } from './dto/sign-up.dto';
|
import { SignUpDto } from './dto/sign-up.dto';
|
||||||
import { AdminSignupResponseDto } from './response-dto/admin-signup-response.dto';
|
import { AdminSignupResponseDto } from './response-dto/admin-signup-response.dto';
|
||||||
import { ValidateAccessTokenResponseDto } from './response-dto/validate-asset-token-response.dto,';
|
import { ValidateAccessTokenResponseDto } from './response-dto/validate-asset-token-response.dto,';
|
||||||
|
import { Response } from 'express';
|
||||||
@ApiTags('Authentication')
|
@ApiTags('Authentication')
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
@Post('/login')
|
@Post('/login')
|
||||||
async login(@Body(ValidationPipe) loginCredential: LoginCredentialDto): Promise<LoginResponseDto> {
|
async login(
|
||||||
return await this.authService.login(loginCredential);
|
@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')
|
@Post('/admin-sign-up')
|
||||||
|
@ -63,6 +63,12 @@ export class AuthService {
|
|||||||
return mapLoginResponse(validatedUser, accessToken);
|
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> {
|
public async adminSignUp(signUpCredential: SignUpDto): Promise<AdminSignupResponseDto> {
|
||||||
const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } });
|
const adminUser = await this.userRepository.findOne({ where: { isAdmin: true } });
|
||||||
|
|
||||||
|
@ -3,5 +3,5 @@ import { jwtSecret } from '../constants/jwt.constant';
|
|||||||
|
|
||||||
export const jwtConfig: JwtModuleOptions = {
|
export const jwtConfig: JwtModuleOptions = {
|
||||||
secret: jwtSecret,
|
secret: jwtSecret,
|
||||||
signOptions: { expiresIn: '36500d' },
|
signOptions: { expiresIn: '7d' },
|
||||||
};
|
};
|
||||||
|
@ -3,6 +3,7 @@ import { Logger } from '@nestjs/common';
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerDocumentOptions, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import cookieParser from 'cookie-parser';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
@ -12,7 +13,7 @@ async function bootstrap() {
|
|||||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||||
|
|
||||||
app.set('trust proxy');
|
app.set('trust proxy');
|
||||||
|
app.use(cookieParser());
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
}
|
}
|
||||||
@ -25,7 +26,7 @@ async function bootstrap() {
|
|||||||
.setVersion('1.17.0')
|
.setVersion('1.17.0')
|
||||||
.addBearerAuth({
|
.addBearerAuth({
|
||||||
type: 'http',
|
type: 'http',
|
||||||
scheme: 'bearer',
|
scheme: 'Bearer',
|
||||||
bearerFormat: 'JWT',
|
bearerFormat: 'JWT',
|
||||||
name: 'JWT',
|
name: 'JWT',
|
||||||
description: 'Enter JWT token',
|
description: 'Enter JWT token',
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Injectable, Logger } from '@nestjs/common';
|
import { Injectable, Logger } from '@nestjs/common';
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { Request } from 'express';
|
||||||
import { JwtPayloadDto } from '../../api-v1/auth/dto/jwt-payload.dto';
|
import { JwtPayloadDto } from '../../api-v1/auth/dto/jwt-payload.dto';
|
||||||
import { jwtSecret } from '../../constants/jwt.constant';
|
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 { JwtPayloadDto } from '../../../api-v1/auth/dto/jwt-payload.dto';
|
||||||
import { UserEntity } from '@app/database/entities/user.entity';
|
import { UserEntity } from '@app/database/entities/user.entity';
|
||||||
import { jwtSecret } from '../../../constants/jwt.constant';
|
import { jwtSecret } from '../../../constants/jwt.constant';
|
||||||
|
import { ImmichJwtService } from '../immich-jwt.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(UserEntity)
|
@InjectRepository(UserEntity)
|
||||||
private usersRepository: Repository<UserEntity>,
|
private usersRepository: Repository<UserEntity>,
|
||||||
|
|
||||||
|
private immichJwtService: ImmichJwtService,
|
||||||
) {
|
) {
|
||||||
super({
|
super({
|
||||||
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
jwtFromRequest: ExtractJwt.fromExtractors([
|
||||||
|
immichJwtService.extractJwtFromHeader,
|
||||||
|
immichJwtService.extractJwtFromCookie,
|
||||||
|
]),
|
||||||
ignoreExpiration: false,
|
ignoreExpiration: false,
|
||||||
secretOrKey: jwtSecret,
|
secretOrKey: jwtSecret,
|
||||||
});
|
});
|
||||||
|
File diff suppressed because one or more lines are too long
56
server/package-lock.json
generated
56
server/package-lock.json
generated
@ -30,6 +30,7 @@
|
|||||||
"bull": "^4.4.0",
|
"bull": "^4.4.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"diskusage": "^1.1.3",
|
"diskusage": "^1.1.3",
|
||||||
"dotenv": "^14.2.0",
|
"dotenv": "^14.2.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
@ -56,6 +57,7 @@
|
|||||||
"@openapitools/openapi-generator-cli": "2.5.1",
|
"@openapitools/openapi-generator-cli": "2.5.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.7",
|
"@types/bull": "^3.15.7",
|
||||||
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/fluent-ffmpeg": "^2.1.20",
|
"@types/fluent-ffmpeg": "^2.1.20",
|
||||||
@ -2412,6 +2414,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/cookie-parser": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/cookiejar": {
|
"node_modules/@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
@ -4401,6 +4412,26 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
@ -13280,6 +13311,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
|
||||||
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
|
||||||
},
|
},
|
||||||
|
"@types/cookie-parser": {
|
||||||
|
"version": "1.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz",
|
||||||
|
"integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/cookiejar": {
|
"@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
@ -14908,6 +14948,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
|
||||||
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw=="
|
||||||
},
|
},
|
||||||
|
"cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
|
@ -49,6 +49,7 @@
|
|||||||
"bull": "^4.4.0",
|
"bull": "^4.4.0",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"diskusage": "^1.1.3",
|
"diskusage": "^1.1.3",
|
||||||
"dotenv": "^14.2.0",
|
"dotenv": "^14.2.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
@ -72,8 +73,10 @@
|
|||||||
"@nestjs/cli": "^8.2.8",
|
"@nestjs/cli": "^8.2.8",
|
||||||
"@nestjs/schematics": "^8.0.11",
|
"@nestjs/schematics": "^8.0.11",
|
||||||
"@nestjs/testing": "^8.4.7",
|
"@nestjs/testing": "^8.4.7",
|
||||||
|
"@openapitools/openapi-generator-cli": "2.5.1",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/bull": "^3.15.7",
|
"@types/bull": "^3.15.7",
|
||||||
|
"@types/cookie-parser": "^1.4.3",
|
||||||
"@types/cron": "^2.0.0",
|
"@types/cron": "^2.0.0",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/fluent-ffmpeg": "^2.1.20",
|
"@types/fluent-ffmpeg": "^2.1.20",
|
||||||
@ -88,7 +91,6 @@
|
|||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
"@typescript-eslint/parser": "^5.0.0",
|
"@typescript-eslint/parser": "^5.0.0",
|
||||||
"@openapitools/openapi-generator-cli": "2.5.1",
|
|
||||||
"eslint": "^8.0.1",
|
"eslint": "^8.0.1",
|
||||||
"eslint-config-prettier": "^8.3.0",
|
"eslint-config-prettier": "^8.3.0",
|
||||||
"eslint-plugin-prettier": "^4.0.0",
|
"eslint-plugin-prettier": "^4.0.0",
|
||||||
|
@ -6,7 +6,7 @@ import {
|
|||||||
Configuration,
|
Configuration,
|
||||||
DeviceInfoApi,
|
DeviceInfoApi,
|
||||||
ServerInfoApi,
|
ServerInfoApi,
|
||||||
UserApi,
|
UserApi
|
||||||
} from './open-api';
|
} from './open-api';
|
||||||
|
|
||||||
class ImmichApi {
|
class ImmichApi {
|
||||||
|
Loading…
Reference in New Issue
Block a user