feat: add authentication - gateway API keys and operator JWTs (#42) - #77
thebabalola wants to merge 4 commits into
Conversation
|
Hi @joelpeace48-cell , auth layer from #42 is ready for review. Gateway keys are scrypt-hashed with per-key salt and prefix scanning, JWTs are HS256 via jose, and protection is deny-by-default with explicit opt-out for public routes. Happy to adjust scopes or expiry if you have a different convention in mind. |
|
@thebabalola CI failed |
…eStudioLab#42) Implement the auth layer required before attestation/usage/export routes land. Every endpoint is protected by default via a global onRequest hook; public routes explicitly opt-out via config { public:true }. Gateway credentials: - API keys prefixed with mt_<name>_ for leak scanning - Stored as scrypt hash + per-key salt, verified with timingSafeEqual - Expiration and rotation overlap - multiple active entries with same prefix are all tried Operator sessions: - HS256 JWTs via jose, issuer/audience modeltrace-api, 8h lifetime - Short-lived, issued via auth.issueOperatorToken Server: - src/config/env.ts: require JWT_SECRET (>=32), API_KEY_STORE JSON array - src/auth/*: types, provider, plugin (registerAuthHooks), testing helper - src/index.ts: decorate app.auth, registerAuthHooks before routes - src/routes/health.ts, src/routes/v1/index.ts: mark liveness/meta as public - src/routes/disputes: scope checks dispute:read/write/admin, derive userId from authenticated subject Tests: - src/auth/index.test.ts covers hashing, valid/invalid keys, expiration, JWT, tampering, rotation and parse helpers - src/index.test.ts covers health/meta public, 401 unauthenticated, valid gateway key, valid JWT, scope 403, default deny - src/routes/disputes/index.test.ts now authenticates via test auth provider and checks scope rejection - .env.example updated with JWT_SECRET and API_KEY_STORE # Conflicts: # .env.example # src/config/env.test.ts # src/config/env.ts # src/index.test.ts # src/index.ts
… tests - Merged rate limiting (FinesseStudioLab#76) and auth (FinesseStudioLab#42) features in env.ts, index.ts, index.test.ts, .env.example, env.test.ts - Applied lazy config (getConfig) to prevent eager parseEnv at import time - Augmented FastifyContextConfig with public boolean for route auth opt-out - Fixed body limit tests to include auth credentials (401 was returned before body parsing could trigger 413/201) - All 84 tests passing, lint clean, 0 audit vulnerabilities
eedfb7a to
a2dd545
Compare
|
Hi @joelpeace48-cell — just resolved the merge conflicts with #76 (rate limiting) and fixed the CI failure (JWT_SECRET was undefined at module load due to eager env parsing). All 84 tests passing now, CI green. Ready for another look when you get a chance. |
|
Nice job @thebabalola |
|
@thebabalola, ci failed |
|
Hi @joelpeace48-cell — the merge of #78 into main added env.exit.test.ts, which did not include JWT_SECRET (required since #42). Added it to the validEnv fixture. 87/87 tests passing, lint clean. Ready for another look. |
Closes #42
Summary of Changes
Establishes the auth layer before more routes land. Every route is protected by default via a global
onRequesthook; public routes explicitly opt-out withconfig: { public: true }. This prevents new endpoints from inheriting open access.Gateway credentials are scrypt-hashed with per-key salt and a detectable
mt_<name>_prefix for leak scanning. Rotation is supported by keeping multiple entries with the same prefix active during the overlap window. Operator sessions are HS256 JWTs (viajose) with issuer/audiencemodeltrace-apiand 8h expiry.What changed
src/config/env.ts: requireJWT_SECRET(>=32 chars) and addAPI_KEY_STOREJSON array; expose viaconfig.auth..env.example: documentJWT_SECRETandAPI_KEY_STORE(empty array means JWT-only).src/auth/types.ts/provider.ts: hash helpers,generateApiKey,parseApiKeyStore,resolveAuthProviderwith ApiKey and Bearer verification, expiration and scope handling.src/auth/plugin.ts:registerAuthHooks(app, auth)- global hook that skipspublic:trueroutes, returns 401 otherwise and stashesreq.identity.src/auth/testing.ts: light test auth provider usingx-test-authheader.src/index.ts: decorateapp.auth, callregisterAuthHooksbefore health/v1 routes; guard top-level listen withimport.meta.urlcheck.src/routes/health.ts&src/routes/v1/index.ts: mark liveness/readiness and/api/v1/metaas public.src/routes/disputes/index.ts: scope gates (dispute:read/dispute:write/admin), derive userId fromreq.identity.subjectwhile keepingx-rolebusiness header.src/auth/index.test.ts: 10 tests for hashing, key/JWT auth, expiry, tampering, rotation overlap, store parsing.src/index.test.ts: 7 tests - health/meta public, 401 unauthenticated, valid gateway key, valid JWT, 403 scope, default deny.src/routes/disputes/index.test.ts&src/routes/health.test.ts&src/config/env.test.ts: updated to provide JWT/registry env and use test auth.Testing / Local Verification
Scopes enforced per acceptance criteria:
attest:write,usage:write,export:read,adminplus dispute scopes. Keys are never stored plaintext.