refactor(server): zod int validation (#28804)

main
Timon 2026-06-04 00:21:07 +02:00 committed by GitHub
parent d21cb28526
commit 2190aa72a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 18 additions and 11 deletions

View File

@ -24,7 +24,7 @@ import { DB } from 'src/schema';
import { immich_uuid_v7 } from 'src/schema/functions'; import { immich_uuid_v7 } from 'src/schema/functions';
import { ExtensionVersion, VectorExtension } from 'src/types'; import { ExtensionVersion, VectorExtension } from 'src/types';
import { vectorIndexQuery } from 'src/utils/database'; import { vectorIndexQuery } from 'src/utils/database';
import { isValidInteger } from 'src/validation'; import z from 'zod';
export let cachedVectorExtension: VectorExtension | undefined; export let cachedVectorExtension: VectorExtension | undefined;
export async function getVectorExtension(runner: Kysely<DB>): Promise<VectorExtension> { export async function getVectorExtension(runner: Kysely<DB>): Promise<VectorExtension> {
@ -292,7 +292,13 @@ export class DatabaseRepository {
`.execute(this.db); `.execute(this.db);
const dimSize = rows[0]?.dimsize; const dimSize = rows[0]?.dimsize;
if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { if (
!z
.int()
.min(1)
.max(2 ** 16)
.safeParse(dimSize).success
) {
this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`); this.logger.warn(`Could not retrieve dimension size of column '${column}' in table '${table}', assuming 512`);
return 512; return 512;
} }
@ -300,7 +306,13 @@ export class DatabaseRepository {
} }
async setDimensionSize(dimSize: number): Promise<void> { async setDimensionSize(dimSize: number): Promise<void> {
if (!isValidInteger(dimSize, { min: 1, max: 2 ** 16 })) { if (
!z
.int()
.min(1)
.max(2 ** 16)
.safeParse(dimSize).success
) {
throw new Error(`Invalid CLIP dimension size: ${dimSize}`); throw new Error(`Invalid CLIP dimension size: ${dimSize}`);
} }

View File

@ -8,7 +8,7 @@ import { DB } from 'src/schema';
import { AssetExifTable } from 'src/schema/tables/asset-exif.table'; import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
import { anyUuid, searchAssetBuilder, withExifInner } from 'src/utils/database'; import { anyUuid, searchAssetBuilder, withExifInner } from 'src/utils/database';
import { paginationHelper } from 'src/utils/pagination'; import { paginationHelper } from 'src/utils/pagination';
import { isValidInteger } from 'src/validation'; import z from 'zod';
export interface SearchAssetIdOptions { export interface SearchAssetIdOptions {
checksum?: Buffer; checksum?: Buffer;
@ -278,7 +278,7 @@ export class SearchRepository {
], ],
}) })
searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions) { searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions) {
if (!isValidInteger(pagination.size, { min: 1, max: 1000 })) { if (!z.int().min(1).max(1000).safeParse(pagination.size).success) {
throw new Error(`Invalid value for 'size': ${pagination.size}`); throw new Error(`Invalid value for 'size': ${pagination.size}`);
} }
@ -313,7 +313,7 @@ export class SearchRepository {
], ],
}) })
searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) { searchFaces({ userIds, embedding, numResults, maxDistance, hasPerson, minBirthDate }: FaceEmbeddingSearch) {
if (!isValidInteger(numResults, { min: 1, max: 1000 })) { if (!z.int().min(1).max(1000).safeParse(numResults).success) {
throw new Error(`Invalid value for 'numResults': ${numResults}`); throw new Error(`Invalid value for 'numResults': ${numResults}`);
} }

View File

@ -125,11 +125,6 @@ const FilenameParamSchema = z.object({
export class FilenameParamDto extends createZodDto(FilenameParamSchema) {} export class FilenameParamDto extends createZodDto(FilenameParamSchema) {}
export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => {
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options;
return Number.isInteger(value) && value >= min && value <= max;
};
/** /**
* Unified email validation * Unified email validation
* Converts email strings to lowercase and validates against HTML5 email regex * Converts email strings to lowercase and validates against HTML5 email regex