@elytracms/studio 0.0.55 → 0.0.56
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.
package/convex/auth.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { convexAuth } from '@convex-dev/auth/server'
|
|
2
2
|
import { Password } from '@convex-dev/auth/providers/Password'
|
|
3
|
+
import { actionGeneric } from 'convex/server'
|
|
3
4
|
import type { AnyDataModel, GenericMutationCtx } from 'convex/server'
|
|
4
|
-
import { ConvexError } from 'convex/values'
|
|
5
|
+
import { ConvexError, v } from 'convex/values'
|
|
5
6
|
import type { GenericId } from 'convex/values'
|
|
6
7
|
import { classifyInviteSignup, normalizeInviteEmail } from '@elytracms/core/persistence'
|
|
7
8
|
import type { InviteSignupDecision } from '@elytracms/core/persistence'
|
|
@@ -9,6 +10,92 @@ import type { InviteSignupDecision } from '@elytracms/core/persistence'
|
|
|
9
10
|
/** The provider list `convexAuth` accepts, derived from its own signature. */
|
|
10
11
|
type ConvexAuthProviders = Parameters<typeof convexAuth>[0]['providers']
|
|
11
12
|
|
|
13
|
+
/**
|
|
14
|
+
* EC-363 — stable, secret-free sign-in failure codes. A PRODUCTION Convex
|
|
15
|
+
* deployment redacts a thrown error's `.message` to a generic "Server Error"
|
|
16
|
+
* before it reaches the client; only a `ConvexError`'s `.data` survives. But
|
|
17
|
+
* `@convex-dev/auth@0.0.94` throws its credential/env failures as PLAIN
|
|
18
|
+
* `Error`s, so on prod every wrong password collapsed onto the client's
|
|
19
|
+
* honest-generic fallback (EC-362's key finding). These codes ride in
|
|
20
|
+
* `ConvexError.data` across that redaction so the sign-in screen can tell the
|
|
21
|
+
* truth. Codes ONLY — never the raw library message (it can carry the account
|
|
22
|
+
* id / env-var names).
|
|
23
|
+
*/
|
|
24
|
+
export type StudioAuthErrorCode =
|
|
25
|
+
| 'invalid-credentials'
|
|
26
|
+
| 'too-many-attempts'
|
|
27
|
+
| 'account-exists'
|
|
28
|
+
| 'deployment-misconfigured'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Map a `@convex-dev/auth` plain-`Error` message to a stable code — or `null`
|
|
32
|
+
* when unrecognized (the caller then rethrows the original error untouched, so
|
|
33
|
+
* unknown failures keep today's honest-generic behavior; never misclassify).
|
|
34
|
+
*
|
|
35
|
+
* The matched throw sites, pinned in the installed `@convex-dev/auth@0.0.94`:
|
|
36
|
+
*
|
|
37
|
+
* - `retrieveAccount` → `throw new Error(result)` with `result` ∈
|
|
38
|
+
* `"InvalidSecret" | "InvalidAccountId" | "TooManyFailedAttempts"`
|
|
39
|
+
* (`dist/server/implementation/index.js`; the strings come from
|
|
40
|
+
* `mutations/retrieveAccountWithCredentials.js`). Thrown INSIDE the signIn
|
|
41
|
+
* action.
|
|
42
|
+
* - The `Password` provider's own in-action throws: `"Invalid password"`
|
|
43
|
+
* (default password requirements at sign-up) and `"Invalid credentials"`
|
|
44
|
+
* (`dist/providers/Password.js`).
|
|
45
|
+
* - `requireEnv` → ``throw new Error(`Missing environment variable \`${name}\``)``
|
|
46
|
+
* (`dist/server/utils.js`) and jose's `importPKCS8` PKCS8/private-key parse
|
|
47
|
+
* errors — the EC-257 misconfigured-deployment family. These fire inside the
|
|
48
|
+
* library's `auth:store` MUTATION (`generateToken`); the backend propagates
|
|
49
|
+
* sub-call errors to the calling action UNREDACTED even on prod (redaction is
|
|
50
|
+
* client-boundary only — `RedactedJsError` is constructed solely in the
|
|
51
|
+
* sync-worker/HTTP-API crates), so the action-seam catch still sees the token.
|
|
52
|
+
* - `createAccountFromCredentials` → ``Account ${account.id} already exists``
|
|
53
|
+
* (`dist/server/implementation/mutations/createAccountFromCredentials.js`),
|
|
54
|
+
* also via the store mutation.
|
|
55
|
+
*
|
|
56
|
+
* Order matters: the env/signing-key family is checked FIRST (those throws fire
|
|
57
|
+
* after password validation and must never degrade to a credential code —
|
|
58
|
+
* mirrors the client ladder, EC-257).
|
|
59
|
+
*/
|
|
60
|
+
export function classifyAuthLibraryError(message: string): StudioAuthErrorCode | null {
|
|
61
|
+
if (/Missing environment variable|JWT_PRIVATE_KEY|JWKS|PKCS8|private key/i.test(message)) {
|
|
62
|
+
return 'deployment-misconfigured'
|
|
63
|
+
}
|
|
64
|
+
if (/TooManyFailedAttempts/.test(message)) return 'too-many-attempts'
|
|
65
|
+
if (/already exists/.test(message)) return 'account-exists'
|
|
66
|
+
if (/InvalidSecret|InvalidAccountId|Invalid credentials|Invalid password/.test(message)) {
|
|
67
|
+
return 'invalid-credentials'
|
|
68
|
+
}
|
|
69
|
+
return null
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* EC-363 — the coding step for the signIn wrap: return a `ConvexError` whose
|
|
74
|
+
* `.data` carries a {@link StudioAuthErrorCode} for a recognized library throw,
|
|
75
|
+
* or the ORIGINAL error untouched for everything else.
|
|
76
|
+
*
|
|
77
|
+
* - An error already carrying `.data` (a `ConvexError`, e.g. the invite gate's
|
|
78
|
+
* `NOT_INVITED_REASON` crossing the store-mutation boundary) passes through
|
|
79
|
+
* unchanged — its data already survives prod redaction. Duck-typed, not
|
|
80
|
+
* `instanceof` (dual module instances, EC-357/EC-362).
|
|
81
|
+
* - Unrecognized errors pass through unchanged: which cases fail and how they
|
|
82
|
+
* read today is preserved exactly; when in doubt, the client's honest generic.
|
|
83
|
+
*/
|
|
84
|
+
export function toCodedSignInError(error: unknown): unknown {
|
|
85
|
+
if (
|
|
86
|
+
typeof error === 'object' &&
|
|
87
|
+
error !== null &&
|
|
88
|
+
'data' in error &&
|
|
89
|
+
(error as { data: unknown }).data !== undefined
|
|
90
|
+
) {
|
|
91
|
+
return error
|
|
92
|
+
}
|
|
93
|
+
const message = error instanceof Error ? error.message : typeof error === 'string' ? error : ''
|
|
94
|
+
const code = classifyAuthLibraryError(message)
|
|
95
|
+
if (code === null) return error
|
|
96
|
+
return new ConvexError({ code })
|
|
97
|
+
}
|
|
98
|
+
|
|
12
99
|
/** The (non-schema-bound) mutation ctx the `convexAuth` callbacks receive. */
|
|
13
100
|
type AuthCallbackCtx = GenericMutationCtx<AnyDataModel>
|
|
14
101
|
|
|
@@ -156,7 +243,7 @@ export async function gateSessionCreation(
|
|
|
156
243
|
* project-owned `AuthAdapter`, so swapping providers later is one factory call, not UI.
|
|
157
244
|
*/
|
|
158
245
|
export function defineStudioAuth(options?: { providers?: ConvexAuthProviders }) {
|
|
159
|
-
|
|
246
|
+
const api = convexAuth({
|
|
160
247
|
providers: options?.providers ?? [Password],
|
|
161
248
|
callbacks: {
|
|
162
249
|
/** EC-358: reject uninvited account creation BEFORE any row commits. */
|
|
@@ -169,4 +256,60 @@ export function defineStudioAuth(options?: { providers?: ConvexAuthProviders })
|
|
|
169
256
|
},
|
|
170
257
|
},
|
|
171
258
|
})
|
|
259
|
+
|
|
260
|
+
// EC-363: wrap the `auth:signIn` ACTION so the library's plain-`Error`
|
|
261
|
+
// credential/env throws reach the client as `ConvexError`s carrying a stable
|
|
262
|
+
// code (`.data` survives prod redaction; `.message` does not). The wrap is a
|
|
263
|
+
// pure delegate — same args validators, same mechanics, same success values;
|
|
264
|
+
// ONLY the thrown-error shape changes, and only for recognized library throws
|
|
265
|
+
// (see `toCodedSignInError`). Re-registering is required because the
|
|
266
|
+
// registered action's `invokeAction` closes over the ORIGINAL handler
|
|
267
|
+
// (mutating `_handler` after the fact would not take effect).
|
|
268
|
+
//
|
|
269
|
+
// `_handler` is set by every `actionGeneric` registration in the installed
|
|
270
|
+
// convex runtime (`dist/esm/server/impl/registration_impl.js`) but is not in
|
|
271
|
+
// the public type — hence the structural cast. The loud throw below runs at
|
|
272
|
+
// module-definition time, so a convex upgrade that drops the seam fails the
|
|
273
|
+
// functions push visibly instead of silently shipping unwrapped errors.
|
|
274
|
+
const rawSignInHandler = (
|
|
275
|
+
api.signIn as unknown as {
|
|
276
|
+
_handler?: (ctx: unknown, args: unknown) => Promise<unknown>
|
|
277
|
+
}
|
|
278
|
+
)._handler
|
|
279
|
+
if (typeof rawSignInHandler !== 'function') {
|
|
280
|
+
throw new Error(
|
|
281
|
+
'defineStudioAuth: convexAuth().signIn no longer exposes `_handler` — the EC-363 ' +
|
|
282
|
+
'coded-auth-error wrap cannot attach. Check the installed convex runtime.',
|
|
283
|
+
)
|
|
284
|
+
}
|
|
285
|
+
const signIn = actionGeneric({
|
|
286
|
+
// Mirrors the library's own `signIn` validators exactly (@convex-dev/auth
|
|
287
|
+
// 0.0.94, `dist/server/implementation/index.js`) so argument validation is
|
|
288
|
+
// unchanged.
|
|
289
|
+
args: {
|
|
290
|
+
provider: v.optional(v.string()),
|
|
291
|
+
params: v.optional(v.any()),
|
|
292
|
+
verifier: v.optional(v.string()),
|
|
293
|
+
refreshToken: v.optional(v.string()),
|
|
294
|
+
calledBy: v.optional(v.string()),
|
|
295
|
+
},
|
|
296
|
+
handler: async (ctx, args) => {
|
|
297
|
+
try {
|
|
298
|
+
return await rawSignInHandler(ctx, args)
|
|
299
|
+
} catch (error) {
|
|
300
|
+
const coded = toCodedSignInError(error)
|
|
301
|
+
if (coded !== error) {
|
|
302
|
+
// Keep the ORIGINAL failure visible in the deployment logs — the
|
|
303
|
+
// client now only receives the stable code.
|
|
304
|
+
console.error('auth:signIn failed:', error)
|
|
305
|
+
}
|
|
306
|
+
throw coded
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
// The delegate preserves the original action's runtime contract; align the
|
|
310
|
+
// exported type with it so consumers' `convex/auth.ts` re-exports and
|
|
311
|
+
// codegen see the exact same `signIn` surface as before.
|
|
312
|
+
}) as unknown as typeof api.signIn
|
|
313
|
+
|
|
314
|
+
return { ...api, signIn }
|
|
172
315
|
}
|
|
@@ -5,11 +5,15 @@ import type { SessionStore } from './local-auth-adapter.js';
|
|
|
5
5
|
* Classify a thrown sign-in error as a DEPLOYMENT-setup failure rather than a
|
|
6
6
|
* credential problem (EC-257). Matches the Convex Auth signing-path throws:
|
|
7
7
|
* `requireEnv('JWT_PRIVATE_KEY'|'JWKS'|'CONVEX_SITE_URL')` (→ "Missing environment
|
|
8
|
-
* variable …") and `importPKCS8` on a malformed key
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* variable …") and `importPKCS8` on a malformed key — plus the EC-363 server code
|
|
9
|
+
* `deployment-misconfigured` (`defineStudioAuth` wraps those library throws in a
|
|
10
|
+
* `ConvexError` whose `.data` survives prod redaction, so this classifies on
|
|
11
|
+
* PRODUCTION deployments too once their functions are redeployed). Deliberately
|
|
12
|
+
* narrow — a genuine wrong-password error carries none of these tokens — and checked
|
|
13
|
+
* BEFORE the credential regex so it always wins. On a prod deployment whose
|
|
14
|
+
* functions PREDATE EC-363 the plain-`Error` message is redacted to "Server Error",
|
|
15
|
+
* which this can't match; there the honest fallback is the neutral "unavailable"
|
|
16
|
+
* branch (never "password too short").
|
|
13
17
|
*/
|
|
14
18
|
export declare function isDeploymentMisconfigured(message: string): boolean;
|
|
15
19
|
/**
|
|
@@ -25,9 +29,11 @@ export declare function isDeploymentMisconfigured(message: string): boolean;
|
|
|
25
29
|
* (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps
|
|
26
30
|
* `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the
|
|
27
31
|
* only classifiable text that survives prod is the `.data` of a `ConvexError` —
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
32
|
+
* our invite gate's `ConvexError(NOT_INVITED_REASON)` and, since EC-363, the
|
|
33
|
+
* coded `ConvexError({ code })` that `defineStudioAuth` wraps around the
|
|
34
|
+
* library's credential/env throws. On DEV deployments nothing is redacted, so
|
|
35
|
+
* `.message` also carries the token (the regex ladder still matches);
|
|
36
|
+
* non-Convex errors only have a `.message`.
|
|
31
37
|
*
|
|
32
38
|
* Duck-types the `data` property rather than `instanceof ConvexError`: two
|
|
33
39
|
* copies of the `convex` module can coexist under Vite (transformed vs
|
|
@@ -39,16 +45,25 @@ type SignInFailure = Extract<SignInResult, {
|
|
|
39
45
|
ok: false;
|
|
40
46
|
}>;
|
|
41
47
|
/**
|
|
42
|
-
* Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362).
|
|
43
|
-
* regex ladder over {@link convexErrorClassifierText} output
|
|
48
|
+
* Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362 / EC-363).
|
|
49
|
+
* Runs the regex ladder over {@link convexErrorClassifierText} output
|
|
50
|
+
* (data-first).
|
|
51
|
+
*
|
|
52
|
+
* EC-363: `defineStudioAuth` now wraps `@convex-dev/auth`'s plain-`Error`
|
|
53
|
+
* credential/env throws server-side in a `ConvexError` carrying a stable code
|
|
54
|
+
* in `.data` (`{"code":"invalid-credentials" | "too-many-attempts" |
|
|
55
|
+
* "account-exists" | "deployment-misconfigured"}`). The data-first text folds
|
|
56
|
+
* that JSON in, so the SAME ladder classifies the codes (they survive prod
|
|
57
|
+
* redaction) AND the raw dev-shape messages — a deployment whose functions
|
|
58
|
+
* predate EC-363 still classifies exactly as before (dev: full message; prod:
|
|
59
|
+
* the honest generic below).
|
|
44
60
|
*
|
|
45
61
|
* Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited
|
|
46
62
|
* sign-up (EC-324) are each checked BEFORE the credential regex so neither
|
|
47
63
|
* degrades to the lying "password too short". The final fallback is HONEST
|
|
48
64
|
* ("unexpected server error — check the deployment logs"), never "unavailable":
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* them to `"Server Error"` with no `.data` and they legitimately land here.
|
|
65
|
+
* unknown errors — and any plain `Error` a pre-EC-363 prod deployment redacts
|
|
66
|
+
* to `"Server Error"` with no `.data` — legitimately land here.
|
|
52
67
|
*/
|
|
53
68
|
export declare function classifySignInError(flow: 'signIn' | 'signUp', text: string): SignInFailure;
|
|
54
69
|
type ChangePasswordFailure = Extract<ChangePasswordResult, {
|
|
@@ -26,14 +26,18 @@ const TOKEN_STORAGE_PREFIX = 'elytra.convex-auth.jwt';
|
|
|
26
26
|
* Classify a thrown sign-in error as a DEPLOYMENT-setup failure rather than a
|
|
27
27
|
* credential problem (EC-257). Matches the Convex Auth signing-path throws:
|
|
28
28
|
* `requireEnv('JWT_PRIVATE_KEY'|'JWKS'|'CONVEX_SITE_URL')` (→ "Missing environment
|
|
29
|
-
* variable …") and `importPKCS8` on a malformed key
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* variable …") and `importPKCS8` on a malformed key — plus the EC-363 server code
|
|
30
|
+
* `deployment-misconfigured` (`defineStudioAuth` wraps those library throws in a
|
|
31
|
+
* `ConvexError` whose `.data` survives prod redaction, so this classifies on
|
|
32
|
+
* PRODUCTION deployments too once their functions are redeployed). Deliberately
|
|
33
|
+
* narrow — a genuine wrong-password error carries none of these tokens — and checked
|
|
34
|
+
* BEFORE the credential regex so it always wins. On a prod deployment whose
|
|
35
|
+
* functions PREDATE EC-363 the plain-`Error` message is redacted to "Server Error",
|
|
36
|
+
* which this can't match; there the honest fallback is the neutral "unavailable"
|
|
37
|
+
* branch (never "password too short").
|
|
34
38
|
*/
|
|
35
39
|
export function isDeploymentMisconfigured(message) {
|
|
36
|
-
return /JWT_PRIVATE_KEY|JWKS|CONVEX_SITE_URL|Missing environment variable|PKCS8|private key/i.test(message);
|
|
40
|
+
return /JWT_PRIVATE_KEY|JWKS|CONVEX_SITE_URL|Missing environment variable|PKCS8|private key|deployment-misconfigured/i.test(message);
|
|
37
41
|
}
|
|
38
42
|
/**
|
|
39
43
|
* Reduce a thrown Convex error to the text used for classification (EC-362).
|
|
@@ -48,9 +52,11 @@ export function isDeploymentMisconfigured(message) {
|
|
|
48
52
|
* (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps
|
|
49
53
|
* `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the
|
|
50
54
|
* only classifiable text that survives prod is the `.data` of a `ConvexError` —
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
55
|
+
* our invite gate's `ConvexError(NOT_INVITED_REASON)` and, since EC-363, the
|
|
56
|
+
* coded `ConvexError({ code })` that `defineStudioAuth` wraps around the
|
|
57
|
+
* library's credential/env throws. On DEV deployments nothing is redacted, so
|
|
58
|
+
* `.message` also carries the token (the regex ladder still matches);
|
|
59
|
+
* non-Convex errors only have a `.message`.
|
|
54
60
|
*
|
|
55
61
|
* Duck-types the `data` property rather than `instanceof ConvexError`: two
|
|
56
62
|
* copies of the `convex` module can coexist under Vite (transformed vs
|
|
@@ -82,16 +88,25 @@ export function convexErrorClassifierText(error) {
|
|
|
82
88
|
return parts.join('\n');
|
|
83
89
|
}
|
|
84
90
|
/**
|
|
85
|
-
* Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362).
|
|
86
|
-
* regex ladder over {@link convexErrorClassifierText} output
|
|
91
|
+
* Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362 / EC-363).
|
|
92
|
+
* Runs the regex ladder over {@link convexErrorClassifierText} output
|
|
93
|
+
* (data-first).
|
|
94
|
+
*
|
|
95
|
+
* EC-363: `defineStudioAuth` now wraps `@convex-dev/auth`'s plain-`Error`
|
|
96
|
+
* credential/env throws server-side in a `ConvexError` carrying a stable code
|
|
97
|
+
* in `.data` (`{"code":"invalid-credentials" | "too-many-attempts" |
|
|
98
|
+
* "account-exists" | "deployment-misconfigured"}`). The data-first text folds
|
|
99
|
+
* that JSON in, so the SAME ladder classifies the codes (they survive prod
|
|
100
|
+
* redaction) AND the raw dev-shape messages — a deployment whose functions
|
|
101
|
+
* predate EC-363 still classifies exactly as before (dev: full message; prod:
|
|
102
|
+
* the honest generic below).
|
|
87
103
|
*
|
|
88
104
|
* Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited
|
|
89
105
|
* sign-up (EC-324) are each checked BEFORE the credential regex so neither
|
|
90
106
|
* degrades to the lying "password too short". The final fallback is HONEST
|
|
91
107
|
* ("unexpected server error — check the deployment logs"), never "unavailable":
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
* them to `"Server Error"` with no `.data` and they legitimately land here.
|
|
108
|
+
* unknown errors — and any plain `Error` a pre-EC-363 prod deployment redacts
|
|
109
|
+
* to `"Server Error"` with no `.data` — legitimately land here.
|
|
95
110
|
*/
|
|
96
111
|
export function classifySignInError(flow, text) {
|
|
97
112
|
// EC-257: a DEPLOYMENT-misconfiguration — its auth signing key is unset — must be
|
|
@@ -117,6 +132,20 @@ export function classifySignInError(flow, text) {
|
|
|
117
132
|
message: 'This deployment is invite-only. Ask an admin to invite your email, then create your account.',
|
|
118
133
|
};
|
|
119
134
|
}
|
|
135
|
+
// EC-363: the sign-in rate limit. New deployments code it server-side
|
|
136
|
+
// (`{"code":"too-many-attempts"}` survives prod redaction); dev/old
|
|
137
|
+
// deployments surface the library's plain `TooManyFailedAttempts`. Checked
|
|
138
|
+
// BEFORE the credential regex; truthful wait-and-retry copy on the existing
|
|
139
|
+
// `unavailable` reason (a throttled deployment "can't sign you in right
|
|
140
|
+
// now" — the adapter contract gains no new reason for a transient state).
|
|
141
|
+
if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {
|
|
142
|
+
return {
|
|
143
|
+
ok: false,
|
|
144
|
+
reason: 'unavailable',
|
|
145
|
+
message: 'Too many failed sign-in attempts — wait a moment, then try again.',
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
// The EC-363 code `account-exists` matches via `exists`.
|
|
120
149
|
if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(text)) {
|
|
121
150
|
return {
|
|
122
151
|
ok: false,
|
|
@@ -124,6 +153,7 @@ export function classifySignInError(flow, text) {
|
|
|
124
153
|
message: 'An account with this email already exists — sign in instead.',
|
|
125
154
|
};
|
|
126
155
|
}
|
|
156
|
+
// The EC-363 code `invalid-credentials` matches via `credential`.
|
|
127
157
|
if (/InvalidSecret|InvalidAccountId|credential|password/i.test(text)) {
|
|
128
158
|
return {
|
|
129
159
|
ok: false,
|
|
@@ -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, 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 * Reduce a thrown Convex error to the text used for classification (EC-362).\n *\n * **Read `.data` FIRST, then `.message`** — because a PRODUCTION Convex\n * deployment REDACTS a UDF's thrown-error `.message` to a generic\n * `\"[Request ID: …] Server Error\"`, but a `ConvexError`'s `.data` payload\n * survives to the client. Proven in the installed `convex@1.42.1` browser\n * client: on an `\"error\"` response `ConvexHttpClient.action` throws\n * `forwardErrorData(respJSON.errorData, new ConvexError(respJSON.errorMessage))`,\n * and `forwardErrorData` sets `error.data = jsonToConvex(errorData)`\n * (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps\n * `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the\n * only classifiable text that survives prod is the `.data` of a `ConvexError` —\n * e.g. our invite gate's `ConvexError(NOT_INVITED_REASON)`. On DEV deployments\n * nothing is redacted, so `.message` also carries the token (the regex ladder\n * still matches); non-Convex errors only have a `.message`.\n *\n * Duck-types the `data` property rather than `instanceof ConvexError`: two\n * copies of the `convex` module can coexist under Vite (transformed vs\n * pre-bundled), so `instanceof` can spuriously fail (EC-357). `.data` may be a\n * string (our gates) or a structured object (future gates) — both are folded in.\n */\nexport function convexErrorClassifierText(error: unknown): string {\n const parts: string[] = []\n if (typeof error === 'object' && error !== null && 'data' in error) {\n const data = (error as { data: unknown }).data\n if (typeof data === 'string') {\n parts.push(data)\n } else if (data !== null && data !== undefined) {\n try {\n parts.push(JSON.stringify(data))\n } catch {\n // Non-serializable data (cycles) — ignore; fall back to the message.\n }\n }\n }\n if (error instanceof Error) parts.push(error.message)\n else if (typeof error === 'string') parts.push(error)\n if (parts.length === 0) parts.push(String(error))\n return parts.join('\\n')\n}\n\ntype SignInFailure = Extract<SignInResult, { ok: false }>\n\n/**\n * Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362). Runs the\n * regex ladder over {@link convexErrorClassifierText} output (data-first).\n *\n * Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited\n * sign-up (EC-324) are each checked BEFORE the credential regex so neither\n * degrades to the lying \"password too short\". The final fallback is HONEST\n * (\"unexpected server error — check the deployment logs\"), never \"unavailable\":\n * on a prod deployment the credential/`InvalidAccountId`/env errors thrown by\n * `@convex-dev/auth` are PLAIN `Error`s (not `ConvexError`s), so prod redacts\n * them to `\"Server Error\"` with no `.data` and they legitimately land here.\n */\nexport function classifySignInError(flow: 'signIn' | 'signUp', text: string): SignInFailure {\n // EC-257: a DEPLOYMENT-misconfiguration — its auth signing key is unset — 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` never\n // run) the `requireEnv`/`importPKCS8` call throws AFTER the password is validated,\n // so EVERY sign-in fails regardless of the password.\n if (isDeploymentMisconfigured(text)) {\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 (`gateAccountCreation`/\n // `gateSessionCreation` throw `ConvexError(NOT_INVITED_REASON)`, whose `.data`\n // survives to the client even in production). Checked BEFORE the credential regex.\n if (/not-invited|invite-only|invite only/i.test(text)) {\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(text)) {\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(text)) {\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 {\n ok: false,\n reason: 'unavailable',\n message: `${verb} failed (unexpected server error) — check the deployment logs.`,\n }\n}\n\ntype ChangePasswordFailure = Extract<ChangePasswordResult, { ok: false }>\n\n/**\n * Pure password-change error classifier (EC-240 H-tail / EC-362). Same\n * data-first text + honest fallback discipline as {@link classifySignInError};\n * `deployment-misconfigured` collapses onto the `unavailable` reason\n * (`ChangePasswordResult` has no dedicated reason for it) with a specific\n * remediation message.\n */\nexport function classifyChangePasswordError(text: string): ChangePasswordFailure {\n if (isDeploymentMisconfigured(text)) {\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(text)) {\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(text)) {\n return {\n ok: false,\n reason: 'weak-password',\n message: 'Choose a stronger password (at least 8 characters).',\n }\n }\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Password change failed (unexpected server error) — check the deployment logs.',\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 // Classify from the DATA-FIRST text (a `ConvexError`'s `.data` survives prod\n // redaction, the redacted `.message` does not — EC-362), then apply the pure\n // regex ladder. See `convexErrorClassifierText` / `classifySignInError`.\n return classifySignInError(flow, convexErrorClassifierText(error))\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 // Data-first classification (EC-362): the change-password ACTION runs on the\n // same deployment, so a prod redaction erases the thrown `.message` the same\n // way — read a surviving `ConvexError.data` first.\n return classifyChangePasswordError(convexErrorClassifierText(error))\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
|
+
{"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 — plus the EC-363 server code\n * `deployment-misconfigured` (`defineStudioAuth` wraps those library throws in a\n * `ConvexError` whose `.data` survives prod redaction, so this classifies on\n * PRODUCTION deployments too once their functions are redeployed). Deliberately\n * narrow — a genuine wrong-password error carries none of these tokens — and checked\n * BEFORE the credential regex so it always wins. On a prod deployment whose\n * functions PREDATE EC-363 the plain-`Error` message is redacted to \"Server Error\",\n * which this can't match; there the honest fallback is the neutral \"unavailable\"\n * 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|deployment-misconfigured/i.test(\n message,\n )\n}\n\n/**\n * Reduce a thrown Convex error to the text used for classification (EC-362).\n *\n * **Read `.data` FIRST, then `.message`** — because a PRODUCTION Convex\n * deployment REDACTS a UDF's thrown-error `.message` to a generic\n * `\"[Request ID: …] Server Error\"`, but a `ConvexError`'s `.data` payload\n * survives to the client. Proven in the installed `convex@1.42.1` browser\n * client: on an `\"error\"` response `ConvexHttpClient.action` throws\n * `forwardErrorData(respJSON.errorData, new ConvexError(respJSON.errorMessage))`,\n * and `forwardErrorData` sets `error.data = jsonToConvex(errorData)`\n * (`dist/esm/browser/http_client.js`, ~L388-396 + L459-461). The server keeps\n * `errorData` for a `ConvexError` in prod while redacting `errorMessage`. So the\n * only classifiable text that survives prod is the `.data` of a `ConvexError` —\n * our invite gate's `ConvexError(NOT_INVITED_REASON)` and, since EC-363, the\n * coded `ConvexError({ code })` that `defineStudioAuth` wraps around the\n * library's credential/env throws. On DEV deployments nothing is redacted, so\n * `.message` also carries the token (the regex ladder still matches);\n * non-Convex errors only have a `.message`.\n *\n * Duck-types the `data` property rather than `instanceof ConvexError`: two\n * copies of the `convex` module can coexist under Vite (transformed vs\n * pre-bundled), so `instanceof` can spuriously fail (EC-357). `.data` may be a\n * string (our gates) or a structured object (future gates) — both are folded in.\n */\nexport function convexErrorClassifierText(error: unknown): string {\n const parts: string[] = []\n if (typeof error === 'object' && error !== null && 'data' in error) {\n const data = (error as { data: unknown }).data\n if (typeof data === 'string') {\n parts.push(data)\n } else if (data !== null && data !== undefined) {\n try {\n parts.push(JSON.stringify(data))\n } catch {\n // Non-serializable data (cycles) — ignore; fall back to the message.\n }\n }\n }\n if (error instanceof Error) parts.push(error.message)\n else if (typeof error === 'string') parts.push(error)\n if (parts.length === 0) parts.push(String(error))\n return parts.join('\\n')\n}\n\ntype SignInFailure = Extract<SignInResult, { ok: false }>\n\n/**\n * Pure sign-in/sign-up error classifier (EC-257 / EC-324 / EC-362 / EC-363).\n * Runs the regex ladder over {@link convexErrorClassifierText} output\n * (data-first).\n *\n * EC-363: `defineStudioAuth` now wraps `@convex-dev/auth`'s plain-`Error`\n * credential/env throws server-side in a `ConvexError` carrying a stable code\n * in `.data` (`{\"code\":\"invalid-credentials\" | \"too-many-attempts\" |\n * \"account-exists\" | \"deployment-misconfigured\"}`). The data-first text folds\n * that JSON in, so the SAME ladder classifies the codes (they survive prod\n * redaction) AND the raw dev-shape messages — a deployment whose functions\n * predate EC-363 still classifies exactly as before (dev: full message; prod:\n * the honest generic below).\n *\n * Order matters: a DEPLOYMENT-misconfiguration (EC-257) and an uninvited\n * sign-up (EC-324) are each checked BEFORE the credential regex so neither\n * degrades to the lying \"password too short\". The final fallback is HONEST\n * (\"unexpected server error — check the deployment logs\"), never \"unavailable\":\n * unknown errors — and any plain `Error` a pre-EC-363 prod deployment redacts\n * to `\"Server Error\"` with no `.data` — legitimately land here.\n */\nexport function classifySignInError(flow: 'signIn' | 'signUp', text: string): SignInFailure {\n // EC-257: a DEPLOYMENT-misconfiguration — its auth signing key is unset — 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` never\n // run) the `requireEnv`/`importPKCS8` call throws AFTER the password is validated,\n // so EVERY sign-in fails regardless of the password.\n if (isDeploymentMisconfigured(text)) {\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 (`gateAccountCreation`/\n // `gateSessionCreation` throw `ConvexError(NOT_INVITED_REASON)`, whose `.data`\n // survives to the client even in production). Checked BEFORE the credential regex.\n if (/not-invited|invite-only|invite only/i.test(text)) {\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 // EC-363: the sign-in rate limit. New deployments code it server-side\n // (`{\"code\":\"too-many-attempts\"}` survives prod redaction); dev/old\n // deployments surface the library's plain `TooManyFailedAttempts`. Checked\n // BEFORE the credential regex; truthful wait-and-retry copy on the existing\n // `unavailable` reason (a throttled deployment \"can't sign you in right\n // now\" — the adapter contract gains no new reason for a transient state).\n if (/too-many-attempts|TooManyFailedAttempts/i.test(text)) {\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Too many failed sign-in attempts — wait a moment, then try again.',\n }\n }\n // The EC-363 code `account-exists` matches via `exists`.\n if (flow === 'signUp' && /already|exists|InvalidAccountId/i.test(text)) {\n return {\n ok: false,\n reason: 'account-exists',\n message: 'An account with this email already exists — sign in instead.',\n }\n }\n // The EC-363 code `invalid-credentials` matches via `credential`.\n if (/InvalidSecret|InvalidAccountId|credential|password/i.test(text)) {\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 {\n ok: false,\n reason: 'unavailable',\n message: `${verb} failed (unexpected server error) — check the deployment logs.`,\n }\n}\n\ntype ChangePasswordFailure = Extract<ChangePasswordResult, { ok: false }>\n\n/**\n * Pure password-change error classifier (EC-240 H-tail / EC-362). Same\n * data-first text + honest fallback discipline as {@link classifySignInError};\n * `deployment-misconfigured` collapses onto the `unavailable` reason\n * (`ChangePasswordResult` has no dedicated reason for it) with a specific\n * remediation message.\n */\nexport function classifyChangePasswordError(text: string): ChangePasswordFailure {\n if (isDeploymentMisconfigured(text)) {\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(text)) {\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(text)) {\n return {\n ok: false,\n reason: 'weak-password',\n message: 'Choose a stronger password (at least 8 characters).',\n }\n }\n return {\n ok: false,\n reason: 'unavailable',\n message: 'Password change failed (unexpected server error) — check the deployment logs.',\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 // Classify from the DATA-FIRST text (a `ConvexError`'s `.data` survives prod\n // redaction, the redacted `.message` does not — EC-362), then apply the pure\n // regex ladder. See `convexErrorClassifierText` / `classifySignInError`.\n return classifySignInError(flow, convexErrorClassifierText(error))\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 // Data-first classification (EC-362): the change-password ACTION runs on the\n // same deployment, so a prod redaction erases the thrown `.message` the same\n // way — read a surviving `ConvexError.data` first.\n return classifyChangePasswordError(convexErrorClassifierText(error))\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"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elytracms/studio",
|
|
3
3
|
"author": "finaldream <hi@finaldream.de>",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.56",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "The Elytra studio engine (UI + lib + routes + data-only Convex schema) as an importable package. Extraction staged in EXTRACTION.md (EC-223); Stages 1-2 = UI primitives + router-free lib leaves.",
|
|
7
7
|
"license": "MIT",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"lucide-react": "^1.23.0",
|
|
142
142
|
"tailwind-merge": "^3.6.0",
|
|
143
143
|
"zod": "^4.4.3",
|
|
144
|
-
"@elytracms/core": "^0.0.
|
|
144
|
+
"@elytracms/core": "^0.0.56"
|
|
145
145
|
},
|
|
146
146
|
"peerDependencies": {
|
|
147
147
|
"@tanstack/react-router": "^1.170.0",
|