conceptadev / conceptadev/rockets

PATCH /me 400s on every request when userMetadata updateDto has no class-validator metadata

Open
#103 0 comments 0 reactions 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.