fix: close admin user security gaps
This commit is contained in:
@@ -120,3 +120,70 @@ Every production behavior above was added only after the corresponding expected
|
||||
- The lock/concurrency and migration tests are focused unit/SQL-shape tests; no live PostgreSQL instance was available for a two-connection race test or an actual migration run/revert. A database-backed integration test remains advisable before production rollout.
|
||||
- Removing superseded generic user CRUD/read routes and `DELETE auth/me` is intentionally security-hardening and may affect undocumented external clients. Repository frontend searches showed no use of those removed routes.
|
||||
- Full unrelated backend test-suite repair was intentionally out of scope; the focused Task 1 + Task 2 suite and backend build are green.
|
||||
|
||||
## Fix Round 1
|
||||
|
||||
### Review findings addressed
|
||||
|
||||
- Removed `linkPlayerId` from the validated public registration DTO and from internal create DTO plumbing. `AuthController.register` now has a concrete `AuthRegisterLoginDto` body rather than `any`, `AuthService.register` copies only the four permitted registration fields, and the obsolete `UsersService.linkPlayerToUserId` path was removed. Only `AdminUsersService` now changes `Player.user`.
|
||||
- Rebuilt existing-account social login around one database transaction. Candidate user rows are locked, any email change uses a narrow repository update, the user is reloaded with current role/status under an alias-scoped row lock, inactive state is rechecked, and only then is the JWT signed. The same method covers Facebook, Google, Twitter, and Apple.
|
||||
- Restricted `GET users/:id/teams` to the authenticated user's own ID. The query now returns an explicit minimal projection containing only player ID/name and team ID/name, matching the fields consumed by the current modern team selector.
|
||||
- Removed body coercion for role/status mutation IDs. Genuine integer numbers are required; booleans and numeric strings are rejected.
|
||||
- Added a focused Nest HTTP boundary suite with actual URI versioning, global validation, controller decorators, JWT guard behavior, real `RolesGuard`, and HTTP serialization assertions.
|
||||
|
||||
### RED evidence
|
||||
|
||||
1. Registration isolation:
|
||||
- Command: `npm test -- --runInBand auth.controller.spec.ts auth.service.spec.ts -t "narrow validated registration|public registration player"`
|
||||
- Failure: controller parameter metadata was `Object` instead of `AuthRegisterLoginDto`; registration still attempted public player linkage.
|
||||
2. Social-login race:
|
||||
- Command: `npm test -- --runInBand auth.service.spec.ts -t "concurrently deactivated social|locks and reloads an existing"`
|
||||
- Failure: existing flow bypassed the transaction repository, used entity-wide `UsersService.update`, and signed stale state.
|
||||
3. Self-only safe team bootstrap:
|
||||
- Command: `npm test -- --runInBand users.controller.security.spec.ts users.teams.spec.ts`
|
||||
- Failure: `findMyTeams` did not exist and the controller still delegated arbitrary IDs to raw `findTeams`.
|
||||
4. Strict numeric role/status bodies:
|
||||
- Command: `npm test -- --runInBand admin-users.controller.spec.ts -t "non-number role"`
|
||||
- Failure: both JSON `true` and `"1"` were coerced to valid enum ID `1`.
|
||||
5. Nest HTTP boundary:
|
||||
- Command: `npm test -- --runInBand admin-users.http.spec.ts`
|
||||
- Initial infrastructure failure: the focused module did not wire the existing database-backed `IsNotExist` validator container. The test module was corrected to use the real validator with a mocked repository; no validation was weakened.
|
||||
|
||||
### Files added
|
||||
|
||||
- `src/users/admin-users.http.spec.ts`
|
||||
- `src/users/users.teams.spec.ts`
|
||||
- `src/users/dto/user-team-response.dto.ts`
|
||||
|
||||
### Files modified
|
||||
|
||||
- `src/auth/auth.controller.ts`
|
||||
- `src/auth/auth.controller.spec.ts`
|
||||
- `src/auth/auth.service.ts`
|
||||
- `src/auth/auth.service.spec.ts`
|
||||
- `src/auth/dto/auth-register-login.dto.ts`
|
||||
- `src/users/admin-users.controller.spec.ts`
|
||||
- `src/users/dto/admin-user.dto.ts`
|
||||
- `src/users/dto/create-user.dto.ts`
|
||||
- `src/users/users.controller.ts`
|
||||
- `src/users/users.controller.security.spec.ts`
|
||||
- `src/users/users.service.ts`
|
||||
|
||||
### GREEN evidence
|
||||
|
||||
- Focused Task 1 + Task 2 tests:
|
||||
- Command: `npm test -- --runInBand users.service.spec.ts users.teams.spec.ts users.controller.security.spec.ts admin-users.controller.spec.ts admin-users.service.spec.ts admin-users.http.spec.ts auth.controller.spec.ts auth.service.spec.ts jwt.strategy.spec.ts logging.service.spec.ts AddPlayerLookupIndexes.spec.ts`
|
||||
- Result: **11 suites passed, 56 tests passed, 0 failed**.
|
||||
- Targeted ESLint across all Fix Round 1 source/spec files: **exit 0, no findings**.
|
||||
- Backend build via `npm run build`: **exit 0**.
|
||||
- `git diff --check`: **exit 0**.
|
||||
- Both frontend directories: **no changes**.
|
||||
|
||||
### Client contract impact
|
||||
|
||||
- Both frontend codebases currently send `linkPlayerId` during invite registration. The backend now strips it and performs no assignment, as required; registration still succeeds, but player linkage must subsequently use the guarded admin assignment endpoint.
|
||||
- Both frontends call `GET users/:currentUserId/teams`. That self-ID URL remains valid. The modern selector consumes only the retained player/team ID and name fields. The legacy frontend also displayed team balance and team role from this response; those sensitive/unneeded fields are no longer returned, and the legacy frontend was intentionally not edited.
|
||||
|
||||
### Remaining limitation
|
||||
|
||||
- No ready local PostgreSQL test database/harness was available without new infrastructure. No dependencies or testcontainers were added. Concurrency remains covered by transaction/alias-lock assertions and stale-state regressions; migration remains covered by exact up/down SQL and metadata tests. A live two-connection PostgreSQL race and migration run/revert remain recommended before rollout.
|
||||
|
||||
Reference in New Issue
Block a user