feat(auth): API-ключи фаза 3A — снятие kill-switch + копируемый ключ (reveal под step-up) (#557) #580

Open
agent_coder wants to merge 2 commits from feat/557-apikey-phase3a into develop

2 Commits

Author SHA1 Message Date
agent_coder 4f22d597b3 docs(api-key): correct stale "show-once" comments for the reveal/copy flow (#557)
The reveal/copy flow makes several pre-existing docstrings false (they claimed the
token is shown/returned only ONCE and is "never retrievable"). Comment-only fixes,
no logic change:

- api-key.service.ts create(): the token is not "returned ONCE"; no token material
  is ever stored (self-contained JWT) and it is re-obtainable by the owner via a
  deterministic re-mint under a step-up (POST /api-keys/reveal).
- api-key.controller.ts: drop "never retrievable again"; the JWT is returned in a
  body on create OR via /api-keys/reveal, never logged, never stored.
- client api-key-service.ts / api-key-query.ts / types.ts: drop "show-once modal"
  and "shown exactly once"; the create flow discards the token and it is copied
  later via the per-row reveal action.

Tests unchanged: 62 server / 28 client green; tsc clean on touched files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:23:38 +03:00
agent_coder 559d676cd6 feat(api-key): фаза 3A — снять kill-switch API_KEYS_ENABLED + копируемый ключ (reveal под step-up)
Removes the API_KEYS_ENABLED kill-switch entirely and makes api-keys copyable via
a deterministic re-mint revealed under a password step-up (#557, epic #556).

Part 1 — remove the kill-switch (keys are now always on):
- environment.service.ts: delete isApiKeysEnabled()/getApiKeysEnabledRaw().
- environment.validation.ts: delete the API_KEYS_ENABLED field + decorators
  (validation has no forbidNonWhitelisted, so a stray env value is ignored).
- api-key.service.ts: drop OnModuleInit boot-log, the validate kill-switch branch,
  and the now-unused EnvironmentService injection.
- api-key.controller.ts: drop assertEnabled() + its call sites and the
  EnvironmentService injection.
- .env.example: delete the API_KEYS_ENABLED block.

Part 2 — deterministic mint: apiKeyJwtService gets noTimestamp:true, so the
api-key token carries neither exp nor iat and re-minting the same key is
byte-identical (basis of copyable reveal).

Part 3 — POST /api-keys/reveal { id, password }: JwtAuthGuard +
rejectApiKeyPrincipal (403) + AUTH throttler; step-up via
AuthService.verifyUserCredentials BEFORE any key lookup (401, no oracle);
owner-only even for admin; uniform 404 for absent/revoked/expired/other-owner/
disabled-creator (ForbiddenException from generateApiToken normalized to 404);
re-mint via the existing deterministic path; token never persisted; durable
AuditEvent.API_KEY_REVEALED + structured log without token material.

Part 4 — UI: replace the show-once modal with a per-row Copy action -> password
prompt -> reveal -> clipboard write + toast. The token never enters React state,
localStorage or the query cache (gcTime:0 + reset-after-read, mirroring #506).
i18n en+ru added; immediate-revoke behavior kept.

Tests: server api-key + token.service specs (reveal, deterministic no-exp/no-iat,
byte-identical, kill-switch-removal) and client api-key vitest (copy flow, no-leak,
wrong-password) all green. Mutation-verified: skipping the owner check reds the
non-owner-404 test, skipping step-up reds the wrong-password test, dropping
noTimestamp reds the deterministic/no-iat tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 00:23:38 +03:00