Overhauled lockout process

This commit is contained in:
2026-10-24 17:36:30 +02:00
parent a731c979bb
commit 25e251aa79
12 changed files with 574 additions and 527 deletions
@@ -192,3 +192,138 @@ export function decodeEntityId(hashId: string | number | null | undefined): stri
return str;
}
/**
* Checks whether an incoming string is already a valid Hashid matching the configured salt and min length.
* Prevents double-encoding already-hashed route parameters or IDs.
*/
export function isAlreadyHashId(candidate: string | null | undefined): boolean {
if (
!candidate ||
typeof candidate !== 'string' ||
candidate.length < HASHER_SETTINGS.minHashLength ||
/^\d+$/.test(candidate)
) {
return false;
}
try {
const decoded = hasher.decode(candidate);
if (!Array.isArray(decoded) || decoded.length === 0) {
return false;
}
return hasher.encode(decoded as number[]) === candidate;
} catch {
return false;
}
}
/**
* Deterministically encodes a numeric ID or numeric string into a HashID string.
* Synchronous and does not perform HTTP network round trips.
*/
export function encodeHashIdSync(id: number | string | bigint | null | undefined): string {
return encodeEntityId(id);
}
/**
* Deterministically decodes a HashID string back to original numeric ID or string.
* Synchronous and does not perform HTTP network round trips.
*/
export function decodeHashIdSync(hashId: string | number | null | undefined): number | string {
if (hashId === undefined || hashId === null || hashId === '') return '';
if (typeof hashId === 'number') return hashId;
const str = String(hashId).trim();
if (!str) return '';
if (/^-?\d+$/.test(str)) {
const parsed = parseInt(str, 10);
return isNaN(parsed) ? str : parsed;
}
try {
const decoded = hasher.decode(str);
if (Array.isArray(decoded) && decoded.length > 0) {
return Number(decoded[0]);
}
} catch {}
return str;
}
/**
* Decodes a HashID or numeric value strictly to a canonical integer.
* Useful for request payloads expecting int64 integers.
*/
export function decodeToNumberSync(hashId: number | string | null | undefined): number {
if (hashId === null || hashId === undefined || hashId === '') return 0;
if (typeof hashId === 'number') {
return isNaN(hashId) ? 0 : Math.trunc(hashId);
}
const str = String(hashId).trim();
if (/^-?\d+$/.test(str)) {
const parsed = parseInt(str, 10);
return isNaN(parsed) ? 0 : parsed;
}
try {
const decoded = hasher.decode(str);
if (Array.isArray(decoded) && decoded.length > 0) {
return Number(decoded[0]);
}
} catch {}
const fallback = Number(str);
return isNaN(fallback) ? 0 : Math.trunc(fallback);
}
/**
* Legacy service resolver facade for compatibility with API endpoints and monitoring tables.
*/
export const toolsService = {
encodeIdSync(id: number | string | bigint | null | undefined): string {
return encodeHashIdSync(id);
},
decodeIdSync(hashId: string | number | null | undefined): number | string {
return decodeHashIdSync(hashId);
},
decodeToNumberSync(hashId: number | string | null | undefined): number {
return decodeToNumberSync(hashId);
},
async encodeId(id: number | string | bigint | null | undefined): Promise<string> {
return encodeHashIdSync(id);
},
async decodeId(hashId: string | number | null | undefined): Promise<number | string> {
return decodeHashIdSync(hashId);
},
async resolveProjectId(projectId: number | string): Promise<string> {
return encodeHashIdSync(projectId);
},
async resolveOrganisationId(organisationId: number | string): Promise<string> {
return encodeHashIdSync(organisationId);
},
async resolveProgrammeId(programmeId: number | string): Promise<string> {
return encodeHashIdSync(programmeId);
},
async resolvePortfolioId(portfolioId: number | string): Promise<string> {
return encodeHashIdSync(portfolioId);
},
async resolveAwardId(awardId: number | string): Promise<string> {
return encodeHashIdSync(awardId);
},
async resolveContractId(contractId: number | string): Promise<string> {
return encodeHashIdSync(contractId);
},
async resolveHashedParams<T extends Record<string, any>>(
params: T,
keysToHash: (keyof T)[]
): Promise<T> {
const resolved: any = { ...params };
for (const key of keysToHash) {
const val = params[key];
if (val !== undefined && val !== null) {
resolved[key] = encodeHashIdSync(val);
}
}
return resolved;
},
async primeCache(): Promise<void> {},
};