conceptadev / conceptadev/rockets
PATCH /me 400s on every request when userMetadata updateDto has no class-validator metadata
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 2
- Avg merge
- 2d 10h
- Merged PRs (30d)
- 23
Description
## Summary
`MeController.updateUser` (`packages/rockets-server/src/gateways/http/me.controller.ts`) returns **`400` for every request** when the configured `userMetadata.updateDto` carries no class-validator metadata — including a request with an empty body — and leaks an internal validator string to the client.
`RocketsAuthUserMetadataDto` is exactly such a DTO. It is the documented base extension point ("implementation-specific fields should be defined in extending classes") and the default in `rockets-server-auth`'s own e2e helper. **An auth app that never subclasses it has no working `PATCH /me` at all.**
## Reproduction
Boot the standard auth e2e app (`createRocketsAuthStandardE2eTestingModule`, whose `userMetadata.updateDto` defaults to `RocketsAuthUserMetadataDto`), authenticate, then:
```
PATCH /me {} -> 400
PATCH /me {"userMetadata":{}} -> 400
PATCH /me {"userMetadata":{"firstName":"Ana"}} -> 400
```
All three:
```json
{
"statusCode": 400,
"errorCode": "HTTP_BAD_REQUEST",
"message": ["an unknown value was passed to the validate function"],
"timestamp": "..."
}
```
Verified at the util level too, against the built `dist` and the real class:
```
whitelistedFromDto(RocketsAuthUserMetadataDto, {})
→ THREW 400 ["an unknown value was passed to the validate function"]
```
## Root cause
`whitelistedFromDto` (`packages/rockets-core/src/common/utils/whitelisted-from-dto.util.ts`) validates with:
```typescript
const errors = await validate(instance as object, {
whitelist: true,
forbidNonWhitelisted: false,
forbidUnknownValues: true, // <—
skipMissingProperties: true,
});
```
class-validator 0.14.x treats a target with **zero registered constraints** as an "unknown value" under `forbidUnknownValues: true` and fails it wholesale. `RocketsAuthUserMetadataDto` has `@Expose()` / `@ApiProperty()` only — no class-validator metadata — so it always trips this.
`MeController`'s `?? {}` fallback does not help: `{}` hits the same wall, because the rejection is about the *target class*, not the payload.
## Why the suite was green
- Every `/me` spec in `rockets-server` (`rockets-me-validation.e2e-spec.ts`, `__e2e__/user-metadata.e2e-spec.ts`, …) supplies a DTO with real class-validator decorators.
- `rockets-server-auth` had **no `PATCH /me` coverage at all** until PR #93.
So no test ever combined "the default base DTO" with "this endpoint".
## Second call site
`packages/rockets-server-auth/src/domains/invitation/application/listeners/invitation-user-acceptance.listener.ts` calls the same helper with `config.userMetadataUpdateDto`. There the throw is swallowed (event published from an `onCommit` callback flushed with `Promise.allSettled`; `AggregateRoot.commit()` is a synchronous `void`; `EventBus.bind` catches; the listener catches), so invitation acceptance returns **200 with the metadata silently unwritten**.
## Possible fixes (not chosen — needs a maintainer decision)
1. `whitelistedFromDto` treats a metadata-less DTO as pass-through-with-whitelist rather than an error (most consumer-friendly; weakens the "unknown value" guard).
2. `@Allow()`-stamp the base DTO's declared keys, the way `allowStandardSchemaKeys` does for schema DTOs (#83) — targeted, keeps `forbidUnknownValues` honest.
3. `defineRocketsAuth` / the metadata config rejects an undecorated `updateDto` at boot (fail-fast; turns a runtime 400 into a boot error, but breaks apps relying on the base today).
Whichever is chosen, the leaked internal string `"an unknown value was passed to the validate function"` should not reach clients.
## Current state
Pinned by a regression test asserting the **broken** behaviour, in `packages/rockets-server-auth/src/__e2e__/rockets-auth-error-details.e2e-spec.ts` (`DEFECT #94: PATCH /me 400s on every payload with the default metadata DTO`), following the same "pin the trap" pattern used for the #83 whitelist hazard. Fixing this issue **should** fail that test — update it deliberately at that point.
Found while adding real-path error-details coverage in #93.
Contributor guide
Research direction
Start with packages/rockets-core/src/common/utils/whitelisted-from-dto.util.ts and MeController.updateUser in packages/rockets-server/src/gateways/http/me.controller.ts, then review the listed metadata-less DTO reproduction. Run packages/rockets-server-auth/src/__e2e__/rockets-auth-error-details.e2e-spec.ts; done means the default PATCH /me cases no longer fail wholesale and the internal validator string is not exposed, with the regression test updated deliberately.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, testing
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100