refactor: always validate login (i.e. check cookie)

This commit is contained in:
izzy 2025-11-06 17:50:48 +00:00
parent 35ac0b3269
commit a597b0edb1
No known key found for this signature in database
GPG key ID: 5059F398521BB0F6
7 changed files with 31 additions and 63 deletions

View file

@ -13,10 +13,16 @@ part of openapi.api;
class MaintenanceLoginDto {
/// Returns a new [MaintenanceLoginDto] instance.
MaintenanceLoginDto({
required this.token,
this.token,
});
String token;
///
/// Please note: This property should have been non-nullable! Since the specification file
/// does not include a default value (using the "default:" property), however, the generated
/// source code must fall back to having a nullable type.
/// Consider adding a "default:" property in the specification file to hide this note.
///
String? token;
@override
bool operator ==(Object other) => identical(this, other) || other is MaintenanceLoginDto &&
@ -25,14 +31,18 @@ class MaintenanceLoginDto {
@override
int get hashCode =>
// ignore: unnecessary_parenthesis
(token.hashCode);
(token == null ? 0 : token!.hashCode);
@override
String toString() => 'MaintenanceLoginDto[token=$token]';
Map<String, dynamic> toJson() {
final json = <String, dynamic>{};
if (this.token != null) {
json[r'token'] = this.token;
} else {
// json[r'token'] = null;
}
return json;
}
@ -45,7 +55,7 @@ class MaintenanceLoginDto {
final json = value.cast<String, dynamic>();
return MaintenanceLoginDto(
token: mapValueOfType<String>(json, r'token')!,
token: mapValueOfType<String>(json, r'token'),
);
}
return null;
@ -93,7 +103,6 @@ class MaintenanceLoginDto {
/// The list of required keys that must be present in a JSON.
static const requiredKeys = <String>{
'token',
};
}

View file

@ -248,13 +248,6 @@
"parameters": [],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MaintenanceModeResponseDto"
}
}
},
"description": ""
}
},
@ -314,13 +307,6 @@
"parameters": [],
"responses": {
"201": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/MaintenanceModeResponseDto"
}
}
},
"description": ""
}
},
@ -12695,17 +12681,6 @@
},
"type": "object"
},
"MaintenanceModeResponseDto": {
"properties": {
"isMaintenanceMode": {
"type": "boolean"
}
},
"required": [
"isMaintenanceMode"
],
"type": "object"
},
"ManualJobName": {
"enum": [
"person-cleanup",

View file

@ -44,7 +44,7 @@ export type MaintenanceModeResponseDto = {
isMaintenanceMode: boolean;
};
export type MaintenanceLoginDto = {
token: string;
token?: string;
};
export type MaintenanceAuthDto = {
username: string;

View file

@ -23,8 +23,9 @@ export class MaintenanceWorkerController {
@Body() dto: MaintenanceLoginDto,
@Res({ passthrough: true }) response: Response,
): Promise<MaintenanceAuthDto> {
const auth = await this.service.login(dto.token ?? request.cookies[ImmichCookie.MaintenanceToken]);
response.cookie(ImmichCookie.MaintenanceToken, dto.token);
const token = dto.token ?? request.cookies[ImmichCookie.MaintenanceToken];
const auth = await this.service.login(token);
response.cookie(ImmichCookie.MaintenanceToken, token);
return auth;
}

View file

@ -2,7 +2,7 @@ import { BadRequestException, Body, Controller, Post, Res } from '@nestjs/common
import { ApiTags } from '@nestjs/swagger';
import { Response } from 'express';
import { AuthDto } from 'src/dtos/auth.dto';
import { MaintenanceAuthDto, MaintenanceLoginDto, MaintenanceModeResponseDto } from 'src/dtos/maintenance.dto';
import { MaintenanceAuthDto, MaintenanceLoginDto } from 'src/dtos/maintenance.dto';
import { ImmichCookie, Permission } from 'src/enum';
import { Auth, Authenticated } from 'src/middleware/auth.guard';
import { MaintenanceRepository } from 'src/repositories/maintenance.repository';
@ -20,22 +20,18 @@ export class MaintenanceController {
@Post('start')
@Authenticated({ permission: Permission.Maintenance, admin: true })
async startMaintenance(
@Auth() auth: AuthDto,
@Res({ passthrough: true }) response: Response,
): Promise<MaintenanceModeResponseDto> {
async startMaintenance(@Auth() auth: AuthDto, @Res({ passthrough: true }) response: Response): Promise<void> {
const { secret } = await this.service.startMaintenance();
const jwt = await MaintenanceRepository.createJwt(secret, {
username: auth.user.name,
});
response.cookie(ImmichCookie.MaintenanceToken, jwt);
return { isMaintenanceMode: true };
}
@Post('end')
@Authenticated({ permission: Permission.Maintenance, admin: true })
endMaintenance(): Promise<MaintenanceModeResponseDto> {
endMaintenance(): void {
throw new BadRequestException('Not in maintenance mode');
}
}

View file

@ -6,7 +6,7 @@ export class MaintenanceModeResponseDto {
}
export class MaintenanceLoginDto {
@ValidateString()
@ValidateString({ optional: true })
token?: string;
}

View file

@ -20,31 +20,18 @@ export function maintenanceShouldRedirect(maintenanceMode: boolean, currentUrl:
export const loadMaintenanceAuth = async () => {
try {
const maintenanceAuth = get(maintenanceAuth$);
const query = new URLSearchParams(location.search);
const queryToken = query.get('token');
if (!maintenanceAuth || queryToken) {
const cookie = document.cookie
.split(';')
.map((cookie) => cookie.split('=', 2).map((value) => value.trim()))
.find(([name]) => name === 'immich_maintenance_token');
try {
const auth = await maintenanceLogin({
maintenanceLoginDto: {
token: query.get('token') ?? undefined,
},
});
const token = queryToken ?? cookie?.[1];
if (token) {
try {
const auth = await maintenanceLogin({
maintenanceLoginDto: {
token,
},
});
maintenanceAuth$.set(auth);
} catch (error) {
void error;
}
}
maintenanceAuth$.set(auth);
} catch (error) {
void error;
}
return maintenanceAuth;