Commit 706fd16
feat: Authenticated chat sessions with asymmetric key verification (#2827)
* [US-001] Add PublicKeyConfig and WebClientAuthConfig schemas
Extend WebClientConfigSchema with optional auth block supporting
asymmetric public key configuration for authenticated chat sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-002] Add public key validation utility
validatePublicKey validates PEM format, rejects private keys,
checks algorithm/key-type match, and enforces minimum key sizes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-003] Add public key management API endpoints
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-004] Add asymmetric JWT verification tests for app credential auth
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-005] Persist authenticated user identity and metadata on conversations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-006] Add public key management UI to app edit page
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [US-007] Create global playground app via seed script
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address local review findings for authenticated chat sessions
- Critical: require exp claim in asymmetric JWT verification (was optional)
- Major: JWT agentId claim takes precedence over header for global apps
- Major: fail hard if tenant-scoped app has null tenantId/projectId
- Major: add runtime typeof guards for JWT claim extraction
- Major: show error toast on key fetch failure (was silent)
- Major: rename authMethod to app_credential_web_client_authenticated
- Minor: generic error message for JWT claim validation (no detail leakage)
- Minor: add error logging to catch blocks in JWT verification
- Minor: add aria-label to delete key button
- Minor: fix operationId to use create- prefix
- Minor: use Apps tag instead of App Auth
- Update openapi snapshot for operationId change
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: remove review artifacts from git, add to gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments
1. Move inline schemas (AddPublicKeyRequest, PublicKeyListResponse,
PublicKeyResponse) from route file to validation/schemas.ts
2. Create dedicated DAL functions (getAppAuthKeysForProject,
updateAppAuthKeysForProject) that fetch only config+type instead
of full app record
3. Track third-party userIds as metadata.externalUserId instead of
overwriting conversations.userId (reserved for Inkeep user IDs)
4. Replace inline type cast in runAuth.ts with imported WebClientConfig
type (Zod-inferred)
- Update openapi snapshot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: address PR review comments
1. Move inline schemas to validation/schemas.ts
2. Create dedicated DAL functions (getAppAuthKeysForProject,
updateAppAuthKeysForProject) for performant fetches
3. Track third-party userIds as metadata.externalUserId
(preserve conversations.userId for Inkeep user IDs)
4. Replace inline type cast with imported WebClientConfig type
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: add pr-diff to root gitignore
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: remove pr-context from gitignore, keep only pr-diff
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: migrate playground to app-credential auth path
- Update playground token endpoint to sign JWTs in app-credential format
(kid: 'playground-rsa', claims: tid/pid/agentId instead of tenantId/projectId)
- Return appId in playground token response
- Update chat widget to use x-inkeep-app-id header with global playground app
- Falls back to legacy tenant/project headers if appId not returned
- Token now verified via tryAppCredentialAuth → global app → SpiceDB
instead of the bespoke tryTempJwtAuth path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: skip PoW for authenticated sessions
PoW is an anti-abuse mechanism for anonymous sessions. Authenticated
sessions are gated by the customer's JWT — PoW is unnecessary and
blocks the playground which sets shouldBypassCaptcha: true.
Move PoW check into the anonymous-only branch of tryAppCredentialAuth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: unify authenticated app auth — treat all apps the same
- Remove SpiceDB canUseProjectStrict check from global app auth path
- Token issuer (e.g., manage-ui) is the authorization gate, not agents-api
- Global apps resolve scope from token claims (tid/pid/agentId), trusted
- Tenant-scoped apps resolve scope from app record, same as before
- sub claim is always externalUserId, never used for Inkeep authz
- Both paths return through a single unified code path
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: add verifiedClaims propagation and validateScopeClaims
Identity model redesign (specs/2026-03-26-identity-model-redesign):
- Extract non-standard JWT claims as verifiedClaims in tryAppCredentialAuth
- Enforce 1KB size limit on verified claims (reject tokens exceeding it)
- Add verifiedClaims to BaseExecutionContext.metadata (runtime access)
- Persist verifiedClaims to conversations.metadata (analytics)
- Keep verifiedClaims strictly separate from unverified userProperties
- Add validateScopeClaims flag to WebClientAuthConfig
- When enabled, global apps validate tid/pid via SpiceDB canUseProjectStrict
- Update playground app seed to set validateScopeClaims: true
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: formatting fixes from Biome
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor: extract buildConversationMetadata helper
Replace inscrutable nested ternary spread in chat.ts and
chatDataStream.ts with a readable helper function that builds
conversation metadata from the execution context and user properties.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: Biome formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: proper error codes for auth failures + allowAnonymous flag
- Replace HTTPException(401) with createApiError({ code: 'unauthorized' })
for authenticated session errors — fixes misleading "internal_server_error"
code on 401 responses
- Add allowAnonymous flag to WebClientAuthConfig (default: true)
Apps with auth keys configured can still serve anonymous sessions
When false, anonymous fallback is blocked and auth is required
- Restructure tryAppCredentialAuth to fall through to anonymous path
when asymmetric verification fails and allowAnonymous is true
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test: add dual-mode tests + fix remaining error formatting
- Add tests for anonymous fallback when auth keys are configured
- Add test for allowAnonymous: false rejecting anonymous tokens
- Add test for authenticated tokens on apps with allowAnonymous: true
- Add test verifying error responses use 'unauthorized' code not 'internal_server_error'
- Fix remaining HTTPException(403/500) in authenticated path to use createApiError
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: Biome formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: widen app edit dialog, prevent horizontal scroll
- Increase dialog from max-w-lg (512px) to max-w-2xl (672px)
- Add overflow-x-hidden to prevent horizontal scroll
- Add break-all and whitespace-pre-wrap on PEM textarea
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat: show public key on hover, copy to clipboard on click
Public keys are not secrets — show the full PEM in a tooltip on hover
and copy to clipboard when clicking the key row. Adds a small copy
icon as affordance.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fail hard with 401 when auth-configured app gets invalid token
When an app has auth keys configured and both asymmetric and anonymous
JWT verification fail, throw a hard 401 error instead of returning
null (which lets dev mode create a default context with test-project).
Prevents leaked test-project/test-tenant values from reaching
downstream handlers and producing misleading 404 errors.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* style: Biome formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: update global app tests for opt-in SpiceDB validation
- Test 1: global app without validateScopeClaims should NOT call
canUseProjectStrict (SpiceDB validation is opt-in)
- Test 2: add validateScopeClaims: true to test SpiceDB denial
behavior specifically for apps that opt in
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: update OpenAPI snapshot
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>1 parent ec1b2f7 commit 706fd16
File tree
31 files changed
+3255
-40
lines changed- agents-api
- __snapshots__
- src
- __tests__
- manage/routes/crud
- middleware
- run/routes/chat
- domains
- manage/routes
- run/routes
- middleware
- agents-manage-ui/src
- components
- agent/playground
- apps
- form
- hooks
- lib
- actions
- api
- packages/agents-core/src
- auth
- data-access/runtime
- types
- utils
- __tests__
- validation
- __tests__
31 files changed
+3255
-40
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
108 | 108 | | |
109 | 109 | | |
110 | 110 | | |
| 111 | + | |
| 112 | + | |
111 | 113 | | |
112 | 114 | | |
113 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
124 | 124 | | |
125 | 125 | | |
126 | 126 | | |
127 | | - | |
| 127 | + | |
| 128 | + | |
0 commit comments