Overhauled lockout process
This commit is contained in:
@@ -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> {},
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user