Files
2026-05-27 23:29:53 +01:00

177 lines
10 KiB
Markdown

# Tasks: enterprise-auth
**Change ID**: enterprise-auth
**Package**: `@suaveplan/auth`
**Target path**: `universal/auth/auth`
**Prerequisite packages on Verdaccio**: `@suaveplan/error`, `@suaveplan/types`, `@suaveplan/events`, `@suaveplan/crypto`, `@suaveplan/crypto-keys`, `@suaveplan/logger`, `@suaveplan/retry`, `@suaveplan/security-codes`, `@suaveplan/idempotency`
---
## 1. Scaffold
- [x] Create directory `universal/auth/auth/` with `src/`, `tests/`, `src/__benchmarks__/`, and all feature subdirectories per the layout in `proposal.md`.
- [x] Write `package.json` at version `0.1.0` with:
- `"type": "module"`, `"sideEffects": false`
- `exports` map with a top-level `"."` barrel plus one subpath per feature module (`"./adapters/auth-adapter/auth-adapter"`, `"./adapters/memory-adapter/memory-adapter"`, `"./api-keys/api-keys/api-keys"`, `"./jwt/token-lifecycle/token-lifecycle"`, `"./middleware/auth-middleware/auth-middleware"`, `"./schemas/auth.schemas"`, `"./types/auth.types"`)
- `dependencies`: `@suaveplan/error workspace:^`, `@suaveplan/types workspace:^`, `@suaveplan/events workspace:^`, `@suaveplan/crypto workspace:^`, `@suaveplan/crypto-keys workspace:^`, `@suaveplan/logger workspace:^`, `@suaveplan/retry workspace:^`, `@suaveplan/security-codes workspace:^`, `@suaveplan/idempotency workspace:^`, `zod catalog:forms`, `@opentelemetry/api catalog:observability`, `tslib catalog:build`
- `devDependencies`: `vitest catalog:testing`, `@vitest/coverage-istanbul catalog:testing`, `fast-check catalog:testing`, `mitata catalog:testing`, `@suaveplan/testing workspace:^`
- [x] Write `tsconfig.json` extending `@suaveplan/typescript-config/library.json`.
- [x] Write `vitest.config.ts` using `createTestConfig()` from `@suaveplan/testing/config` (pool: `forks`).
- [x] Add `lint`, `typecheck`, `test`, `test:coverage`, `build`, `docs:check`, `bench` scripts to `package.json`.
---
## 2. Types and errors
### 2a. Types (`src/types/auth.types.ts` + `src/types/auth.types.md`)
- [x] Define all public interfaces: `ApiKey`, `ApiKeyRequest`, `ApiKeyGeneration`, `ApiKeyValidationResult`, `JWTPayload`, `AuthenticatedJWTRequest`, `TokenValidationResult`, `UserContext`, `AuthenticatedRequest`, `PermissionCheckResult`, `SessionInfo`, `ApiKeyOptions`, `JWTOptions`, `MiddlewareOptions`, `ApiKeyMiddlewareOptions`, `MiddlewareFn`.
- [x] Add genesis-only optional fields: `clock?: Clock` and `idGenerator?: IdGenerator` on all factory option interfaces; `bus?: EventBusLike` on `ApiKeyOptions`, `JWTOptions`, `MiddlewareOptions`.
- [x] Write `src/types/auth.types.md` (≥200 words, simple + advanced examples, full API reference for every type).
### 2b. Error codes (`src/error-codes.ts`)
- [x] Define `AuthErrorCode` union — ten codes: `AUTH_TOKEN_INVALID`, `AUTH_TOKEN_EXPIRED`, `AUTH_TOKEN_BLACKLISTED`, `AUTH_SESSION_NOT_FOUND`, `AUTH_SESSION_EXPIRED`, `AUTH_API_KEY_INVALID`, `AUTH_API_KEY_EXPIRED`, `AUTH_API_KEY_REVOKED`, `AUTH_PERMISSION_DENIED`, `AUTH_STORAGE_ERROR`.
- [x] Define `AUTH_ERROR_METADATA` with `httpStatus`, `severity`, `retryable`, `userMessageKey`, `category`, `description` for each code.
- [x] Export `registerAuthErrorCodes()` — calls `ErrorCodes.registerMany(AUTH_ERROR_METADATA)`. Call as eager side-effect at file bottom.
- [x] Export `AuthError` class extending `SuaveplanError<AuthErrorCode>`.
### 2c. Zod schemas (`src/schemas/auth.schemas.ts` + `src/schemas/auth.schemas.md`)
- [x] Write Zod schemas: `ApiKeyRequestSchema`, `JWTOptionsSchema`, `ApiKeyOptionsSchema`, `MiddlewareOptionsSchema`, `AuthAdapterConfigSchema`.
- [x] Write `src/schemas/auth.schemas.md`.
- [x] Write `src/schemas/auth.schemas.test.ts` — parse valid/invalid for each schema.
---
## 3. AuthAdapter interface
- [x] Implement `AuthAdapter` interface in `src/adapters/auth-adapter/auth-adapter.ts` with the full contract from `proposal.md`.
- [x] Write `auth-adapter.md` (≥200 words, integration guide, expected error behaviour for each method).
- [x] Write `auth-adapter.types.ts`.
---
## 4. MemoryAuthAdapter
- [x] Implement `MemoryAuthAdapter` in `src/adapters/memory-adapter/memory-adapter.ts`:
- `Map`-backed storage for API keys (by hash), token blacklist (by jti + TTL), sessions (by sessionId).
- TTL cleanup: expired tokens removed lazily on lookup (no setInterval at module scope).
- Accepts optional `{ clock?: Clock }` constructor option for deterministic expiry checks.
- [x] Write `memory-adapter.md`.
- [x] Write `memory-adapter.test.ts` — store/find/revoke API keys, blacklist tokens, create/find/delete sessions, TTL expiry with injected FakeClock, list operations. Each `it` has `{ timeout: 5000 }`.
---
## 5. API key management
File: `src/api-keys/api-keys/api-keys.ts`
- [x] Implement `createApiKey(request, options?)`:
- Generate `rawKey` using `@suaveplan/crypto`'s `randomBytes` (32 bytes, base58-encoded).
- Compute HMAC-SHA256 of `rawKey` with a secret from options; store the hash (never the plaintext).
- Accept `idGenerator` for key ID, `clock` for `createdAt`.
- Emit `auth:apikey:created` on optional `bus`.
- Instrument with `auth.apikey.create` OTel span.
- [x] Implement `validateApiKey(rawKey, options?)`:
- Hash the incoming key and look up via adapter; check revoked/expired status.
- Update `lastUsedAt` on successful validation.
- Instrument with `auth.apikey.validate` OTel span, `auth.apikey_validate_total` counter.
- [x] Implement `revokeApiKey`, `listApiKeys`, `rotateApiKey`, `updateApiKeyPermissions`, `hasPermission`, `validateApiKeyMiddleware`.
- [x] Write `api-keys.md` (≥200 words, security model explanation, rotation guide).
- [x] Write `api-keys.test.ts` (≤500 lines): creation/validation round-trip, revocation, rotation, expiry, permission check, middleware path. Each `it` has `{ timeout: 5000 }`.
---
## 6. JWT token lifecycle
File: `src/jwt/token-lifecycle/token-lifecycle.ts`
- [x] Implement `validateSessionToken(token, options?)`:
- Decode and verify JWT signature using `@suaveplan/crypto-keys`.
- Check expiry via injectable `clock`; check blacklist via adapter.
- Return `TokenValidationResult` with structured error reason.
- Instrument with `auth.token.verify` span.
- [x] Implement `refreshToken(refreshToken, options?)`:
- Validate refresh token; issue new access + refresh token pair.
- Blacklist the old refresh token jti.
- Emit `auth:token:issued` event on `bus`.
- [x] Implement `revokeToken`, `revokeAllUserTokens`, `isTokenBlacklisted`, `getUserSessions`, `verifyTokenMiddleware`, `optionalTokenMiddleware`.
- [x] Write `token-lifecycle.md` (≥200 words, refresh flow diagram, blacklist strategy).
- [x] Write `token-lifecycle.test.ts` (≤500 lines): issue/verify round-trip, expired token, blacklisted token, refresh flow, revoke all, middleware verify/optional paths. `{ timeout: 5000 }` on sync, `{ timeout: 30000 }` on async.
---
## 7. Permission middleware
File: `src/middleware/auth-middleware/auth-middleware.ts`
- [x] Implement `getUserFromSession`, `checkPermission`, `requirePermission`, `requireAllPermissions`, `requireAnyPermission`, `withPermissions`.
- [x] `checkPermission` uses exact string match and wildcard `*` (e.g. `resource:*` grants all actions on resource).
- [x] All middleware functions return framework-agnostic `MiddlewareFn`; include a `createMiddlewareAdapter` bridge for Hono and Node.
- [x] Instrument `auth.permission.check` histogram (duration_ms).
- [x] Write `auth-middleware.md` (≥200 words, wildcard semantics, framework adapter guide).
- [x] Write `auth-middleware.test.ts` (≤500 lines): exact match, wildcard match, requireAll/requireAny combinations, denied path, session lookup failures. `{ timeout: 5000 }` on each `it`.
---
## 8. OTel instrumentation (cross-cutting review)
- [x] Confirm all spans, counters, and histograms listed in `proposal.md §5` are wired in each module.
- [x] Add `tests/otel.test.ts`: wire in-memory OTel exporter; assert counter increments for key create, key validate, token issue, token verify, session create, permission check.
---
## 9. Property-based tests (`tests/property.test.ts`)
Use `fast-check`. All test timeouts `{ timeout: 30000 }`.
- [x] **API key round-trip**: for any valid `ApiKeyRequest`, `createApiKey` then `validateApiKey` returns `valid === true`.
- [x] **Blacklist monotonicity**: once a `jti` is blacklisted via `revokeToken`, all subsequent `isTokenBlacklisted` calls return `true` regardless of adapter state mutations.
- [x] **Permission check AND**: `checkPermission` with `requireAllPermissions([a, b])` is `true` iff both individual checks are `true` for arbitrary permission strings and user contexts.
- [x] **MemoryAdapter eviction**: after TTL elapses (injected FakeClock advanced), `isBlacklisted` returns `false` for expired entries.
---
## 10. Benchmarks (`src/__benchmarks__/auth.bench.ts`)
Use `mitata`.
- [x] `createApiKey_throughput`: 1 000 iterations against `MemoryAuthAdapter`.
- [x] `validateApiKey_warm`: 10 000 lookups on an adapter with 1 000 pre-populated keys.
- [x] `validateSessionToken_hmac`: 10 000 verifications of a pre-issued token.
- [x] Document placeholder numbers in `README.md` under `## Benchmarks`.
---
## 11. Docs
Per-file `.md` (all ≥200 words, simple + advanced examples, API reference):
- [x] `src/types/auth.types.md`
- [x] `src/adapters/auth-adapter/auth-adapter.md`
- [x] `src/adapters/memory-adapter/memory-adapter.md`
- [x] `src/api-keys/api-keys/api-keys.md`
- [x] `src/jwt/token-lifecycle/token-lifecycle.md`
- [x] `src/middleware/auth-middleware/auth-middleware.md`
- [x] `src/schemas/auth.schemas.md`
Root README:
- [x] Write `universal/auth/auth/README.md`: purpose, install, quick-start (API key + JWT round-trip), modules table, dependencies, API listing, benchmarks placeholder.
---
## 12. Verify and publish
- [x] Run `bunx turbo lint --filter=@suaveplan/auth` — zero errors.
- [x] Run `bunx turbo typecheck --filter=@suaveplan/auth` — zero errors.
- [x] Run `bunx turbo test --filter=@suaveplan/auth` — all tests pass.
- [x] Run `bunx turbo test:coverage --filter=@suaveplan/auth` — 100/100/100/100.
- [x] Run `bun run docs:check --filter=@suaveplan/auth` — zero errors.
- [x] Confirm no `TODO`, `FIXME`, `@deprecated`, untyped `any`, `ts-ignore`, or `.skip` in `src/` or `tests/`.
- [x] Confirm `"sideEffects": false` in `package.json`.
- [x] Run `bun scripts/publish.ts --package @suaveplan/auth`.
- [x] Update `CLAUDE.md` package listing to include `@suaveplan/auth`.
- [x] Archive: move `openspec/changes/enterprise-auth/` to `openspec/archive/2026-04-24-enterprise-auth/` and fold spec delta into `openspec/specs/auth/spec.md`.