ci/woodpecker/push/publish Pipeline was successful
Co-authored-by: code-infra-01 <[email protected]>
95 lines
3.9 KiB
TypeScript
95 lines
3.9 KiB
TypeScript
import 'reflect-metadata';
|
|
import { getMetadataStorage } from 'class-validator';
|
|
import { BootstrapSetupDto } from './admin/bootstrap.dto.js';
|
|
|
|
/**
|
|
* Boot-time self-check: the global ValidationPipe must be able to SEE the
|
|
* decorated properties of the DTOs it guards (#1391, #436 class).
|
|
*
|
|
* WHY THIS EXISTS. When Nest resolves a @Body() metatype to Object — via
|
|
* `import type` class erasure (#436), or a dependency graph where the
|
|
* controller's decorators and the application's route enhancers disagree
|
|
* (#1391's hypothesized dual-@nestjs/common on a mixed install) — the
|
|
* ValidationPipe's whitelist treats every property as forbidden. The first
|
|
* symptom is a 400 on the FIRST bootstrap attempt of a fresh install, the
|
|
* worst place to discover wiring damage: the operator cannot tell a broken
|
|
* payload from a broken daemon.
|
|
*
|
|
* This check fails LOUD at boot instead: if the pipe cannot see the DTO's
|
|
* decorated properties, the gateway refuses to start with a named cause.
|
|
* It catches the whole class — erasure, decorator metadata loss — on every
|
|
* host, at the moment the damage exists rather than at first use.
|
|
*
|
|
* Storage sharing note: class-validator keys its metadata storage on
|
|
* globalThis, so duplicate package copies do NOT hide metadata (measured,
|
|
* #1391 diagnosis). What hides it is losing the metatype itself, which is
|
|
* what this asserts against.
|
|
*/
|
|
|
|
/**
|
|
* DTOs the global pipe guards, mapped to the properties the whitelist must
|
|
* admit. Target is the CONSTRUCTOR (the object class itself): class-validator
|
|
* decorators register metadata keyed on the constructor, and its executor
|
|
* looks up `object.constructor` (ValidationExecutor.js:50) — the probe
|
|
* through `prototype` returns zero. Extend when adding DTOs to the app.
|
|
*/
|
|
export const PIPE_GUARDED_DTOS: Array<{
|
|
name: string;
|
|
target: abstract new (...args: never[]) => unknown;
|
|
properties: string[];
|
|
}> = [
|
|
{
|
|
name: 'BootstrapSetupDto',
|
|
target: BootstrapSetupDto,
|
|
properties: ['name', 'email', 'password'],
|
|
},
|
|
];
|
|
|
|
export class PipeMetatypeCheckError extends Error {
|
|
constructor(missing: string[]) {
|
|
super(
|
|
'ValidationPipe metatype check failed: ' +
|
|
missing.join('; ') +
|
|
'. The global ValidationPipe cannot see decorated DTO properties — ' +
|
|
'every request body would be rejected as non-whitelisted. ' +
|
|
'Check for import-type erasure or decorator metadata loss in the ' +
|
|
'dependency graph (see issues #436, #1391).',
|
|
);
|
|
this.name = 'PipeMetatypeCheckError';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assert the pipe's whitelist can see every guarded DTO's decorated
|
|
* properties. Throws PipeMetatypeCheckError (fail-loud at boot) listing
|
|
* each miss. Pure function of module state: no I/O, safe to call twice.
|
|
*/
|
|
export function assertValidationPipeSeesDtoDecorators(): void {
|
|
const storage = getMetadataStorage();
|
|
const missing: string[] = [];
|
|
|
|
for (const dto of PIPE_GUARDED_DTOS) {
|
|
// class-validator records constraints keyed on the DTO's constructor
|
|
// (decorators run on the class), and its executor resolves them via
|
|
// object.constructor. A property with no recorded metadata is invisible
|
|
// to the whitelist — whatever the cause — and fails here.
|
|
// Signature mirrors ValidationExecutor.js:50 — (constructor, schema, always,
|
|
// strictGroups, groups?). No schema, always=true, no groups: every
|
|
// constraint regardless of grouping, which is what the whitelist sees.
|
|
const metadatas = storage.getTargetValidationMetadatas(dto.target, '', true, false);
|
|
const decorated = new Set(metadatas.map((m) => m.propertyName));
|
|
|
|
for (const property of dto.properties) {
|
|
if (!decorated.has(property)) {
|
|
missing.push(
|
|
`${dto.name}.${property} has no class-validator constraints visible to the pipe`,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (missing.length > 0) {
|
|
throw new PipeMetatypeCheckError(missing);
|
|
}
|
|
}
|