feat(auth): add account-domain OIDC, actions, and access UI#112
feat(auth): add account-domain OIDC, actions, and access UI#112
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Add the minimal account identity model from openspec/changes/account-oidc-privy-actions: - Migration 004 introduces vana_users, vana_linked_wallets, vana_provider_links with idempotent CREATE statements matching the existing direct-Neon-SQL style. - connect/src/lib/auth/vana-account.ts holds pure helpers (id generation, EVM address normalization, row mappers, OIDC claim builder). The vana_user_id is generated independently of any wallet/provider/email evidence. - connect/src/lib/db/account.ts holds Neon-backed persistence and a transitional resolveVanaUserByPrivyEvidence merge step keyed on provider subject and embedded wallet address. Email is stored as audit metadata only and is never a merge key. - Unit tests prove sub = vana_user_id, that wallet addresses surface as linked claims, that provider ids/email are stored as evidence, and that EVM address storage is normalized to lowercase. Tests do not require DATABASE_URL. OpenSpec tasks 4.1-4.6 marked done; runtime OIDC integration remains out of scope for this seam.
|
Latest auth/custom-JWT update pushed in |
|
Second prep batch pushed in |
## Summary
DELETE `/api/servers/{id}` was returning a generic 500 (`Failed to
deprovision server. Resources may still be running.`) regardless of
which step inside `provider.deprovision()` actually failed. Vercel's
events API only exposes build logs, so the underlying `console.error`
lines were not reachable from the dashboard. Three prior fixes (#113,
#114, #115) addressed plausible causes blind, and the deprovision still
500s in the field.
This change makes the next reproduction self-diagnosing.
## Changes
- `lib/server-provider/gcp.ts`
- Each error inside `deprovision()` is now tagged with its step
(`tunnel`, `vm-delete`, `vm-wait`, `disk:<name>`, `disks-init`) and the
gRPC/HTTP `code`.
- The thrown `Error` carries a `deprovisionErrors` array (step + code +
message per failure) in addition to the human-readable message.
- Split the initial VM `delete` from the LRO `op.promise()` wait so a
stale-op timeout is distinguishable from an immediate API error.
- Skip VM/disk steps when `serverId` is empty (defensive — the route
already gates on that, but the contract is now obvious).
- `app/api/servers/[id]/route.ts`
- 500 response body now includes the actual error message + step
summary, e.g. `Failed to deprovision server: Deprovision partially
failed: vm-wait(code=4): Deadline exceeded [steps: vm-wait(code=4)]`.
- Structured single-line `console.error` with `serverId`, `providerId`,
`tunnelId`, `dnsRecordId`, `errorName`, `errorMessage` so runtime logs
are greppable when they do become accessible.
No secrets are returned: only step labels and the provider's own error
text (GCP/Cloudflare error messages).
## Validation
- `pnpm exec tsc --noEmit` clean (excluding pre-existing `.next/`
validator stubs that reference unmerged PR #112 routes).
- `pnpm exec biome check` clean on changed files.
- `pnpm test`: same 7 pre-existing `use-login-page` failures as on main
(`localStorage.clear is not a function` — jsdom env, OIDC scope,
unrelated). My target `src/app/server/use-server.test.ts` passes.
## Test plan
- [ ] After merge + Vercel deploy on `account.vana.org/server`:
provision a fresh PS, click Remove, capture the 500 response body in
DevTools, share with team for the next targeted fix.
- [ ] Confirm the structured log line shows up in Vercel runtime logs
when accessible.
…ns (#124) Stacked on PR #112. Targets that branch so the integration lands as part of the OIDC slice. ## Summary When a user approves an account-action with `execution_mode === "embedded_wallet_account_hosted"`, account.vana.org now mints a real on-chain grant on the user's Personal Server. Replaces the mock-only path. Also addresses the hardcoded/missing-data audit findings flagged earlier. ## What's wired 1. **`oauth_clients` registry table** (migration 007) — replaces the localStorage admin store. Builder identity (`grantee_address`, `builder_id`, `public_key`) is optional (Sign-in-with-Vana works without it) but all-or-nothing when set. 2. **Admin API** (`/api/admin/oauth-clients`) — POST upsert, GET list, DELETE; owner-auth via masterKeySignature. 3. **`executeGrantViaPersonalServer` helper** — pure function: resolves user PS + OAuth client, POSTs `<ps-url>/v1/grants` with `Bearer <control_plane_token>`. 4. **`handleActionDecision` real-grant branch** — when execution_mode is `embedded_wallet_account_hosted`, calls executor before persisting; failure aborts approval with a typed error. 5. **Consent event audit** — populates `subject_wallet_address` (primary linked wallet), `application_id` (oauth_clients.protocolPrincipal), `authorization_reference` ({grantId, granteeAddress, personalServer}). Subject wallet on denial too. 6. **`DEFAULT_ACCOUNT_ACTION_ISSUER`** reads `VANA_ACCOUNT_ISSUER` env with literal fallback. 7. **DB-backed registry** with `DEV_MEMORY_APP_CLIENT` fallback so demo flows keep working when the table is empty. ## Migration `007_add_oauth_clients.sql` already applied to dev (`ep-red-river`) and prod (`ep-hidden-glade`) Neon branches. ## Tests 322 passed / 17 skipped (matches baseline). ## Out of scope - Migrate device-code state from sessionStorage → DB (#20) - Migrate passport agreement from localStorage → consent event (#21) - Wire admin UI to use the new API (currently still writes localStorage; the API is in place) - Action-result revocation wiring ## Test plan - [ ] CI passes - [ ] On account-dev: register an OAuth client with builder identity via POST /api/admin/oauth-clients - [ ] Trigger an action request from the demo Memory App with execution_mode=embedded_wallet_account_hosted - [ ] Approve the action — observe POST to `<user-ps>/v1/grants` succeed, `grantId` populated, `authorization_reference` set on consent event
Brings the OIDC slice (PR #112's branch tip) into develop so account-dev.vana.org can serve the full Login-with-Vana flow + real-PS grants (PR #124). One conflict resolved: server/page.tsx import keeps the Discoverable UI (RegistrationStatus type) added in PR #122/#123. Production untouched; this PR targets develop only.
Summary
Adds the current account-domain implementation for the Login with Vana demo slice: OIDC foundations, transitional Privy-backed account sessions, account-hosted action requests, account access visibility, mock revocation controls, IAM-protected Hydra admin calls, and the feature-flagged Vana-root Privy JWT auth path prepared for later activation.
This PR contains two planning layers plus production-code seams:
account-domain-identity-issuer: earlier minimal account-domain issuer plan.account-oidc-privy-actions: current implementation source of truth; this supersedes the earlier plan where they differ.vana-connect-mobile#1.Current Direction
subis a Vana-owned opaquevana_user_id, not wallet address, email, Google subject, Privy id, WorkOS id, or Stytch id.Implementation State
vana_account_sessionsupport so/logincan establish a Vana-owned HTTP-only session after a successful Privy-authenticated login./api/auth/sessionfor the demo/client flow and preserves safe OIDCreturn_tobehavior across login.sub=vana_user_id, issuer/audience/ttl validation, config inspection, and public JWKS at/.well-known/jwks.json.GET /api/auth/privy-custom-auth-jwt, which resolves verified login evidence to a Vana account and returns a Vana-signed JWT for Privy JWT-based auth./api/auth/privy-custom-auth-jwt/diagnosticsfor non-production / explicitly enabled environments.useSyncJwtBasedAuthStatebridge underPrivyProvider, gated byNEXT_PUBLIC_PRIVY_JWT_AUTH_SYNC_ENABLEDuntil the Privy dashboard is configured and verified.pnpm auth:smoke-custom-jwt -- <base-url>to smoke JWKS, diagnostics, and unauthenticated JWT behavior without requiring Privy Scale-plan access..env.local.exampleplaceholders anddocs/privy-custom-auth-runbook.mdcovering the Privy Scale-plan gate, dashboard setup, switch-on steps, and rollback./account/accessfor users to review connected apps and grants.action.revokedevents are recorded; RPC revocation is not live yet./serveris no longer surfaced in the app chrome.ACCOUNT_DB_MIGRATE_ALLOW_REMOTE=trueand--allow-remote.GCP_SERVICE_ACCOUNT_KEYis configured.Related PRs
Linear Follow-Ups
Validation
Latest validation:
cd connect && pnpm exec tsc --noEmitcd connect && pnpm lintcd connect && pnpm test=> 322 passed, 17 DB-backed skipped.cd connect && pnpm auth:smoke-custom-jwt -- https://account-dev.vana.org=> ok, JWKS kidaccount-dev-rs256-20260430,jwtSyncEnabled=false, Privy plan gateScale.cd connect && node --check scripts/migrate-account-db.mjscd connect && node --check scripts/migrate-local.mjscd connect && env -u DATABASE_URL node scripts/migrate-account-db.mjsexits safely.git diff --cached --checkData Gateway / Real Grants Path
The current ChatGPT access flow is intentionally mock-first for execution/result payloads, but the grant/protocol slice has a clear path to become real without inventing a new RPC integration. The existing recent repos show the intended chain:
@opendatalabs/personal-server-ts-*packages.personal-server-tsowns theGatewayClientabstraction and calls Data Gateway HTTP endpoints like/v1/grants,/v1/servers,/v1/builders, and/v1/files.vana-com/data-gatewayis the Data Portability RPC Gateway: it stores/indexes protocol state, validates EIP-712 payloads, uses Data Portability contract configuration, and is backed byRPC_URL.Practical next slice: replace mock grant create/revoke with the existing Personal Server -> Data Gateway path, store the returned
grantIdon the account action, and keep ChatGPT connector execution/result references mocked until connector runtime + encrypted reference delivery are wired. Direct account -> Data Gateway calls are possible but lower-level and more brittle because they require server/builder identity, file IDs, EIP-712 signatures, and protocol payload details that Personal Server already abstracts.Known Gaps
NEXT_PUBLIC_PRIVY_JWT_AUTH_SYNC_ENABLEDremainsfalse.Deployment Note
Hydra admin calls support
HYDRA_ADMIN_AUDIENCEseparately fromHYDRA_ADMIN_URL. For Cloud Run, keepHYDRA_ADMIN_URLon the readable admin domain (for examplehttps://oauth-admin-dev.vana.org) and setHYDRA_ADMIN_AUDIENCEto the underlying Cloud Run service URL so Google ID tokens pass IAM audience validation.