@robelest/convex-auth 0.0.4-preview.30 → 0.0.4-preview.31
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/dist/bin.js +125 -36
- package/dist/browser/index.d.ts +3 -13
- package/dist/browser/index.js +47 -12
- package/dist/browser/navigation.js +1 -1
- package/dist/browser/passkey.js +7 -7
- package/dist/browser/runtime.js +13 -15
- package/dist/client/core/types.d.ts +179 -63
- package/dist/client/core/types.js +6 -0
- package/dist/client/factors/totp.js +1 -1
- package/dist/client/index.d.ts +5 -4
- package/dist/client/index.js +115 -56
- package/dist/client/runtime/mutex.js +3 -2
- package/dist/component/_generated/component.d.ts +40 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/http.js +9 -0
- package/dist/component/index.d.ts +1 -1
- package/dist/component/model.d.ts +25 -25
- package/dist/component/model.js +2 -1
- package/dist/component/modules.js +1 -0
- package/dist/component/public/factors/passkeys.js +31 -1
- package/dist/component/public/identity/codes.js +1 -1
- package/dist/component/public/identity/tokens.js +2 -1
- package/dist/component/public/identity/verifiers.js +15 -5
- package/dist/component/public.js +2 -2
- package/dist/component/schema.d.ts +292 -290
- package/dist/component/schema.js +2 -1
- package/dist/core/index.d.ts +8 -3
- package/dist/core/index.js +7 -2
- package/dist/expo/index.d.ts +21 -0
- package/dist/expo/index.js +148 -0
- package/dist/expo/passkey.js +174 -0
- package/dist/providers/apple.d.ts +1 -1
- package/dist/providers/apple.js +6 -8
- package/dist/providers/custom.d.ts +1 -1
- package/dist/providers/custom.js +4 -7
- package/dist/providers/github.d.ts +1 -1
- package/dist/providers/github.js +5 -8
- package/dist/providers/google.d.ts +1 -1
- package/dist/providers/google.js +5 -8
- package/dist/providers/microsoft.d.ts +1 -1
- package/dist/providers/microsoft.js +5 -9
- package/dist/providers/password.d.ts +18 -37
- package/dist/providers/password.js +170 -115
- package/dist/providers/redirect.d.ts +1 -0
- package/dist/providers/redirect.js +20 -0
- package/dist/server/auth.d.ts +6 -7
- package/dist/server/auth.js +3 -2
- package/dist/server/{ctxCache.js → cache/context.js} +2 -2
- package/dist/server/{componentContext.d.ts → component/context.d.ts} +2 -2
- package/dist/server/context.js +3 -10
- package/dist/server/contract.d.ts +2 -87
- package/dist/server/contract.js +1 -1
- package/dist/server/cookies.js +25 -1
- package/dist/server/core.js +1 -1
- package/dist/server/errors.js +24 -1
- package/dist/server/{auth-context.d.ts → facade.d.ts} +3 -45
- package/dist/server/{auth-context.js → facade.js} +11 -12
- package/dist/server/http.d.ts +7 -7
- package/dist/server/http.js +36 -7
- package/dist/server/{convexIdentity.d.ts → identity/convex.d.ts} +3 -3
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.js +3 -2
- package/dist/server/mounts.d.ts +175 -22
- package/dist/server/mutations/code.js +7 -1
- package/dist/server/mutations/{credentialsSignIn.js → credentials/signin.js} +10 -10
- package/dist/server/mutations/index.js +1 -1
- package/dist/server/mutations/invalidate.js +11 -1
- package/dist/server/mutations/oauth.js +25 -27
- package/dist/server/mutations/signin.js +6 -0
- package/dist/server/mutations/signout.js +5 -0
- package/dist/server/mutations/store.js +1 -1
- package/dist/server/oauth/factory.js +11 -3
- package/dist/server/passkey.js +126 -110
- package/dist/server/prefetch.js +8 -1
- package/dist/server/redirects.js +11 -3
- package/dist/server/refresh.js +6 -1
- package/dist/server/runtime.d.ts +58 -36
- package/dist/server/runtime.js +333 -36
- package/dist/server/services/group.js +4 -0
- package/dist/server/sessions.js +1 -0
- package/dist/server/signin.js +8 -6
- package/dist/server/sso/domain.d.ts +159 -16
- package/dist/server/sso/domain.js +1 -1
- package/dist/server/sso/http.js +144 -60
- package/dist/server/sso/oidc.js +28 -12
- package/dist/server/sso/policy.js +30 -14
- package/dist/server/sso/provision.js +1 -1
- package/dist/server/sso/saml.js +18 -9
- package/dist/server/sso/scim.js +12 -4
- package/dist/server/sso/shared.js +5 -5
- package/dist/server/telemetry.js +3 -0
- package/dist/server/tokens.js +10 -2
- package/dist/server/totp.js +127 -100
- package/dist/server/types.d.ts +224 -151
- package/dist/server/url.js +1 -1
- package/dist/server/users.js +93 -53
- package/dist/server/wellknown.d.ts +130 -0
- package/dist/server/wellknown.js +195 -0
- package/dist/shared/errors.js +0 -1
- package/package.json +36 -4
- package/dist/server/oauth/index.js +0 -12
- package/dist/server/utils/dispatch.js +0 -36
- package/dist/shared/authResults.d.ts +0 -16
- /package/dist/server/{componentContext.js → component/context.js} +0 -0
- /package/dist/server/{convexIdentity.js → identity/convex.js} +0 -0
package/dist/client/index.js
CHANGED
|
@@ -59,12 +59,16 @@ function buildSignInRequestKey(provider, params) {
|
|
|
59
59
|
params
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
|
+
function formDataEntries(formData) {
|
|
63
|
+
return formData;
|
|
64
|
+
}
|
|
62
65
|
/**
|
|
63
66
|
* Create a framework-agnostic auth client.
|
|
64
67
|
*
|
|
65
68
|
* Returns an object with `signIn`, `signOut`, `onChange`, `state`, and any
|
|
66
|
-
* factor helpers enabled by your configured providers.
|
|
67
|
-
* passkey support is added by
|
|
69
|
+
* factor helpers enabled by your configured providers. Platform-specific
|
|
70
|
+
* passkey support is added by higher-level entrypoints such as
|
|
71
|
+
* `@robelest/convex-auth/browser`.
|
|
68
72
|
*
|
|
69
73
|
* ### SPA mode (default)
|
|
70
74
|
*
|
|
@@ -124,7 +128,6 @@ function client(options) {
|
|
|
124
128
|
}
|
|
125
129
|
const storage = options.storage !== void 0 ? options.storage : runtime.storage !== void 0 ? runtime.storage : null;
|
|
126
130
|
const proxyRuntime = proxy ? requireProxyRuntime() : null;
|
|
127
|
-
const replaceUrl = options.replaceUrl ?? (runtime.location ? (url$1) => runtime.location.replace(url$1) : (_url) => {});
|
|
128
131
|
function getLocation() {
|
|
129
132
|
if (typeof options.location === "function") return options.location();
|
|
130
133
|
if (options.location instanceof URL) return options.location;
|
|
@@ -151,16 +154,20 @@ function client(options) {
|
|
|
151
154
|
function cleanUrlParams(params) {
|
|
152
155
|
const loc = getLocation();
|
|
153
156
|
if (!loc) return;
|
|
157
|
+
if (!runtime.location) return;
|
|
154
158
|
const searchParams = new URLSearchParams(loc.search);
|
|
155
159
|
let changed = false;
|
|
156
160
|
for (const p of params) if (searchParams.has(p)) {
|
|
157
161
|
searchParams.delete(p);
|
|
158
162
|
changed = true;
|
|
159
163
|
}
|
|
160
|
-
if (changed)
|
|
164
|
+
if (changed) {
|
|
165
|
+
const next = searchParams.toString() ? `${loc.pathname}?${searchParams}` : loc.pathname;
|
|
166
|
+
runtime.location.replace(next);
|
|
167
|
+
}
|
|
161
168
|
}
|
|
162
169
|
const url = proxy ? void 0 : resolveUrl(convex, options.url);
|
|
163
|
-
const escapedNamespace = proxy
|
|
170
|
+
const escapedNamespace = (proxy ?? url).replace(/^https?:\/\//, "").replace(/[^a-zA-Z0-9]/g, "_");
|
|
164
171
|
const key = (name) => `${name}_${escapedNamespace}`;
|
|
165
172
|
const { get: storageGet, set: storageSet, remove: storageRemove } = createStorageHelpers({
|
|
166
173
|
storage,
|
|
@@ -197,6 +204,7 @@ function client(options) {
|
|
|
197
204
|
let authEpoch = 0;
|
|
198
205
|
let destroyed = false;
|
|
199
206
|
let activeSignIn = null;
|
|
207
|
+
let initializePromise = null;
|
|
200
208
|
const handshakeWaiters = /* @__PURE__ */ new Set();
|
|
201
209
|
let snapshot = {
|
|
202
210
|
phase: hasServerToken ? "authenticated" : isLoading ? "loading" : "unauthenticated",
|
|
@@ -204,7 +212,6 @@ function client(options) {
|
|
|
204
212
|
isAuthenticated: hasServerToken,
|
|
205
213
|
token
|
|
206
214
|
};
|
|
207
|
-
let handlingCodeFlow = false;
|
|
208
215
|
const settleHandshakeWaiters = (epoch, outcome) => {
|
|
209
216
|
for (const waiter of Array.from(handshakeWaiters)) {
|
|
210
217
|
if (waiter.epoch !== epoch) continue;
|
|
@@ -404,18 +411,19 @@ function client(options) {
|
|
|
404
411
|
* }
|
|
405
412
|
* ```
|
|
406
413
|
*
|
|
407
|
-
* @example OAuth (
|
|
414
|
+
* @example OAuth (returns a redirect URL)
|
|
408
415
|
* ```ts
|
|
409
|
-
* await auth.signIn('google');
|
|
416
|
+
* const result = await auth.signIn('google');
|
|
417
|
+
* if (result.kind === 'redirect') {
|
|
418
|
+
* window.location.href = result.redirect.toString();
|
|
419
|
+
* }
|
|
410
420
|
* ```
|
|
411
421
|
*/
|
|
412
422
|
const signIn = async (provider, args) => {
|
|
413
423
|
await persistInvite();
|
|
414
424
|
const params = args instanceof FormData ? (() => {
|
|
415
425
|
const formParams = {};
|
|
416
|
-
|
|
417
|
-
formParams[key$1] = typeof value === "string" ? value : value.name;
|
|
418
|
-
});
|
|
426
|
+
for (const [key$1, value] of formDataEntries(args)) formParams[key$1] = typeof value === "string" ? value : value.name;
|
|
419
427
|
return formParams;
|
|
420
428
|
})() : args ?? {};
|
|
421
429
|
const flow = typeof params.flow === "string" && params.flow.length > 0 ? params.flow : "signIn";
|
|
@@ -424,7 +432,6 @@ function client(options) {
|
|
|
424
432
|
if (result.kind === "redirect") {
|
|
425
433
|
const redirectUrl = new URL(result.redirect);
|
|
426
434
|
if (resultOptions.persistVerifier) await storageSet(VERIFIER_STORAGE_KEY, result.verifier);
|
|
427
|
-
if (runtime.location) await runtime.location.redirect(redirectUrl);
|
|
428
435
|
return {
|
|
429
436
|
kind: "redirect",
|
|
430
437
|
redirect: redirectUrl,
|
|
@@ -474,15 +481,24 @@ function client(options) {
|
|
|
474
481
|
persistVerifier: false
|
|
475
482
|
});
|
|
476
483
|
const verifier = await storageGet(VERIFIER_STORAGE_KEY) ?? void 0;
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
484
|
+
try {
|
|
485
|
+
const result = await convex.action(requireApiRefs().signIn, {
|
|
486
|
+
provider,
|
|
487
|
+
params,
|
|
488
|
+
verifier
|
|
489
|
+
});
|
|
490
|
+
if (params.code !== void 0) await storageRemove(VERIFIER_STORAGE_KEY);
|
|
491
|
+
return await handleSignInActionResult(result, {
|
|
492
|
+
shouldStore: true,
|
|
493
|
+
persistVerifier: true
|
|
494
|
+
});
|
|
495
|
+
} catch (error) {
|
|
496
|
+
if (params.code !== void 0) {
|
|
497
|
+
const convexCode = error instanceof ConvexError && typeof error.data?.code === "string" ? error.data.code : null;
|
|
498
|
+
if (convexCode !== null && ["INVALID_VERIFICATION_CODE", "INVALID_VERIFIER"].includes(convexCode)) await storageRemove(VERIFIER_STORAGE_KEY);
|
|
499
|
+
}
|
|
500
|
+
throw error;
|
|
501
|
+
}
|
|
486
502
|
})();
|
|
487
503
|
activeSignIn = {
|
|
488
504
|
key: signInKey,
|
|
@@ -531,7 +547,7 @@ function client(options) {
|
|
|
531
547
|
const withMutex = mutex ? (key$1, callback) => mutex.withKey(key$1, callback) : localMutex;
|
|
532
548
|
if (proxy) {
|
|
533
549
|
const tokenBeforeRefresh = token;
|
|
534
|
-
return await withMutex(
|
|
550
|
+
return await withMutex(`__convexAuthProxyRefresh_${escapedNamespace}`, async () => {
|
|
535
551
|
if (token !== tokenBeforeRefresh) return token;
|
|
536
552
|
try {
|
|
537
553
|
const result = await retryWithJitteredBackoff(() => proxyFetch({
|
|
@@ -556,41 +572,98 @@ function client(options) {
|
|
|
556
572
|
});
|
|
557
573
|
}
|
|
558
574
|
const tokenBeforeLockAcquisition = token;
|
|
559
|
-
return await withMutex(REFRESH_TOKEN_STORAGE_KEY, async () => {
|
|
575
|
+
return await withMutex(key(REFRESH_TOKEN_STORAGE_KEY), async () => {
|
|
560
576
|
const tokenAfterLockAcquisition = token;
|
|
561
577
|
if (tokenAfterLockAcquisition !== tokenBeforeLockAcquisition) return tokenAfterLockAcquisition;
|
|
562
578
|
const refreshToken = await storageGet(REFRESH_TOKEN_STORAGE_KEY) ?? null;
|
|
563
579
|
if (!refreshToken) {
|
|
564
|
-
|
|
580
|
+
await setToken({
|
|
581
|
+
shouldStore: true,
|
|
582
|
+
tokens: null,
|
|
583
|
+
resyncConvexAuth: false
|
|
584
|
+
});
|
|
565
585
|
return null;
|
|
566
586
|
}
|
|
567
587
|
await verifyCodeAndSetToken({ refreshToken }, { resyncConvexAuth: false });
|
|
568
588
|
return token;
|
|
569
589
|
});
|
|
570
590
|
};
|
|
571
|
-
const
|
|
572
|
-
if (
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
591
|
+
const resolveOAuthInput = (input) => {
|
|
592
|
+
if (input instanceof URL) return {
|
|
593
|
+
code: input.searchParams.get("code"),
|
|
594
|
+
cleanupUrl: input.searchParams.has("code") ? (() => {
|
|
595
|
+
const next = new URL(input.toString());
|
|
596
|
+
next.searchParams.delete("code");
|
|
597
|
+
return next;
|
|
598
|
+
})() : null
|
|
599
|
+
};
|
|
600
|
+
if (typeof input === "object") return {
|
|
601
|
+
code: input.code,
|
|
602
|
+
cleanupUrl: null
|
|
603
|
+
};
|
|
578
604
|
try {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
605
|
+
const url$1 = new URL(input);
|
|
606
|
+
return {
|
|
607
|
+
code: url$1.searchParams.get("code"),
|
|
608
|
+
cleanupUrl: url$1.searchParams.has("code") ? (() => {
|
|
609
|
+
const next = new URL(url$1.toString());
|
|
610
|
+
next.searchParams.delete("code");
|
|
611
|
+
return next;
|
|
612
|
+
})() : null
|
|
613
|
+
};
|
|
614
|
+
} catch {
|
|
615
|
+
return {
|
|
616
|
+
code: input,
|
|
617
|
+
cleanupUrl: null
|
|
618
|
+
};
|
|
585
619
|
}
|
|
586
620
|
};
|
|
621
|
+
const completeOAuth = async (input) => {
|
|
622
|
+
const { code, cleanupUrl } = resolveOAuthInput(input);
|
|
623
|
+
if (!code) return { handled: false };
|
|
624
|
+
if ((await signIn(void 0, { code })).kind !== "signedIn") throw new Error("OAuth code exchange did not complete sign-in.");
|
|
625
|
+
return {
|
|
626
|
+
handled: true,
|
|
627
|
+
cleanupUrl
|
|
628
|
+
};
|
|
629
|
+
};
|
|
587
630
|
const hydrateFromStorage = async () => {
|
|
588
631
|
const storedToken = await storageGet(JWT_STORAGE_KEY) ?? null;
|
|
589
632
|
await setToken({
|
|
590
633
|
shouldStore: false,
|
|
591
|
-
tokens: storedToken === null ? null : { token: storedToken }
|
|
634
|
+
tokens: storedToken === null ? null : { token: storedToken },
|
|
635
|
+
resyncConvexAuth: storedToken !== null
|
|
592
636
|
});
|
|
593
637
|
};
|
|
638
|
+
const initialize = async () => {
|
|
639
|
+
if (initializePromise !== null) return await initializePromise;
|
|
640
|
+
initializePromise = (async () => {
|
|
641
|
+
if (proxy) {
|
|
642
|
+
if (!hasServerToken && runtime.environment !== "server") try {
|
|
643
|
+
await fetchAccessToken({ forceRefreshToken: true });
|
|
644
|
+
} catch (error) {
|
|
645
|
+
logMessage("convex-auth/client", LOG_LEVELS.ERROR, ["[convex-auth] Proxy token refresh failed:", error]);
|
|
646
|
+
}
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
try {
|
|
650
|
+
await hydrateFromStorage();
|
|
651
|
+
} catch {
|
|
652
|
+
try {
|
|
653
|
+
await setToken({
|
|
654
|
+
shouldStore: false,
|
|
655
|
+
tokens: null,
|
|
656
|
+
resyncConvexAuth: false
|
|
657
|
+
});
|
|
658
|
+
} catch {}
|
|
659
|
+
}
|
|
660
|
+
})();
|
|
661
|
+
try {
|
|
662
|
+
await initializePromise;
|
|
663
|
+
} finally {
|
|
664
|
+
initializePromise = Promise.resolve();
|
|
665
|
+
}
|
|
666
|
+
};
|
|
594
667
|
/**
|
|
595
668
|
* Subscribe to auth state changes. Invokes the callback immediately
|
|
596
669
|
* with the current state, then again on every state transition.
|
|
@@ -619,29 +692,14 @@ function client(options) {
|
|
|
619
692
|
});
|
|
620
693
|
}) ?? null;
|
|
621
694
|
bindConvexAuth();
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
logMessage("convex-auth/client", LOG_LEVELS.ERROR, ["[convex-auth] Proxy token refresh failed:", error]);
|
|
625
|
-
});
|
|
626
|
-
} else (async () => {
|
|
627
|
-
try {
|
|
628
|
-
await hydrateFromStorage();
|
|
629
|
-
await handleCodeFlow();
|
|
630
|
-
} catch {
|
|
631
|
-
try {
|
|
632
|
-
await setToken({
|
|
633
|
-
shouldStore: false,
|
|
634
|
-
tokens: null
|
|
635
|
-
});
|
|
636
|
-
} catch {}
|
|
637
|
-
}
|
|
638
|
-
})().catch((error) => {
|
|
639
|
-
logMessage("convex-auth/client", LOG_LEVELS.ERROR, ["[convex-auth] SPA initialization failed:", error]);
|
|
695
|
+
initialize().catch((error) => {
|
|
696
|
+
logMessage("convex-auth/client", LOG_LEVELS.ERROR, ["[convex-auth] Client initialization failed:", error]);
|
|
640
697
|
});
|
|
641
698
|
return {
|
|
642
699
|
get state() {
|
|
643
700
|
return snapshot;
|
|
644
701
|
},
|
|
702
|
+
initialize,
|
|
645
703
|
param,
|
|
646
704
|
get invite() {
|
|
647
705
|
const pendingInvite = getPendingInvite();
|
|
@@ -652,6 +710,7 @@ function client(options) {
|
|
|
652
710
|
accept: acceptInvite
|
|
653
711
|
};
|
|
654
712
|
},
|
|
713
|
+
completeOAuth,
|
|
655
714
|
signIn,
|
|
656
715
|
signOut,
|
|
657
716
|
onChange,
|
|
@@ -7,7 +7,8 @@ async function localMutex(key, callback) {
|
|
|
7
7
|
const currentTail = new Promise((resolve) => {
|
|
8
8
|
releaseCurrent = resolve;
|
|
9
9
|
});
|
|
10
|
-
|
|
10
|
+
const currentTailPromise = previousTail.then(() => currentTail, () => currentTail);
|
|
11
|
+
mutexTails[key] = currentTailPromise;
|
|
11
12
|
try {
|
|
12
13
|
await previousTail;
|
|
13
14
|
} catch {}
|
|
@@ -15,7 +16,7 @@ async function localMutex(key, callback) {
|
|
|
15
16
|
return await callback();
|
|
16
17
|
} finally {
|
|
17
18
|
releaseCurrent?.();
|
|
18
|
-
if (mutexTails[key] ===
|
|
19
|
+
if (mutexTails[key] === currentTailPromise) delete mutexTails[key];
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -187,6 +187,23 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
187
187
|
transports?: Array<string>;
|
|
188
188
|
userId: string;
|
|
189
189
|
} | null, Name>;
|
|
190
|
+
passkeyGetById: FunctionReference<"query", "internal", {
|
|
191
|
+
passkeyId: string;
|
|
192
|
+
}, {
|
|
193
|
+
_creationTime: number;
|
|
194
|
+
_id: string;
|
|
195
|
+
algorithm: number;
|
|
196
|
+
backedUp: boolean;
|
|
197
|
+
counter: number;
|
|
198
|
+
createdAt: number;
|
|
199
|
+
credentialId: string;
|
|
200
|
+
deviceType: string;
|
|
201
|
+
lastUsedAt?: number;
|
|
202
|
+
name?: string;
|
|
203
|
+
publicKey: ArrayBuffer;
|
|
204
|
+
transports?: Array<string>;
|
|
205
|
+
userId: string;
|
|
206
|
+
} | null, Name>;
|
|
190
207
|
passkeyInsert: FunctionReference<"mutation", "internal", {
|
|
191
208
|
algorithm: number;
|
|
192
209
|
backedUp: boolean;
|
|
@@ -1788,6 +1805,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
1788
1805
|
};
|
|
1789
1806
|
verifiers: {
|
|
1790
1807
|
verifierCreate: FunctionReference<"mutation", "internal", {
|
|
1808
|
+
expirationTime?: number;
|
|
1791
1809
|
sessionId?: string;
|
|
1792
1810
|
signature?: string;
|
|
1793
1811
|
}, string, Name>;
|
|
@@ -1799,6 +1817,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
1799
1817
|
}, {
|
|
1800
1818
|
_creationTime: number;
|
|
1801
1819
|
_id: string;
|
|
1820
|
+
expirationTime?: number;
|
|
1802
1821
|
sessionId?: string;
|
|
1803
1822
|
signature?: string;
|
|
1804
1823
|
} | null, Name>;
|
|
@@ -1807,6 +1826,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
1807
1826
|
}, {
|
|
1808
1827
|
_creationTime: number;
|
|
1809
1828
|
_id: string;
|
|
1829
|
+
expirationTime?: number;
|
|
1810
1830
|
sessionId?: string;
|
|
1811
1831
|
signature?: string;
|
|
1812
1832
|
} | null, Name>;
|
|
@@ -2159,6 +2179,23 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
2159
2179
|
transports?: Array<string>;
|
|
2160
2180
|
userId: string;
|
|
2161
2181
|
} | null, Name>;
|
|
2182
|
+
passkeyGetById: FunctionReference<"query", "internal", {
|
|
2183
|
+
passkeyId: string;
|
|
2184
|
+
}, {
|
|
2185
|
+
_creationTime: number;
|
|
2186
|
+
_id: string;
|
|
2187
|
+
algorithm: number;
|
|
2188
|
+
backedUp: boolean;
|
|
2189
|
+
counter: number;
|
|
2190
|
+
createdAt: number;
|
|
2191
|
+
credentialId: string;
|
|
2192
|
+
deviceType: string;
|
|
2193
|
+
lastUsedAt?: number;
|
|
2194
|
+
name?: string;
|
|
2195
|
+
publicKey: ArrayBuffer;
|
|
2196
|
+
transports?: Array<string>;
|
|
2197
|
+
userId: string;
|
|
2198
|
+
} | null, Name>;
|
|
2162
2199
|
passkeyInsert: FunctionReference<"mutation", "internal", {
|
|
2163
2200
|
algorithm: number;
|
|
2164
2201
|
backedUp: boolean;
|
|
@@ -3150,6 +3187,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
3150
3187
|
verifier?: string;
|
|
3151
3188
|
} | null, Name>;
|
|
3152
3189
|
verifierCreate: FunctionReference<"mutation", "internal", {
|
|
3190
|
+
expirationTime?: number;
|
|
3153
3191
|
sessionId?: string;
|
|
3154
3192
|
signature?: string;
|
|
3155
3193
|
}, string, Name>;
|
|
@@ -3161,6 +3199,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
3161
3199
|
}, {
|
|
3162
3200
|
_creationTime: number;
|
|
3163
3201
|
_id: string;
|
|
3202
|
+
expirationTime?: number;
|
|
3164
3203
|
sessionId?: string;
|
|
3165
3204
|
signature?: string;
|
|
3166
3205
|
} | null, Name>;
|
|
@@ -3169,6 +3208,7 @@ type ComponentApi<Name extends string | undefined = string | undefined> = {
|
|
|
3169
3208
|
}, {
|
|
3170
3209
|
_creationTime: number;
|
|
3171
3210
|
_id: string;
|
|
3211
|
+
expirationTime?: number;
|
|
3172
3212
|
sessionId?: string;
|
|
3173
3213
|
signature?: string;
|
|
3174
3214
|
} | null, Name>;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as convex_server4 from "convex/server";
|
|
2
2
|
|
|
3
3
|
//#region src/component/convex.config.d.ts
|
|
4
|
-
declare const component:
|
|
4
|
+
declare const component: convex_server4.ComponentDefinition<any>;
|
|
5
5
|
//#endregion
|
|
6
6
|
export { component as default };
|
|
7
7
|
//# sourceMappingURL=convex.config.d.ts.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { AuthProviderConfig, ConvexAuthConfig, ConvexCredentialsConfig, CorsConfig, DeviceProviderConfig, EmailConfig, EmailUserConfig, GenericDoc, GroupConnectionPolicy, GroupConnectionPolicyPatch, HttpKeyContext, KeyScope, PhoneConfig, PhoneUserConfig, ScopeChecker } from "../server/types.js";
|
|
2
|
-
import { AuthConfig, AuthContext, AuthContextConfig, InferAuth, OptionalAuthContext, UserDoc } from "../server/
|
|
2
|
+
import { AuthConfig, AuthContext, AuthContextConfig, InferAuth, OptionalAuthContext, UserDoc } from "../server/facade.js";
|
|
3
3
|
import { HttpAuthContext, HttpAuthContextConfig, OptionalHttpAuthContext } from "../server/http.js";
|
|
4
4
|
import { AuthApi, createAuth } from "../server/auth.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as convex_values440 from "convex/values";
|
|
2
2
|
|
|
3
3
|
//#region src/component/model.d.ts
|
|
4
|
-
declare const vApiKeyDoc:
|
|
4
|
+
declare const vApiKeyDoc: convex_values440.VObject<{
|
|
5
5
|
lastUsedAt?: number | undefined;
|
|
6
6
|
expiresAt?: number | undefined;
|
|
7
7
|
rateLimit?: {
|
|
@@ -16,7 +16,7 @@ declare const vApiKeyDoc: convex_values480.VObject<{
|
|
|
16
16
|
_creationTime: number;
|
|
17
17
|
name: string;
|
|
18
18
|
revoked: boolean;
|
|
19
|
-
userId:
|
|
19
|
+
userId: convex_values440.GenericId<"User">;
|
|
20
20
|
prefix: string;
|
|
21
21
|
hashedKey: string;
|
|
22
22
|
scopes: {
|
|
@@ -24,43 +24,43 @@ declare const vApiKeyDoc: convex_values480.VObject<{
|
|
|
24
24
|
actions: string[];
|
|
25
25
|
}[];
|
|
26
26
|
createdAt: number;
|
|
27
|
-
_id:
|
|
27
|
+
_id: convex_values440.GenericId<"ApiKey">;
|
|
28
28
|
}, {
|
|
29
|
-
userId:
|
|
30
|
-
prefix:
|
|
31
|
-
hashedKey:
|
|
32
|
-
name:
|
|
33
|
-
scopes:
|
|
29
|
+
userId: convex_values440.VId<convex_values440.GenericId<"User">, "required">;
|
|
30
|
+
prefix: convex_values440.VString<string, "required">;
|
|
31
|
+
hashedKey: convex_values440.VString<string, "required">;
|
|
32
|
+
name: convex_values440.VString<string, "required">;
|
|
33
|
+
scopes: convex_values440.VArray<{
|
|
34
34
|
resource: string;
|
|
35
35
|
actions: string[];
|
|
36
|
-
}[],
|
|
36
|
+
}[], convex_values440.VObject<{
|
|
37
37
|
resource: string;
|
|
38
38
|
actions: string[];
|
|
39
39
|
}, {
|
|
40
|
-
resource:
|
|
41
|
-
actions:
|
|
40
|
+
resource: convex_values440.VString<string, "required">;
|
|
41
|
+
actions: convex_values440.VArray<string[], convex_values440.VString<string, "required">, "required">;
|
|
42
42
|
}, "required", "resource" | "actions">, "required">;
|
|
43
|
-
rateLimit:
|
|
43
|
+
rateLimit: convex_values440.VObject<{
|
|
44
44
|
maxRequests: number;
|
|
45
45
|
windowMs: number;
|
|
46
46
|
} | undefined, {
|
|
47
|
-
maxRequests:
|
|
48
|
-
windowMs:
|
|
47
|
+
maxRequests: convex_values440.VFloat64<number, "required">;
|
|
48
|
+
windowMs: convex_values440.VFloat64<number, "required">;
|
|
49
49
|
}, "optional", "maxRequests" | "windowMs">;
|
|
50
|
-
rateLimitState:
|
|
50
|
+
rateLimitState: convex_values440.VObject<{
|
|
51
51
|
attemptsLeft: number;
|
|
52
52
|
lastAttemptTime: number;
|
|
53
53
|
} | undefined, {
|
|
54
|
-
attemptsLeft:
|
|
55
|
-
lastAttemptTime:
|
|
54
|
+
attemptsLeft: convex_values440.VFloat64<number, "required">;
|
|
55
|
+
lastAttemptTime: convex_values440.VFloat64<number, "required">;
|
|
56
56
|
}, "optional", "attemptsLeft" | "lastAttemptTime">;
|
|
57
|
-
expiresAt:
|
|
58
|
-
lastUsedAt:
|
|
59
|
-
createdAt:
|
|
60
|
-
revoked:
|
|
61
|
-
metadata:
|
|
62
|
-
_id:
|
|
63
|
-
_creationTime:
|
|
57
|
+
expiresAt: convex_values440.VFloat64<number | undefined, "optional">;
|
|
58
|
+
lastUsedAt: convex_values440.VFloat64<number | undefined, "optional">;
|
|
59
|
+
createdAt: convex_values440.VFloat64<number, "required">;
|
|
60
|
+
revoked: convex_values440.VBoolean<boolean, "required">;
|
|
61
|
+
metadata: convex_values440.VAny<any, "optional", string>;
|
|
62
|
+
_id: convex_values440.VId<convex_values440.GenericId<"ApiKey">, "required">;
|
|
63
|
+
_creationTime: convex_values440.VFloat64<number, "required">;
|
|
64
64
|
}, "required", "_creationTime" | "name" | "revoked" | "lastUsedAt" | "expiresAt" | "userId" | "prefix" | "hashedKey" | "scopes" | "rateLimit" | "rateLimitState" | "createdAt" | "metadata" | "_id" | "rateLimit.maxRequests" | "rateLimit.windowMs" | "rateLimitState.attemptsLeft" | "rateLimitState.lastAttemptTime" | `metadata.${string}`>;
|
|
65
65
|
//#endregion
|
|
66
66
|
export { vApiKeyDoc };
|
package/dist/component/model.js
CHANGED
|
@@ -137,7 +137,8 @@ const vAccountDoc = v.object({
|
|
|
137
137
|
const vAuthVerifierDoc = v.object({
|
|
138
138
|
...vDocMeta(TABLES.AuthVerifier),
|
|
139
139
|
sessionId: v.optional(v.id(TABLES.Session)),
|
|
140
|
-
signature: v.optional(v.string())
|
|
140
|
+
signature: v.optional(v.string()),
|
|
141
|
+
expirationTime: v.optional(v.number())
|
|
141
142
|
});
|
|
142
143
|
const vVerificationCodeDoc = v.object({
|
|
143
144
|
...vDocMeta(TABLES.VerificationCode),
|
|
@@ -6,6 +6,7 @@ const modules = {
|
|
|
6
6
|
"./component/_generated/server.ts": () => import("./_generated/server.js"),
|
|
7
7
|
"./component/convex.config.ts": () => import("./convex.config.js"),
|
|
8
8
|
"./component/functions.ts": () => import("./functions.js"),
|
|
9
|
+
"./component/http.ts": () => import("./http.js"),
|
|
9
10
|
"./component/index.ts": () => import("./index.js"),
|
|
10
11
|
"./component/model.ts": () => import("./model.js"),
|
|
11
12
|
"./component/public.ts": () => import("./public.js"),
|
|
@@ -100,6 +100,36 @@ const passkeyGetByCredentialId = query({
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
/**
|
|
103
|
+
* Get a single passkey by its document ID.
|
|
104
|
+
*
|
|
105
|
+
* Performs a direct point lookup on the `Passkey` table. Returns `null`
|
|
106
|
+
* when no passkey exists with the given ID (e.g. it was already deleted).
|
|
107
|
+
* Useful when callers already hold a passkey ID and need to inspect the
|
|
108
|
+
* document — for example to capture the owning `userId` before deletion.
|
|
109
|
+
*
|
|
110
|
+
* @param passkeyId - The `_id` of the `Passkey` document to retrieve.
|
|
111
|
+
* @returns The `Passkey` document, or `null` if no passkey exists with the
|
|
112
|
+
* given ID.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```ts
|
|
116
|
+
* const passkey = await ctx.runQuery(
|
|
117
|
+
* components.auth.factors.passkeys.passkeyGetById,
|
|
118
|
+
* { passkeyId },
|
|
119
|
+
* );
|
|
120
|
+
* if (passkey === null) {
|
|
121
|
+
* throw new Error("Passkey not found");
|
|
122
|
+
* }
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
const passkeyGetById = query({
|
|
126
|
+
args: { passkeyId: v.id("Passkey") },
|
|
127
|
+
returns: v.union(vPasskeyDoc, v.null()),
|
|
128
|
+
handler: async (ctx, { passkeyId }) => {
|
|
129
|
+
return await ctx.db.get("Passkey", passkeyId);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
/**
|
|
103
133
|
* List all passkeys registered to a user.
|
|
104
134
|
*
|
|
105
135
|
* Retrieves every `Passkey` document associated with the given user via
|
|
@@ -234,5 +264,5 @@ const passkeyDelete = mutation({
|
|
|
234
264
|
});
|
|
235
265
|
|
|
236
266
|
//#endregion
|
|
237
|
-
export { passkeyDelete, passkeyGetByCredentialId, passkeyInsert, passkeyListByUserId, passkeyUpdateCounter, passkeyUpdateMeta };
|
|
267
|
+
export { passkeyDelete, passkeyGetByCredentialId, passkeyGetById, passkeyInsert, passkeyListByUserId, passkeyUpdateCounter, passkeyUpdateMeta };
|
|
238
268
|
//# sourceMappingURL=passkeys.js.map
|
|
@@ -56,7 +56,7 @@ const verificationCodeGetByCode = query({
|
|
|
56
56
|
args: { code: v.string() },
|
|
57
57
|
returns: v.union(vVerificationCodeDoc, v.null()),
|
|
58
58
|
handler: async (ctx, { code }) => {
|
|
59
|
-
return await ctx.db.query("VerificationCode").withIndex("code", (q) => q.eq("code", code)).
|
|
59
|
+
return await ctx.db.query("VerificationCode").withIndex("code", (q) => q.eq("code", code)).first();
|
|
60
60
|
}
|
|
61
61
|
});
|
|
62
62
|
/**
|
|
@@ -212,7 +212,8 @@ const refreshTokenExchange = mutation({
|
|
|
212
212
|
await Promise.all(tokens.map((token) => ctx.db.delete("RefreshToken", token._id)));
|
|
213
213
|
};
|
|
214
214
|
const refreshTokenDoc = await ctx.db.get("RefreshToken", args.refreshTokenId);
|
|
215
|
-
if (refreshTokenDoc === null || refreshTokenDoc.
|
|
215
|
+
if (refreshTokenDoc === null || refreshTokenDoc.sessionId !== args.sessionId) return null;
|
|
216
|
+
if (refreshTokenDoc.expirationTime < args.now) {
|
|
216
217
|
await cleanupSessionArtifacts();
|
|
217
218
|
return null;
|
|
218
219
|
}
|
|
@@ -3,6 +3,12 @@ import { mutation, query } from "../../functions.js";
|
|
|
3
3
|
import { v } from "convex/values";
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/identity/verifiers.ts
|
|
6
|
+
const DEFAULT_VERIFIER_TTL_MS = 1e3 * 60 * 15;
|
|
7
|
+
async function getUnexpiredVerifier(ctx, verifierId) {
|
|
8
|
+
const verifier = await ctx.db.get("AuthVerifier", verifierId);
|
|
9
|
+
if (verifier?.expirationTime !== void 0 && verifier.expirationTime < Date.now()) return null;
|
|
10
|
+
return verifier;
|
|
11
|
+
}
|
|
6
12
|
/**
|
|
7
13
|
* Create a new PKCE verifier, optionally linked to a session.
|
|
8
14
|
*
|
|
@@ -26,13 +32,15 @@ import { v } from "convex/values";
|
|
|
26
32
|
const verifierCreate = mutation({
|
|
27
33
|
args: {
|
|
28
34
|
sessionId: v.optional(v.id("Session")),
|
|
29
|
-
signature: v.optional(v.string())
|
|
35
|
+
signature: v.optional(v.string()),
|
|
36
|
+
expirationTime: v.optional(v.number())
|
|
30
37
|
},
|
|
31
38
|
returns: v.id("AuthVerifier"),
|
|
32
|
-
handler: async (ctx, { sessionId, signature }) => {
|
|
39
|
+
handler: async (ctx, { sessionId, signature, expirationTime }) => {
|
|
33
40
|
return await ctx.db.insert("AuthVerifier", {
|
|
34
41
|
sessionId,
|
|
35
|
-
signature
|
|
42
|
+
signature,
|
|
43
|
+
expirationTime: expirationTime ?? Date.now() + DEFAULT_VERIFIER_TTL_MS
|
|
36
44
|
});
|
|
37
45
|
}
|
|
38
46
|
});
|
|
@@ -60,7 +68,7 @@ const verifierGetById = query({
|
|
|
60
68
|
args: { verifierId: v.id("AuthVerifier") },
|
|
61
69
|
returns: v.union(vAuthVerifierDoc, v.null()),
|
|
62
70
|
handler: async (ctx, { verifierId }) => {
|
|
63
|
-
return await ctx
|
|
71
|
+
return await getUnexpiredVerifier(ctx, verifierId);
|
|
64
72
|
}
|
|
65
73
|
});
|
|
66
74
|
/**
|
|
@@ -89,7 +97,9 @@ const verifierGetBySignature = query({
|
|
|
89
97
|
args: { signature: v.string() },
|
|
90
98
|
returns: v.union(vAuthVerifierDoc, v.null()),
|
|
91
99
|
handler: async (ctx, { signature }) => {
|
|
92
|
-
|
|
100
|
+
const verifier = await ctx.db.query("AuthVerifier").withIndex("signature", (q) => q.eq("signature", signature)).unique();
|
|
101
|
+
if (verifier?.expirationTime !== void 0 && verifier.expirationTime < Date.now()) return null;
|
|
102
|
+
return verifier;
|
|
93
103
|
}
|
|
94
104
|
});
|
|
95
105
|
/**
|