@elytracms/studio 0.0.47 → 0.0.48

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/convex/auth.ts +61 -12
  2. package/convex/authority.ts +40 -2
  3. package/convex/functions.ts +9 -0
  4. package/convex/invites.ts +75 -0
  5. package/convex/members.ts +44 -18
  6. package/convex/schema.ts +37 -0
  7. package/convex/users.ts +89 -2
  8. package/dist/components/account/account-settings.d.ts +13 -0
  9. package/dist/components/account/account-settings.js +54 -0
  10. package/dist/components/account/account-settings.js.map +1 -0
  11. package/dist/components/account/credential-panel.d.ts +14 -0
  12. package/dist/components/account/credential-panel.js +52 -0
  13. package/dist/components/account/credential-panel.js.map +1 -0
  14. package/dist/components/account/self-account-schema.d.ts +19 -0
  15. package/dist/components/account/self-account-schema.js +52 -0
  16. package/dist/components/account/self-account-schema.js.map +1 -0
  17. package/dist/components/account/self-account-view-source.d.ts +35 -0
  18. package/dist/components/account/self-account-view-source.js +110 -0
  19. package/dist/components/account/self-account-view-source.js.map +1 -0
  20. package/dist/components/auth-context.d.ts +5 -1
  21. package/dist/components/auth-context.js +9 -0
  22. package/dist/components/auth-context.js.map +1 -1
  23. package/dist/components/current-user-menu.js +11 -2
  24. package/dist/components/current-user-menu.js.map +1 -1
  25. package/dist/components/deployment-setup-gate.d.ts +31 -0
  26. package/dist/components/deployment-setup-gate.js +92 -0
  27. package/dist/components/deployment-setup-gate.js.map +1 -0
  28. package/dist/components/project-session.d.ts +1 -1
  29. package/dist/components/project-session.js.map +1 -1
  30. package/dist/components/use-hydration-phase.js +1 -1
  31. package/dist/components/use-hydration-phase.js.map +1 -1
  32. package/dist/components/users/invite-user-bar.d.ts +3 -0
  33. package/dist/components/users/invite-user-bar.js +91 -0
  34. package/dist/components/users/invite-user-bar.js.map +1 -0
  35. package/dist/components/users/users-module.js +13 -2
  36. package/dist/components/users/users-module.js.map +1 -1
  37. package/dist/lib/auth/auth-adapter.d.ts +50 -2
  38. package/dist/lib/auth/auth-adapter.js.map +1 -1
  39. package/dist/lib/auth/convex-auth-adapter.d.ts +28 -1
  40. package/dist/lib/auth/convex-auth-adapter.js +113 -1
  41. package/dist/lib/auth/convex-auth-adapter.js.map +1 -1
  42. package/dist/lib/auth/dev-auto-login-adapter.d.ts +3 -1
  43. package/dist/lib/auth/dev-auto-login-adapter.js +6 -0
  44. package/dist/lib/auth/dev-auto-login-adapter.js.map +1 -1
  45. package/dist/lib/auth/index.d.ts +2 -2
  46. package/dist/lib/auth/index.js +1 -1
  47. package/dist/lib/auth/index.js.map +1 -1
  48. package/dist/lib/auth/local-auth-adapter.d.ts +3 -1
  49. package/dist/lib/auth/local-auth-adapter.js +16 -0
  50. package/dist/lib/auth/local-auth-adapter.js.map +1 -1
  51. package/dist/lib/i18n/messages.en.js +45 -0
  52. package/dist/lib/i18n/messages.en.js.map +1 -1
  53. package/dist/lib/operations/studio-operations.js +1 -1
  54. package/dist/lib/operations/studio-operations.js.map +1 -1
  55. package/dist/lib/persistence/backend-mode.d.ts +1 -1
  56. package/dist/lib/persistence/backend-mode.js +1 -1
  57. package/dist/lib/persistence/backend-mode.js.map +1 -1
  58. package/dist/lib/persistence/convex-adapter.d.ts +2 -1
  59. package/dist/lib/persistence/convex-adapter.js +69 -1
  60. package/dist/lib/persistence/convex-adapter.js.map +1 -1
  61. package/dist/lib/persistence/deployment-readiness.d.ts +36 -0
  62. package/dist/lib/persistence/deployment-readiness.js +26 -0
  63. package/dist/lib/persistence/deployment-readiness.js.map +1 -0
  64. package/dist/lib/persistence/index.d.ts +3 -1
  65. package/dist/lib/persistence/index.js +4 -1
  66. package/dist/lib/persistence/index.js.map +1 -1
  67. package/dist/lib/workspace/switcher-source.js +1 -1
  68. package/dist/lib/workspace/switcher-source.js.map +1 -1
  69. package/dist/lib/workspace/workspace-source.d.ts +1 -1
  70. package/dist/lib/workspace/workspace-source.js +1 -1
  71. package/dist/lib/workspace/workspace-source.js.map +1 -1
  72. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"convex-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/convex-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConvexHttpClient } from 'convex/browser'\nimport { actionRef, queryRef } from '../persistence/convex-client'\nimport type { AuthAdapter, AuthSession, SignInCredentials, SignInResult } from './auth-adapter'\nimport type { SessionStore } from './local-auth-adapter'\n\n/**\n * Convex Auth implementation of the project-owned {@link AuthAdapter}\n * (EC-150). Decision record (also in `convex/auth.ts`): **Convex Auth** over\n * Clerk — self-contained on the Convex stack, sessions in our own tables, no\n * extra vendor; reachable only through this adapter so a swap stays local.\n *\n * Flow (server side lives in `convex/auth.ts` + `convex/users.ts`):\n * 1. `signIn` calls the `auth:signIn` action with the Password provider\n * (`flow: 'signIn'`); on success Convex returns a JWT + refresh token.\n * 2. The JWT is set on the *shared* Convex client (`client.setAuth`), so every\n * subsequent persistence query/mutation runs authenticated and the\n * server-side `requireRole` guards see the caller.\n * 3. `currentSession` restores the JWT from storage and asks `users:me`.\n * 4. `signOut` calls `auth:signOut` and clears the JWT.\n *\n * v1 scope note: the stored JWT is short-lived and this adapter does not yet\n * run the refresh-token loop (the official `@convex-dev/auth/react` client\n * does); an expired session simply resolves to \"signed out\" and the user signs\n * in again. Live-deployment hardening is EC-156. No live deployment exists in\n * this repo — this file is typecheck-verified and exercised against the local\n * adapter's shared interface.\n */\n\nconst TOKEN_STORAGE_PREFIX = 'elytra.convex-auth.jwt'\n\n/**\n * Per-deployment token key (EC-179, vision AD-8). One project per backend, so a\n * session is scoped to its deployment: signing into Aurora's backend must not\n * carry a JWT to Northwind's. Keying the stored token by the deployment URL\n * makes each backend's session independent — switching to a project on a\n * not-yet-authenticated backend reads no token and the guard asks for sign-in.\n * (A future shared external IdP — WorkOS/Clerk — would issue one identity valid\n * on every deployment, collapsing this back to a single session.)\n */\nexport function tokenStorageKey(deploymentUrl?: string): string {\n return deploymentUrl ? `${TOKEN_STORAGE_PREFIX}::${deploymentUrl}` : TOKEN_STORAGE_PREFIX\n}\n\n/**\n * Best-effort \"is there a session for this deployment?\" — a synchronous token\n * presence check used to label per-backend projects in the switcher/launcher\n * (EC-179). It does not validate the token (an expired one still redirects to\n * sign-in on entry); it just answers \"has the user signed into this backend?\".\n */\nexport function hasStoredConvexSession(\n deploymentUrl: string,\n store: SessionStore = defaultStore(),\n): boolean {\n return store.getItem(tokenStorageKey(deploymentUrl)) !== null\n}\n\ninterface SignInActionResult {\n tokens?: { token: string; refreshToken: string } | null\n}\n\ninterface MeResult {\n id: string\n email: string\n name?: string\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class ConvexAuthAdapter implements AuthAdapter {\n private readonly tokenKey: string\n\n constructor(\n private readonly client: ConvexHttpClient,\n private readonly store: SessionStore = defaultStore(),\n /** Deployment URL the token is scoped to (per-backend sessions, EC-179). */\n deploymentUrl?: string,\n ) {\n this.tokenKey = tokenStorageKey(deploymentUrl)\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) return null\n this.client.setAuth(token)\n try {\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return null\n }\n return { user: this.toUser(me) }\n } catch {\n // Expired/invalid JWT or unreachable backend → treat as signed out.\n this.clearToken()\n return null\n }\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signIn', credentials)\n }\n\n async signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signUp', credentials)\n }\n\n /**\n * Shared password flow (EC-150/EC-179). `signIn` and `signUp` differ only by\n * the Convex Auth `flow` param; both return a token + load `users:me`.\n */\n private async authenticate(\n flow: 'signIn' | 'signUp',\n credentials: SignInCredentials,\n ): Promise<SignInResult> {\n let result: SignInActionResult\n try {\n result = (await this.client.action(actionRef('auth:signIn'), {\n provider: 'password',\n params: {\n email: credentials.email.trim().toLowerCase(),\n password: credentials.password,\n flow,\n },\n })) as SignInActionResult\n } catch (error) {\n // Convex Auth surfaces credential/registration problems as thrown errors.\n const message = error instanceof Error ? error.message : String(error)\n if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(message)) {\n return {\n ok: false,\n reason: 'account-exists',\n message: 'An account with this email already exists — sign in instead.',\n }\n }\n if (/InvalidSecret|InvalidAccountId|credential|password/i.test(message)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message:\n flow === 'signUp'\n ? 'That email or password can’t be used (password too short?).'\n : 'Email or password is incorrect.',\n }\n }\n const verb = flow === 'signUp' ? 'Sign-up' : 'Sign-in'\n return { ok: false, reason: 'unavailable', message: `${verb} is currently unavailable.` }\n }\n if (!result.tokens) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(this.tokenKey, result.tokens.token)\n this.client.setAuth(result.tokens.token)\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return { ok: false, reason: 'unavailable', message: 'Sign-in is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n }\n\n async signOut(): Promise<void> {\n try {\n await this.client.action(actionRef('auth:signOut'), {})\n } catch {\n // Already signed out / unreachable — clearing local state is what matters.\n }\n this.clearToken()\n }\n\n private toUser(me: MeResult): AuthSession['user'] {\n return {\n id: me.id,\n email: me.email,\n ...(me.name ? { name: me.name } : {}),\n // No global flags: project creation is config/CLI-owned (AD-11), so there\n // is no in-studio `create-projects` capability. Per-project roles come\n // from `projectMembers`.\n globalFlags: [],\n }\n }\n\n private clearToken(): void {\n this.store.removeItem(this.tokenKey)\n this.client.clearAuth()\n }\n}\n"]}
1
+ {"version":3,"file":"convex-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/convex-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ConvexHttpClient } from 'convex/browser'\nimport { actionRef, mutationRef, queryRef } from '../persistence/convex-client'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nimport type { SessionStore } from './local-auth-adapter'\n\n/**\n * Convex Auth implementation of the project-owned {@link AuthAdapter}\n * (EC-150). Decision record (also in `convex/auth.ts`): **Convex Auth** over\n * Clerk — self-contained on the Convex stack, sessions in our own tables, no\n * extra vendor; reachable only through this adapter so a swap stays local.\n *\n * Flow (server side lives in `convex/auth.ts` + `convex/users.ts`):\n * 1. `signIn` calls the `auth:signIn` action with the Password provider\n * (`flow: 'signIn'`); on success Convex returns a JWT + refresh token.\n * 2. The JWT is set on the *shared* Convex client (`client.setAuth`), so every\n * subsequent persistence query/mutation runs authenticated and the\n * server-side `requireRole` guards see the caller.\n * 3. `currentSession` restores the JWT from storage and asks `users:me`.\n * 4. `signOut` calls `auth:signOut` and clears the JWT.\n *\n * v1 scope note: the stored JWT is short-lived and this adapter does not yet\n * run the refresh-token loop (the official `@convex-dev/auth/react` client\n * does); an expired session simply resolves to \"signed out\" and the user signs\n * in again. Live-deployment hardening is EC-156. No live deployment exists in\n * this repo — this file is typecheck-verified and exercised against the local\n * adapter's shared interface.\n */\n\nconst TOKEN_STORAGE_PREFIX = 'elytra.convex-auth.jwt'\n\n/**\n * Classify a thrown sign-in error as a DEPLOYMENT-setup failure rather than a\n * credential problem (EC-257). Matches the Convex Auth signing-path throws:\n * `requireEnv('JWT_PRIVATE_KEY'|'JWKS'|'CONVEX_SITE_URL')` (→ \"Missing environment\n * variable …\") and `importPKCS8` on a malformed key. Deliberately narrow — a genuine\n * wrong-password error carries none of these tokens — and checked BEFORE the credential\n * regex so it always wins. Note: a PRODUCTION deployment redacts non-`ConvexError`\n * messages to a generic \"Server Error\", which this can't match; there the honest\n * fallback is the neutral \"unavailable\" branch (never \"password too short\").\n */\nexport function isDeploymentMisconfigured(message: string): boolean {\n return /JWT_PRIVATE_KEY|JWKS|CONVEX_SITE_URL|Missing environment variable|PKCS8|private key/i.test(\n message,\n )\n}\n\n/**\n * Per-deployment token key (EC-179, vision AD-8). One project per backend, so a\n * session is scoped to its deployment: signing into Aurora's backend must not\n * carry a JWT to Northwind's. Keying the stored token by the deployment URL\n * makes each backend's session independent — switching to a project on a\n * not-yet-authenticated backend reads no token and the guard asks for sign-in.\n * (A future shared external IdP — WorkOS/Clerk — would issue one identity valid\n * on every deployment, collapsing this back to a single session.)\n */\nexport function tokenStorageKey(deploymentUrl?: string): string {\n return deploymentUrl ? `${TOKEN_STORAGE_PREFIX}::${deploymentUrl}` : TOKEN_STORAGE_PREFIX\n}\n\n/**\n * Best-effort \"is there a session for this deployment?\" — a synchronous token\n * presence check used to label per-backend projects in the switcher/launcher\n * (EC-179). It does not validate the token (an expired one still redirects to\n * sign-in on entry); it just answers \"has the user signed into this backend?\".\n */\nexport function hasStoredConvexSession(\n deploymentUrl: string,\n store: SessionStore = defaultStore(),\n): boolean {\n return store.getItem(tokenStorageKey(deploymentUrl)) !== null\n}\n\ninterface SignInActionResult {\n tokens?: { token: string; refreshToken: string } | null\n}\n\ninterface MeResult {\n id: string\n email: string\n name?: string\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class ConvexAuthAdapter implements AuthAdapter {\n private readonly tokenKey: string\n\n constructor(\n private readonly client: ConvexHttpClient,\n private readonly store: SessionStore = defaultStore(),\n /** Deployment URL the token is scoped to (per-backend sessions, EC-179). */\n deploymentUrl?: string,\n ) {\n this.tokenKey = tokenStorageKey(deploymentUrl)\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) return null\n this.client.setAuth(token)\n try {\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return null\n }\n return { user: this.toUser(me) }\n } catch {\n // Expired/invalid JWT or unreachable backend → treat as signed out.\n this.clearToken()\n return null\n }\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signIn', credentials)\n }\n\n async signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.authenticate('signUp', credentials)\n }\n\n /**\n * Shared password flow (EC-150/EC-179). `signIn` and `signUp` differ only by\n * the Convex Auth `flow` param; both return a token + load `users:me`.\n */\n private async authenticate(\n flow: 'signIn' | 'signUp',\n credentials: SignInCredentials,\n ): Promise<SignInResult> {\n let result: SignInActionResult\n try {\n result = (await this.client.action(actionRef('auth:signIn'), {\n provider: 'password',\n params: {\n email: credentials.email.trim().toLowerCase(),\n password: credentials.password,\n flow,\n },\n })) as SignInActionResult\n } catch (error) {\n // Convex Auth surfaces credential/registration problems as thrown errors.\n const message = error instanceof Error ? error.message : String(error)\n // EC-257: a DEPLOYMENT-misconfiguration — not a credential problem — must be\n // classified FIRST so it never falls through to the lying \"password too short\".\n // Convex Auth signs the session JWT with the deployment's `JWT_PRIVATE_KEY`\n // (+ `JWKS`/`CONVEX_SITE_URL`); when any is unset (`npx @convex-dev/auth`\n // never run) the `requireEnv`/`importPKCS8` call throws *after* the password\n // is validated, so EVERY sign-in fails regardless of the password. This is\n // the exact footgun that cost hours on the Konditorei proof (2026-06-22).\n if (isDeploymentMisconfigured(message)) {\n return {\n ok: false,\n reason: 'deployment-misconfigured',\n message:\n 'This deployment can’t sign you in: its auth signing key is not set. Run `npx @convex-dev/auth` against this deployment, then retry. (The deployment-setup check reports the same.)',\n }\n }\n // EC-324: an uninvited sign-up is rejected server-side (`beforeSessionCreation`\n // throws a `ConvexError('not-invited: …')`, whose data survives to the client\n // even in production). Classified BEFORE the credential regex so it never\n // degrades to \"password too short\".\n if (/not-invited|invite-only|invite only/i.test(message)) {\n return {\n ok: false,\n reason: 'not-invited',\n message:\n 'This deployment is invite-only. Ask an admin to invite your email, then create your account.',\n }\n }\n if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(message)) {\n return {\n ok: false,\n reason: 'account-exists',\n message: 'An account with this email already exists — sign in instead.',\n }\n }\n if (/InvalidSecret|InvalidAccountId|credential|password/i.test(message)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message:\n flow === 'signUp'\n ? 'That email or password can’t be used (password too short?).'\n : 'Email or password is incorrect.',\n }\n }\n const verb = flow === 'signUp' ? 'Sign-up' : 'Sign-in'\n return { ok: false, reason: 'unavailable', message: `${verb} is currently unavailable.` }\n }\n if (!result.tokens) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(this.tokenKey, result.tokens.token)\n this.client.setAuth(result.tokens.token)\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n this.clearToken()\n return { ok: false, reason: 'unavailable', message: 'Sign-in is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n }\n\n async signOut(): Promise<void> {\n try {\n await this.client.action(actionRef('auth:signOut'), {})\n } catch {\n // Already signed out / unreachable — clearing local state is what matters.\n }\n this.clearToken()\n }\n\n /**\n * Self-service identity edit (EC-240 H-tail item 2). Patches the caller's OWN\n * auth `users` row (name / avatar) via the self-scoped `users:updateProfile`\n * mutation, then re-reads `users:me` so the returned session carries the new\n * name. Auth-owned fields never flow through the members-rail `users.save`.\n */\n async updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.mutation(mutationRef('users:updateProfile'), {\n ...(input.name !== undefined ? { name: input.name } : {}),\n ...(input.avatar !== undefined ? { avatar: input.avatar } : {}),\n })\n const me = (await this.client.query(queryRef('users:me'), {})) as MeResult | null\n if (!me) {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n return { ok: true, session: { user: this.toUser(me) } }\n } catch {\n return { ok: false, reason: 'unavailable', message: 'Profile update is currently unavailable.' }\n }\n }\n\n /**\n * Self-service password change (EC-240 H-tail item 3). Delegates entirely to\n * the provider via the self-scoped `users:changePassword` ACTION (which calls\n * Convex Auth's `retrieveAccount` to verify the current password, then\n * `modifyAccountCredentials` to set the new one). **Seam only:** the raw\n * password values are forwarded to the provider and NEVER logged or inspected\n * here.\n */\n async changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n const token = this.store.getItem(this.tokenKey)\n if (!token) {\n return { ok: false, reason: 'not-authenticated', message: 'You are not signed in.' }\n }\n this.client.setAuth(token)\n try {\n await this.client.action(actionRef('users:changePassword'), {\n currentPassword: input.currentPassword,\n newPassword: input.newPassword,\n })\n return { ok: true }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n if (isDeploymentMisconfigured(message)) {\n return {\n ok: false,\n reason: 'unavailable',\n message:\n 'This deployment can’t change your password: its auth signing key is not set. Run `npx @convex-dev/auth`.',\n }\n }\n if (/InvalidSecret|InvalidAccountId|current password|incorrect/i.test(message)) {\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Your current password is incorrect.',\n }\n }\n if (/at least|too short|8 characters|password requirement|weak/i.test(message)) {\n return {\n ok: false,\n reason: 'weak-password',\n message: 'Choose a stronger password (at least 8 characters).',\n }\n }\n return { ok: false, reason: 'unavailable', message: 'Password change is currently unavailable.' }\n }\n }\n\n private toUser(me: MeResult): AuthSession['user'] {\n return {\n id: me.id,\n email: me.email,\n ...(me.name ? { name: me.name } : {}),\n // No global flags: project creation is config/CLI-owned (AD-11), so there\n // is no in-studio `create-projects` capability. Per-project roles come\n // from `projectMembers`.\n globalFlags: [],\n }\n }\n\n private clearToken(): void {\n this.store.removeItem(this.tokenKey)\n this.client.clearAuth()\n }\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AuthAdapter, AuthSession, SignInCredentials, SignInResult } from './auth-adapter';
1
+ import type { AuthAdapter, AuthSession, ChangePasswordInput, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult } from './auth-adapter';
2
2
  /**
3
3
  * Dev-only unattended-auth decorator (testing de-friction). Wraps any
4
4
  * {@link AuthAdapter}: when no session can be restored, it transparently signs
@@ -22,4 +22,6 @@ export declare class DevAutoLoginAdapter implements AuthAdapter {
22
22
  signIn(credentials: SignInCredentials): Promise<SignInResult>;
23
23
  signUp(credentials: SignInCredentials): Promise<SignInResult>;
24
24
  signOut(): Promise<void>;
25
+ updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult>;
26
+ changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult>;
25
27
  }
@@ -36,5 +36,11 @@ export class DevAutoLoginAdapter {
36
36
  signOut() {
37
37
  return this.inner.signOut();
38
38
  }
39
+ updateProfile(input) {
40
+ return this.inner.updateProfile(input);
41
+ }
42
+ changePassword(input) {
43
+ return this.inner.changePassword(input);
44
+ }
39
45
  }
40
46
  //# sourceMappingURL=dev-auto-login-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev-auto-login-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/dev-auto-login-adapter.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,KAAkB,EAClB,WAA8B;QAD9B,UAAK,GAAL,KAAK,CAAa;QAClB,gBAAW,GAAX,WAAW,CAAmB;IAC9C,CAAC;IAEJ,KAAK,CAAC,cAAc;QAClB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAA;QAClD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1C,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;IAC7B,CAAC;CACF","sourcesContent":["import type { AuthAdapter, AuthSession, SignInCredentials, SignInResult } from './auth-adapter'\n\n/**\n * Dev-only unattended-auth decorator (testing de-friction). Wraps any\n * {@link AuthAdapter}: when no session can be restored, it transparently signs\n * in with the configured dev credentials so the studio boots already\n * authenticated for local/automated testing.\n *\n * This is **not** an auth bypass — it performs a real {@link AuthAdapter.signIn},\n * so the resulting session is a genuine backend session and the server-side\n * role guards still apply (a non-member or wrong password surfaces as the normal\n * signed-out state, not silent access). It is only ever constructed in a dev\n * build (see `resolveDevAutoLogin`), so it cannot reach production.\n *\n * Every method but {@link currentSession} delegates unchanged; `currentSession`\n * adds the auto-sign-in fallback after the wrapped adapter reports no session.\n */\nexport class DevAutoLoginAdapter implements AuthAdapter {\n constructor(\n private readonly inner: AuthAdapter,\n private readonly credentials: SignInCredentials,\n ) {}\n\n async currentSession(): Promise<AuthSession | null> {\n const restored = await this.inner.currentSession()\n if (restored) return restored\n const result = await this.inner.signIn(this.credentials)\n return result.ok ? result.session : null\n }\n\n signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signIn(credentials)\n }\n\n signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signUp(credentials)\n }\n\n signOut(): Promise<void> {\n return this.inner.signOut()\n }\n}\n"]}
1
+ {"version":3,"file":"dev-auto-login-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/dev-auto-login-adapter.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;GAcG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,KAAkB,EAClB,WAA8B;QAD9B,UAAK,GAAL,KAAK,CAAa;QAClB,gBAAW,GAAX,WAAW,CAAmB;IAC9C,CAAC;IAEJ,KAAK,CAAC,cAAc;QAClB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,EAAE,CAAA;QAClD,IAAI,QAAQ;YAAE,OAAO,QAAQ,CAAA;QAC7B,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QACxD,OAAO,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAA;IAC1C,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,MAAM,CAAC,WAA8B;QACnC,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAAA;IACvC,CAAC;IAED,OAAO;QACL,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAA;IAC7B,CAAC;IAED,aAAa,CAAC,KAAyB;QACrC,OAAO,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,KAAK,CAAC,CAAA;IACxC,CAAC;IAED,cAAc,CAAC,KAA0B;QACvC,OAAO,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,CAAA;IACzC,CAAC;CACF","sourcesContent":["import type {\n AuthAdapter,\n AuthSession,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Dev-only unattended-auth decorator (testing de-friction). Wraps any\n * {@link AuthAdapter}: when no session can be restored, it transparently signs\n * in with the configured dev credentials so the studio boots already\n * authenticated for local/automated testing.\n *\n * This is **not** an auth bypass — it performs a real {@link AuthAdapter.signIn},\n * so the resulting session is a genuine backend session and the server-side\n * role guards still apply (a non-member or wrong password surfaces as the normal\n * signed-out state, not silent access). It is only ever constructed in a dev\n * build (see `resolveDevAutoLogin`), so it cannot reach production.\n *\n * Every method but {@link currentSession} delegates unchanged; `currentSession`\n * adds the auto-sign-in fallback after the wrapped adapter reports no session.\n */\nexport class DevAutoLoginAdapter implements AuthAdapter {\n constructor(\n private readonly inner: AuthAdapter,\n private readonly credentials: SignInCredentials,\n ) {}\n\n async currentSession(): Promise<AuthSession | null> {\n const restored = await this.inner.currentSession()\n if (restored) return restored\n const result = await this.inner.signIn(this.credentials)\n return result.ok ? result.session : null\n }\n\n signIn(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signIn(credentials)\n }\n\n signUp(credentials: SignInCredentials): Promise<SignInResult> {\n return this.inner.signUp(credentials)\n }\n\n signOut(): Promise<void> {\n return this.inner.signOut()\n }\n\n updateProfile(input: UpdateProfileInput): Promise<UpdateProfileResult> {\n return this.inner.updateProfile(input)\n }\n\n changePassword(input: ChangePasswordInput): Promise<ChangePasswordResult> {\n return this.inner.changePassword(input)\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import type { StudioBackendMode } from '../persistence/backend-mode';
2
2
  import type { AuthAdapter } from './auth-adapter';
3
- export type { AuthAdapter, AuthSession, AuthUser, SignInCredentials, SignInResult, } from './auth-adapter';
3
+ export type { AuthAdapter, AuthSession, AuthUser, ChangePasswordInput, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileInput, UpdateProfileResult, } from './auth-adapter';
4
4
  export { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter';
5
5
  export type { SessionStore } from './local-auth-adapter';
6
6
  export { ConvexAuthAdapter } from './convex-auth-adapter';
@@ -13,7 +13,7 @@ export type { AuthStatus, RouteGuardDecision } from './route-guard';
13
13
  * persistence adapter uses, so the session JWT authenticates every call); the
14
14
  * explicit playground mode means the deterministic local adapter with the
15
15
  * seeded dev users. A misconfigured backend is a hard error here too — never
16
- * a silent local fallback (the `BackendGate` renders it before this runs).
16
+ * a silent local fallback (the `DeploymentSetupGate` renders it before this runs).
17
17
  */
18
18
  export declare function selectAuthAdapter(): AuthAdapter;
19
19
  /** Mirrors `persistenceMode()` — auth and persistence always agree (EC-150). */
@@ -14,7 +14,7 @@ export { SIGN_IN_PATH, isPublicPath, postSignInTarget, resolveRouteGuard, sessio
14
14
  * persistence adapter uses, so the session JWT authenticates every call); the
15
15
  * explicit playground mode means the deterministic local adapter with the
16
16
  * seeded dev users. A misconfigured backend is a hard error here too — never
17
- * a silent local fallback (the `BackendGate` renders it before this runs).
17
+ * a silent local fallback (the `DeploymentSetupGate` renders it before this runs).
18
18
  */
19
19
  export function selectAuthAdapter() {
20
20
  const config = resolveBackendConfig();
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AASvD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAGtB;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QACxE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,gBAAgB,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAA;IACjF,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAoB;IAC5C,MAAM,WAAW,GAAG,mBAAmB,EAAE,CAAA;IACzC,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;AAC9E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ;IACtB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACnE,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,sBAAsB,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;AACvE,CAAC","sourcesContent":["import { getConvexClient } from '../persistence/convex-client'\nimport { resolveBackendConfig, resolveDevAutoLogin } from '../persistence/backend-mode'\nimport { deploymentUrlForSlug, resolveDeploymentUrl } from '../workspace'\nimport type { StudioBackendMode } from '../persistence/backend-mode'\nimport type { AuthAdapter } from './auth-adapter'\nimport { ConvexAuthAdapter, hasStoredConvexSession } from './convex-auth-adapter'\nimport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nimport { LocalAuthAdapter } from './local-auth-adapter'\n\nexport type {\n AuthAdapter,\n AuthSession,\n AuthUser,\n SignInCredentials,\n SignInResult,\n} from './auth-adapter'\nexport { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter'\nexport type { SessionStore } from './local-auth-adapter'\nexport { ConvexAuthAdapter } from './convex-auth-adapter'\nexport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nexport {\n SIGN_IN_PATH,\n isPublicPath,\n postSignInTarget,\n resolveRouteGuard,\n sessionPrincipal,\n} from './route-guard'\nexport type { AuthStatus, RouteGuardDecision } from './route-guard'\n\n/**\n * Select the auth backend (EC-150/EC-153), mirroring `selectPersistenceAdapter`\n * exactly: Convex mode means Convex Auth (over the same shared client the\n * persistence adapter uses, so the session JWT authenticates every call); the\n * explicit playground mode means the deterministic local adapter with the\n * seeded dev users. A misconfigured backend is a hard error here too — never\n * a silent local fallback (the `BackendGate` renders it before this runs).\n */\nexport function selectAuthAdapter(): AuthAdapter {\n const config = resolveBackendConfig()\n if (config.kind === 'convex') {\n // The active project (EC-179) selects the deployment; the session token is\n // scoped to that deployment URL so each backend keeps its own session.\n const url = resolveDeploymentUrl(config.url)\n const base = new ConvexAuthAdapter(getConvexClient(url), undefined, url)\n return withDevAutoLogin(base)\n }\n if (config.kind === 'playground') return withDevAutoLogin(new LocalAuthAdapter())\n throw new Error(config.detail)\n}\n\n/**\n * Dev-only: wrap an adapter so the studio auto-signs-in for unattended testing\n * when `VITE_ELYTRA_DEV_LOGIN` is configured in a dev build. No-op otherwise\n * (and dead-code-eliminated in production builds — see `resolveDevAutoLogin`).\n */\nfunction withDevAutoLogin(adapter: AuthAdapter): AuthAdapter {\n const credentials = resolveDevAutoLogin()\n return credentials ? new DevAutoLoginAdapter(adapter, credentials) : adapter\n}\n\n/** Mirrors `persistenceMode()` — auth and persistence always agree (EC-150). */\nexport function authMode(): StudioBackendMode {\n const config = resolveBackendConfig()\n if (config.kind === 'misconfigured') throw new Error(config.detail)\n return config.kind\n}\n\n/**\n * Whether the user has a session on a given project's backend (EC-179). One\n * project per backend, so this is the per-project sign-in state the switcher and\n * launcher surface (\"Sign in\" vs \"Open project\"). Playground mode is a single\n * local backend — always \"signed in\". Best-effort (token presence, see\n * {@link hasStoredConvexSession}); entry still enforces a valid session.\n */\nexport function hasProjectSession(slug: string): boolean {\n const config = resolveBackendConfig()\n if (config.kind !== 'convex') return true\n return hasStoredConvexSession(deploymentUrlForSlug(slug, config.url))\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAA;AAC9D,OAAO,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACvF,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAA;AAGzE,OAAO,EAAE,iBAAiB,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAA;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAA;AAavD,OAAO,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAA;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AAC9D,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,eAAe,CAAA;AAGtB;;;;;;;GAOG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QAC7B,2EAA2E;QAC3E,uEAAuE;QACvE,MAAM,GAAG,GAAG,oBAAoB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC5C,MAAM,IAAI,GAAG,IAAI,iBAAiB,CAAC,eAAe,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;QACxE,OAAO,gBAAgB,CAAC,IAAI,CAAC,CAAA;IAC/B,CAAC;IACD,IAAI,MAAM,CAAC,IAAI,KAAK,YAAY;QAAE,OAAO,gBAAgB,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAA;IACjF,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,OAAoB;IAC5C,MAAM,WAAW,GAAG,mBAAmB,EAAE,CAAA;IACzC,OAAO,WAAW,CAAC,CAAC,CAAC,IAAI,mBAAmB,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;AAC9E,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,QAAQ;IACtB,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe;QAAE,MAAM,IAAI,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACnE,OAAO,MAAM,CAAC,IAAI,CAAA;AACpB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,MAAM,GAAG,oBAAoB,EAAE,CAAA;IACrC,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACzC,OAAO,sBAAsB,CAAC,oBAAoB,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAA;AACvE,CAAC","sourcesContent":["import { getConvexClient } from '../persistence/convex-client'\nimport { resolveBackendConfig, resolveDevAutoLogin } from '../persistence/backend-mode'\nimport { deploymentUrlForSlug, resolveDeploymentUrl } from '../workspace'\nimport type { StudioBackendMode } from '../persistence/backend-mode'\nimport type { AuthAdapter } from './auth-adapter'\nimport { ConvexAuthAdapter, hasStoredConvexSession } from './convex-auth-adapter'\nimport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nimport { LocalAuthAdapter } from './local-auth-adapter'\n\nexport type {\n AuthAdapter,\n AuthSession,\n AuthUser,\n ChangePasswordInput,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileInput,\n UpdateProfileResult,\n} from './auth-adapter'\nexport { LocalAuthAdapter, LOCAL_DEV_CREDENTIALS } from './local-auth-adapter'\nexport type { SessionStore } from './local-auth-adapter'\nexport { ConvexAuthAdapter } from './convex-auth-adapter'\nexport { DevAutoLoginAdapter } from './dev-auto-login-adapter'\nexport {\n SIGN_IN_PATH,\n isPublicPath,\n postSignInTarget,\n resolveRouteGuard,\n sessionPrincipal,\n} from './route-guard'\nexport type { AuthStatus, RouteGuardDecision } from './route-guard'\n\n/**\n * Select the auth backend (EC-150/EC-153), mirroring `selectPersistenceAdapter`\n * exactly: Convex mode means Convex Auth (over the same shared client the\n * persistence adapter uses, so the session JWT authenticates every call); the\n * explicit playground mode means the deterministic local adapter with the\n * seeded dev users. A misconfigured backend is a hard error here too — never\n * a silent local fallback (the `DeploymentSetupGate` renders it before this runs).\n */\nexport function selectAuthAdapter(): AuthAdapter {\n const config = resolveBackendConfig()\n if (config.kind === 'convex') {\n // The active project (EC-179) selects the deployment; the session token is\n // scoped to that deployment URL so each backend keeps its own session.\n const url = resolveDeploymentUrl(config.url)\n const base = new ConvexAuthAdapter(getConvexClient(url), undefined, url)\n return withDevAutoLogin(base)\n }\n if (config.kind === 'playground') return withDevAutoLogin(new LocalAuthAdapter())\n throw new Error(config.detail)\n}\n\n/**\n * Dev-only: wrap an adapter so the studio auto-signs-in for unattended testing\n * when `VITE_ELYTRA_DEV_LOGIN` is configured in a dev build. No-op otherwise\n * (and dead-code-eliminated in production builds — see `resolveDevAutoLogin`).\n */\nfunction withDevAutoLogin(adapter: AuthAdapter): AuthAdapter {\n const credentials = resolveDevAutoLogin()\n return credentials ? new DevAutoLoginAdapter(adapter, credentials) : adapter\n}\n\n/** Mirrors `persistenceMode()` — auth and persistence always agree (EC-150). */\nexport function authMode(): StudioBackendMode {\n const config = resolveBackendConfig()\n if (config.kind === 'misconfigured') throw new Error(config.detail)\n return config.kind\n}\n\n/**\n * Whether the user has a session on a given project's backend (EC-179). One\n * project per backend, so this is the per-project sign-in state the switcher and\n * launcher surface (\"Sign in\" vs \"Open project\"). Playground mode is a single\n * local backend — always \"signed in\". Best-effort (token presence, see\n * {@link hasStoredConvexSession}); entry still enforces a valid session.\n */\nexport function hasProjectSession(slug: string): boolean {\n const config = resolveBackendConfig()\n if (config.kind !== 'convex') return true\n return hasStoredConvexSession(deploymentUrlForSlug(slug, config.url))\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { AuthAdapter, AuthSession, SignInCredentials, SignInResult } from './auth-adapter';
1
+ import type { AuthAdapter, AuthSession, ChangePasswordResult, SignInCredentials, SignInResult, UpdateProfileResult } from './auth-adapter';
2
2
  /**
3
3
  * Deterministic local auth (EC-150) for the in-memory persistence mode: the
4
4
  * whole sign-in → role-enforcement → sign-out flow works offline, in tests,
@@ -54,4 +54,6 @@ export declare class LocalAuthAdapter implements AuthAdapter {
54
54
  signIn(credentials: SignInCredentials): Promise<SignInResult>;
55
55
  signUp(): Promise<SignInResult>;
56
56
  signOut(): Promise<void>;
57
+ updateProfile(): Promise<UpdateProfileResult>;
58
+ changePassword(): Promise<ChangePasswordResult>;
57
59
  }
@@ -74,5 +74,21 @@ export class LocalAuthAdapter {
74
74
  async signOut() {
75
75
  this.store.removeItem(STORAGE_KEY);
76
76
  }
77
+ async updateProfile() {
78
+ // Playground users are fixed, documented fixtures — there is no persistence to
79
+ // write. Surfaced as a state, not a throw, so the account surface can explain it.
80
+ return {
81
+ ok: false,
82
+ reason: 'unsupported',
83
+ message: 'Profile editing isn’t available in playground mode.',
84
+ };
85
+ }
86
+ async changePassword() {
87
+ return {
88
+ ok: false,
89
+ reason: 'unsupported',
90
+ message: 'Password change isn’t available in playground mode.',
91
+ };
92
+ }
77
93
  }
78
94
  //# sourceMappingURL=local-auth-adapter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"local-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/local-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEV_USERS } from '@elytracms/core/persistence'\nimport type { AuthAdapter, AuthSession, SignInCredentials, SignInResult } from './auth-adapter'\n\n/**\n * Deterministic local auth (EC-150) for the in-memory persistence mode: the\n * whole sign-in → role-enforcement → sign-out flow works offline, in tests,\n * and in fixtures with zero backend setup. User ids are the canonical\n * `DEV_USERS` ids from `@elytracms/core/persistence`, which the seeded\n * `projectMembers` fixtures reference — so `admin@example.com` is admin,\n * `editor@…` editor, `viewer@…` viewer on every sample project.\n *\n * Dev credentials (fixed, documented, local-only):\n * - admin@example.com / elytra-admin\n * - editor@example.com / elytra-editor\n * - viewer@example.com / elytra-viewer\n *\n * Adminness is the `projectMembers` role, not a global flag — project creation\n * is config/CLI-owned now (AD-11), so there is no in-studio `create-projects`\n * capability to grant.\n *\n * The session survives reloads via injected storage (defaults to\n * `sessionStorage` in the browser, an in-memory map elsewhere).\n */\n\nexport const LOCAL_DEV_CREDENTIALS = [\n { user: DEV_USERS.admin, password: 'elytra-admin' },\n { user: DEV_USERS.editor, password: 'elytra-editor' },\n { user: DEV_USERS.viewer, password: 'elytra-viewer' },\n] as const\n\nconst STORAGE_KEY = 'elytra.local-auth.user-id'\n\n/** The storage slice the adapter needs (sessionStorage-compatible). */\nexport interface SessionStore {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class LocalAuthAdapter implements AuthAdapter {\n private readonly store: SessionStore\n\n constructor(store: SessionStore = defaultStore()) {\n this.store = store\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const userId = this.store.getItem(STORAGE_KEY)\n if (!userId) return null\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.id === userId)\n return entry ? { user: entry.user } : null\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n const email = credentials.email.trim().toLowerCase()\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.email === email)\n if (!entry || entry.password !== credentials.password) {\n // Deliberately identical for unknown email vs wrong password.\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(STORAGE_KEY, entry.user.id)\n return { ok: true, session: { user: entry.user } }\n }\n\n async signUp(): Promise<SignInResult> {\n // Playground mode has a fixed, documented set of dev users — there is no\n // registration. Surfaced as a state, not a throw, so the sign-in screen can\n // explain it.\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Sign-up isn’t available in playground mode — use the dev credentials below.',\n }\n }\n\n async signOut(): Promise<void> {\n this.store.removeItem(STORAGE_KEY)\n }\n}\n"]}
1
+ {"version":3,"file":"local-auth-adapter.js","sourceRoot":"","sources":["../../../src/lib/auth/local-auth-adapter.ts"],"names":[],"mappings":"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","sourcesContent":["import { DEV_USERS } from '@elytracms/core/persistence'\nimport type {\n AuthAdapter,\n AuthSession,\n ChangePasswordResult,\n SignInCredentials,\n SignInResult,\n UpdateProfileResult,\n} from './auth-adapter'\n\n/**\n * Deterministic local auth (EC-150) for the in-memory persistence mode: the\n * whole sign-in → role-enforcement → sign-out flow works offline, in tests,\n * and in fixtures with zero backend setup. User ids are the canonical\n * `DEV_USERS` ids from `@elytracms/core/persistence`, which the seeded\n * `projectMembers` fixtures reference — so `admin@example.com` is admin,\n * `editor@…` editor, `viewer@…` viewer on every sample project.\n *\n * Dev credentials (fixed, documented, local-only):\n * - admin@example.com / elytra-admin\n * - editor@example.com / elytra-editor\n * - viewer@example.com / elytra-viewer\n *\n * Adminness is the `projectMembers` role, not a global flag — project creation\n * is config/CLI-owned now (AD-11), so there is no in-studio `create-projects`\n * capability to grant.\n *\n * The session survives reloads via injected storage (defaults to\n * `sessionStorage` in the browser, an in-memory map elsewhere).\n */\n\nexport const LOCAL_DEV_CREDENTIALS = [\n { user: DEV_USERS.admin, password: 'elytra-admin' },\n { user: DEV_USERS.editor, password: 'elytra-editor' },\n { user: DEV_USERS.viewer, password: 'elytra-viewer' },\n] as const\n\nconst STORAGE_KEY = 'elytra.local-auth.user-id'\n\n/** The storage slice the adapter needs (sessionStorage-compatible). */\nexport interface SessionStore {\n getItem(key: string): string | null\n setItem(key: string, value: string): void\n removeItem(key: string): void\n}\n\nfunction defaultStore(): SessionStore {\n if (typeof window !== 'undefined' && window.sessionStorage) return window.sessionStorage\n const memory = new Map<string, string>()\n return {\n getItem: (key) => memory.get(key) ?? null,\n setItem: (key, value) => void memory.set(key, value),\n removeItem: (key) => void memory.delete(key),\n }\n}\n\nexport class LocalAuthAdapter implements AuthAdapter {\n private readonly store: SessionStore\n\n constructor(store: SessionStore = defaultStore()) {\n this.store = store\n }\n\n async currentSession(): Promise<AuthSession | null> {\n const userId = this.store.getItem(STORAGE_KEY)\n if (!userId) return null\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.id === userId)\n return entry ? { user: entry.user } : null\n }\n\n async signIn(credentials: SignInCredentials): Promise<SignInResult> {\n const email = credentials.email.trim().toLowerCase()\n const entry = LOCAL_DEV_CREDENTIALS.find((c) => c.user.email === email)\n if (!entry || entry.password !== credentials.password) {\n // Deliberately identical for unknown email vs wrong password.\n return {\n ok: false,\n reason: 'invalid-credentials',\n message: 'Email or password is incorrect.',\n }\n }\n this.store.setItem(STORAGE_KEY, entry.user.id)\n return { ok: true, session: { user: entry.user } }\n }\n\n async signUp(): Promise<SignInResult> {\n // Playground mode has a fixed, documented set of dev users — there is no\n // registration. Surfaced as a state, not a throw, so the sign-in screen can\n // explain it.\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Sign-up isn’t available in playground mode — use the dev credentials below.',\n }\n }\n\n async signOut(): Promise<void> {\n this.store.removeItem(STORAGE_KEY)\n }\n\n async updateProfile(): Promise<UpdateProfileResult> {\n // Playground users are fixed, documented fixtures — there is no persistence to\n // write. Surfaced as a state, not a throw, so the account surface can explain it.\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Profile editing isn’t available in playground mode.',\n }\n }\n\n async changePassword(): Promise<ChangePasswordResult> {\n return {\n ok: false,\n reason: 'unsupported',\n message: 'Password change isn’t available in playground mode.',\n }\n }\n}\n"]}
@@ -126,6 +126,13 @@ export const EN_MESSAGES = {
126
126
  'shell.backend.unavailable.title': msg('Backend unavailable'),
127
127
  'shell.backend.unavailable.body': msg('The configured Convex deployment did not respond. Your data is safe on the backend — the studio will not switch to local storage.'),
128
128
  'shell.backend.retryConnection': msg('Retry connection'),
129
+ // EC-258 — the unified deployment-setup boot gate.
130
+ 'shell.deploymentSetup.title': msg('Deployment setup'),
131
+ 'shell.deploymentSetup.checking': msg('Checking deployment…'),
132
+ 'shell.deploymentSetup.description': msg('The studio can’t boot until these are resolved. Fix each item below, then reload.'),
133
+ 'shell.deploymentSetup.issuesLabel': msg('Deployment setup checks', 'aria-label on the list of failing readiness checks.'),
134
+ 'shell.deploymentSetup.fixLabel': msg('Fix', 'Label before a copy-pasteable remediation command on the deployment-setup screen.'),
135
+ 'shell.deploymentSetup.retry': msg('Retry checks'),
129
136
  'shell.unsavedChanges.title': msg('Unsaved changes'),
130
137
  'shell.unsavedChanges.body': msg('You have unsaved changes. Save them before leaving, or discard them.'),
131
138
  'shell.unsavedChanges.saving': msg('Saving…'),
@@ -509,6 +516,44 @@ export const EN_MESSAGES = {
509
516
  'users.field.email.label': msg('Email'),
510
517
  'users.field.role.label': msg('Role'),
511
518
  // ─────────────────────────────────────────────────────────────────────────
519
+ // Invites (EC-324) — invite-based onboarding, admin action in the users rail
520
+ // ─────────────────────────────────────────────────────────────────────────
521
+ 'invites.invite': msg('Invite user'),
522
+ 'invites.pending': msg('Pending:', 'Label before the list of pending invite chips.'),
523
+ 'invites.dialogTitle': msg('Invite a user'),
524
+ 'invites.dialogBody': msg('They join by creating an account with this email. Open sign-up is closed — only invited emails can register.'),
525
+ 'invites.emailLabel': msg('Email'),
526
+ 'invites.roleLabel': msg('Role'),
527
+ 'invites.send': msg('Send invite'),
528
+ 'invites.sending': msg('Sending…'),
529
+ 'invites.sent': msg('Invited {email}.', 'Toast after an invite is created; {email} is the invitee.'),
530
+ 'invites.revoked': msg('Revoked the invite for {email}.'),
531
+ 'invites.revokeAria': msg('Revoke the invite for {email}', 'aria-label on the revoke (×) button of an invite chip.'),
532
+ // ─────────────────────────────────────────────────────────────────────────
533
+ // Account settings (EC-240 H-tail) — the member's OWN record + credentials
534
+ // ─────────────────────────────────────────────────────────────────────────
535
+ 'account.loading': msg('Loading your account…'),
536
+ 'account.loadFailed': msg('Your account failed to load'),
537
+ 'account.notMemberTitle': msg('No access yet'),
538
+ 'account.notMemberBody': msg('You are signed in but not yet a member of this deployment. Ask an admin to grant you a role.'),
539
+ // Account rail schema labels resolve through the catalog (msgLabel), like the users rail.
540
+ 'account.rail.label': msg('Account', 'Header label for the account-settings surface.'),
541
+ 'account.rail.labelSingular': msg('account'),
542
+ 'account.field.name.label': msg('Name'),
543
+ 'account.field.avatar.label': msg('Avatar URL'),
544
+ 'account.field.avatar.hint': msg('A link to your profile image.'),
545
+ 'account.field.email.label': msg('Email'),
546
+ 'account.field.role.label': msg('Role'),
547
+ 'account.security.title': msg('Sign-in & security'),
548
+ 'account.security.method': msg('You sign in with your email and password.'),
549
+ 'account.password.current': msg('Current password'),
550
+ 'account.password.new': msg('New password'),
551
+ 'account.password.confirm': msg('Confirm new password'),
552
+ 'account.password.change': msg('Change password'),
553
+ 'account.password.changing': msg('Changing…'),
554
+ 'account.password.changed': msg('Your password has been changed.'),
555
+ 'account.password.mismatch': msg('The new passwords don’t match.'),
556
+ // ─────────────────────────────────────────────────────────────────────────
512
557
  // Redirects (section)
513
558
  // ─────────────────────────────────────────────────────────────────────────
514
559
  'redirects.loadFailed': msg('Redirects failed to load'),