@robelest/convex-auth 0.0.4-preview.29 → 0.0.4-preview.30
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 +34 -1
- package/dist/browser/passkey.js +2 -2
- package/dist/client/core/types.d.ts +4 -15
- package/dist/client/factors/device.js +3 -3
- package/dist/client/factors/totp.js +8 -8
- package/dist/client/index.js +7 -7
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/model.d.ts +25 -25
- package/dist/component/schema.d.ts +289 -289
- package/dist/core/index.d.ts +5 -24
- package/dist/core/index.js +3 -16
- package/dist/server/auth-context.d.ts +1 -1
- package/dist/server/auth-context.js +14 -1
- package/dist/server/auth.d.ts +3 -17
- package/dist/server/auth.js +2 -7
- package/dist/server/config.js +10 -0
- package/dist/server/context.js +2 -4
- package/dist/server/contract.d.ts +1 -1
- package/dist/server/convexIdentity.d.ts +15 -0
- package/dist/server/convexIdentity.js +1 -0
- package/dist/server/core.js +0 -13
- package/dist/server/device.js +13 -12
- package/dist/server/env.js +10 -2
- package/dist/server/http.d.ts +1 -1
- package/dist/server/identity.js +30 -4
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/mounts.d.ts +79 -79
- package/dist/server/mutations/refresh.js +38 -7
- package/dist/server/mutations/signin.js +12 -2
- package/dist/server/mutations/signout.js +27 -10
- package/dist/server/mutations/store.js +1 -1
- package/dist/server/oauth/factory.js +2 -1
- package/dist/server/passkey.js +12 -13
- package/dist/server/prefetch.js +8 -8
- package/dist/server/runtime.d.ts +19 -25
- package/dist/server/runtime.js +7 -40
- package/dist/server/sessions.d.ts +2 -1
- package/dist/server/sessions.js +21 -11
- package/dist/server/signin.js +17 -16
- package/dist/server/sso/domain.d.ts +1 -1
- package/dist/server/telemetry.js +58 -0
- package/dist/server/tokens.js +24 -4
- package/dist/server/totp.js +13 -11
- package/dist/server/types.d.ts +58 -2
- package/dist/server/utils/span.js +10 -1
- package/dist/shared/authResults.d.ts +16 -0
- package/package.json +6 -3
- package/dist/server/constants.js +0 -6
|
@@ -2,8 +2,9 @@ import { authDb } from "../db.js";
|
|
|
2
2
|
import { log, maybeRedact } from "../log.js";
|
|
3
3
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
4
4
|
import { REFRESH_TOKEN_DIVIDER, REFRESH_TOKEN_REUSE_WINDOW_MS, parseRefreshToken, refreshTokenExpirationTime } from "../refresh.js";
|
|
5
|
-
import { withSpan } from "../utils/span.js";
|
|
5
|
+
import { setActiveSpanAttributes, withSpan } from "../utils/span.js";
|
|
6
6
|
import { finalizeSessionIssuance } from "../sessions.js";
|
|
7
|
+
import { buildRefreshIdentityAttributes } from "../telemetry.js";
|
|
7
8
|
import { v } from "convex/values";
|
|
8
9
|
|
|
9
10
|
//#region src/server/mutations/refresh.ts
|
|
@@ -18,14 +19,17 @@ const refreshSessionArgs = v.object({ refreshToken: v.string() });
|
|
|
18
19
|
*/
|
|
19
20
|
async function refreshSessionImpl(ctx, args, config) {
|
|
20
21
|
const db = authDb(ctx, config);
|
|
21
|
-
return withSpan("convex-auth.refresh.session", {
|
|
22
|
+
return withSpan("convex-auth.refresh.session", {
|
|
23
|
+
hasRefreshToken: true,
|
|
24
|
+
"auth.flow": "refresh"
|
|
25
|
+
}, async () => {
|
|
22
26
|
try {
|
|
23
27
|
let refreshTokenId;
|
|
24
28
|
let sessionId;
|
|
25
29
|
try {
|
|
26
30
|
({refreshTokenId, sessionId} = parseRefreshToken(args.refreshToken));
|
|
27
31
|
} catch {
|
|
28
|
-
throw new RefreshFailure("Failed to parse refresh token");
|
|
32
|
+
throw new RefreshFailure("parse_failure", "Failed to parse refresh token");
|
|
29
33
|
}
|
|
30
34
|
log("DEBUG", `refreshSessionImpl args: Token ID: ${maybeRedact(refreshTokenId)} Session ID: ${maybeRedact(sessionId)}`);
|
|
31
35
|
let exchanged;
|
|
@@ -38,16 +42,42 @@ async function refreshSessionImpl(ctx, args, config) {
|
|
|
38
42
|
reuseWindowMs: REFRESH_TOKEN_REUSE_WINDOW_MS
|
|
39
43
|
});
|
|
40
44
|
} catch {
|
|
41
|
-
throw new RefreshFailure("Failed to exchange refresh token");
|
|
45
|
+
throw new RefreshFailure("exchange_failure", "Failed to exchange refresh token");
|
|
42
46
|
}
|
|
43
|
-
if (exchanged === null)
|
|
47
|
+
if (exchanged === null) {
|
|
48
|
+
setActiveSpanAttributes({ "auth.refresh.result": "null" });
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
setActiveSpanAttributes({
|
|
52
|
+
"auth.refresh.result": "success",
|
|
53
|
+
...await buildRefreshIdentityAttributes(ctx, config, {
|
|
54
|
+
userId: exchanged.userId,
|
|
55
|
+
sessionId: exchanged.sessionId,
|
|
56
|
+
refreshTokenId: exchanged.refreshTokenId
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
const user = await db.users.getById(exchanged.userId);
|
|
44
60
|
return {
|
|
45
61
|
userId: exchanged.userId,
|
|
46
62
|
sessionId: exchanged.sessionId,
|
|
63
|
+
identity: {
|
|
64
|
+
subject: exchanged.userId,
|
|
65
|
+
sessionId: exchanged.sessionId,
|
|
66
|
+
...typeof user?.name === "string" ? { name: user.name } : null,
|
|
67
|
+
...typeof user?.email === "string" ? { email: user.email } : null,
|
|
68
|
+
...user?.emailVerificationTime !== void 0 ? { emailVerified: true } : user?.email !== void 0 ? { emailVerified: false } : null,
|
|
69
|
+
...typeof user?.image === "string" ? { picture: user.image } : null,
|
|
70
|
+
...typeof user?.phone === "string" ? { phoneNumber: user.phone } : null,
|
|
71
|
+
...user?.phoneVerificationTime !== void 0 ? { phoneNumberVerified: true } : user?.phone !== void 0 ? { phoneNumberVerified: false } : null
|
|
72
|
+
},
|
|
47
73
|
refreshToken: `${exchanged.refreshTokenId}${REFRESH_TOKEN_DIVIDER}${exchanged.sessionId}`
|
|
48
74
|
};
|
|
49
75
|
} catch (e) {
|
|
50
76
|
if (e instanceof RefreshFailure) {
|
|
77
|
+
setActiveSpanAttributes({
|
|
78
|
+
"auth.refresh.result": e.code,
|
|
79
|
+
"auth.refresh.failure_reason": e.reason
|
|
80
|
+
});
|
|
51
81
|
log("DEBUG", e.reason);
|
|
52
82
|
return null;
|
|
53
83
|
}
|
|
@@ -56,8 +86,9 @@ async function refreshSessionImpl(ctx, args, config) {
|
|
|
56
86
|
});
|
|
57
87
|
}
|
|
58
88
|
var RefreshFailure = class extends Error {
|
|
59
|
-
constructor(reason) {
|
|
89
|
+
constructor(code, reason) {
|
|
60
90
|
super(reason);
|
|
91
|
+
this.code = code;
|
|
61
92
|
this.reason = reason;
|
|
62
93
|
}
|
|
63
94
|
};
|
|
@@ -74,7 +105,7 @@ const callRefreshSession = async (ctx, args) => {
|
|
|
74
105
|
...args
|
|
75
106
|
} });
|
|
76
107
|
if (issuance === null || issuance.refreshToken === null) return null;
|
|
77
|
-
return
|
|
108
|
+
return await finalizeSessionIssuance(ctx.auth.config, issuance);
|
|
78
109
|
};
|
|
79
110
|
|
|
80
111
|
//#endregion
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { LOG_LEVELS } from "../../shared/log.js";
|
|
2
2
|
import { log } from "../log.js";
|
|
3
3
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
4
|
-
import { withSpan } from "../utils/span.js";
|
|
4
|
+
import { setActiveSpanAttributes, withSpan } from "../utils/span.js";
|
|
5
5
|
import { finalizeSessionIssuance, getAuthSessionId, issueSession } from "../sessions.js";
|
|
6
|
+
import { buildSignInIdentityAttributes } from "../telemetry.js";
|
|
6
7
|
import { v } from "convex/values";
|
|
7
8
|
|
|
8
9
|
//#region src/server/mutations/signin.ts
|
|
@@ -13,17 +14,26 @@ const signInArgs = v.object({
|
|
|
13
14
|
});
|
|
14
15
|
async function signInImpl(ctx, args, config) {
|
|
15
16
|
return withSpan("convex-auth.mutations.signIn", {
|
|
17
|
+
"auth.flow": "signIn",
|
|
16
18
|
hasExistingSession: args.sessionId !== void 0,
|
|
17
19
|
generateTokens: args.generateTokens
|
|
18
20
|
}, async () => {
|
|
19
21
|
log(LOG_LEVELS.DEBUG, "signInImpl args:", args);
|
|
20
22
|
const { userId, sessionId: existingSessionId, generateTokens } = args;
|
|
21
|
-
|
|
23
|
+
const issuance = await issueSession(ctx, config, {
|
|
22
24
|
userId,
|
|
23
25
|
existingSessionId,
|
|
24
26
|
replaceSessionId: existingSessionId === void 0 ? await getAuthSessionId(ctx) ?? void 0 : void 0,
|
|
25
27
|
generateTokens
|
|
26
28
|
});
|
|
29
|
+
setActiveSpanAttributes({
|
|
30
|
+
"auth.signin.result": "success",
|
|
31
|
+
...await buildSignInIdentityAttributes(ctx, config, {
|
|
32
|
+
userId: issuance.userId,
|
|
33
|
+
sessionId: issuance.sessionId
|
|
34
|
+
})
|
|
35
|
+
});
|
|
36
|
+
return issuance;
|
|
27
37
|
});
|
|
28
38
|
}
|
|
29
39
|
/**
|
|
@@ -1,19 +1,36 @@
|
|
|
1
1
|
import { authDb } from "../db.js";
|
|
2
2
|
import { AUTH_STORE_REF } from "./store/refs.js";
|
|
3
|
+
import { setActiveSpanAttributes, withSpan } from "../utils/span.js";
|
|
3
4
|
import { deleteSession, getAuthSessionId } from "../sessions.js";
|
|
5
|
+
import { buildSignOutIdentityAttributes } from "../telemetry.js";
|
|
4
6
|
|
|
5
7
|
//#region src/server/mutations/signout.ts
|
|
6
8
|
async function signOutImpl(ctx, config) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
9
|
+
return withSpan("convex-auth.mutations.signOut", { "auth.flow": "signOut" }, async () => {
|
|
10
|
+
const db = authDb(ctx, config);
|
|
11
|
+
const sessionId = await getAuthSessionId(ctx);
|
|
12
|
+
if (sessionId == null) {
|
|
13
|
+
setActiveSpanAttributes({ "auth.signout.result": "no_session" });
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const session = await db.sessions.getById(sessionId);
|
|
17
|
+
if (session == null) {
|
|
18
|
+
setActiveSpanAttributes({ "auth.signout.result": "session_missing" });
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
await deleteSession(ctx, session, config);
|
|
22
|
+
setActiveSpanAttributes({
|
|
23
|
+
"auth.signout.result": "success",
|
|
24
|
+
...await buildSignOutIdentityAttributes(ctx, config, {
|
|
25
|
+
userId: session.userId,
|
|
26
|
+
sessionId: session._id
|
|
27
|
+
})
|
|
28
|
+
});
|
|
29
|
+
return {
|
|
30
|
+
userId: session.userId,
|
|
31
|
+
sessionId: session._id
|
|
32
|
+
};
|
|
33
|
+
});
|
|
17
34
|
}
|
|
18
35
|
const callSignOut = async (ctx) => {
|
|
19
36
|
return ctx.runMutation(AUTH_STORE_REF, { args: { type: "signOut" } });
|
|
@@ -57,7 +57,7 @@ const storeImpl = async (ctx, fnArgs, services) => {
|
|
|
57
57
|
const args = fnArgs.args;
|
|
58
58
|
const config = services.config;
|
|
59
59
|
const getProviderOrThrow = services.providerRegistry.getProviderOrThrow;
|
|
60
|
-
log(LOG_LEVELS.DEBUG, `\`auth:store\` type: ${args.type}`);
|
|
60
|
+
if (args.type !== "refreshSession") log(LOG_LEVELS.DEBUG, `\`auth:store\` type: ${args.type}`);
|
|
61
61
|
const handler = {
|
|
62
62
|
signIn: (a) => signInImpl(ctx, a, config),
|
|
63
63
|
signOut: () => signOutImpl(ctx, config),
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
function normalizeTokens(tokens) {
|
|
3
3
|
const raw = tokens.data;
|
|
4
4
|
const rawScopes = typeof raw.scope === "string" ? raw.scope : void 0;
|
|
5
|
+
const expiresInSeconds = typeof raw.expires_in === "number" ? raw.expires_in : void 0;
|
|
5
6
|
return {
|
|
6
7
|
accessToken: typeof raw.access_token === "string" ? raw.access_token : void 0,
|
|
7
8
|
refreshToken: typeof raw.refresh_token === "string" ? raw.refresh_token : void 0,
|
|
8
9
|
idToken: typeof raw.id_token === "string" ? raw.id_token : void 0,
|
|
9
|
-
accessTokenExpiresAt:
|
|
10
|
+
accessTokenExpiresAt: expiresInSeconds === void 0 ? void 0 : new Date(Date.now() + expiresInSeconds * 1e3),
|
|
10
11
|
scopes: rawScopes ? rawScopes.split(/[,\s]+/).map((scope) => scope.trim()).filter((scope) => scope.length > 0) : void 0,
|
|
11
12
|
raw: tokens.data
|
|
12
13
|
};
|
package/dist/server/passkey.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAuthenticatedUserIdOrNull } from "./identity.js";
|
|
2
2
|
import { authDb } from "./db.js";
|
|
3
3
|
import { callSignIn } from "./mutations/signin.js";
|
|
4
4
|
import { callVerifier } from "./mutations/verifier.js";
|
|
@@ -114,14 +114,13 @@ function requirePasskeyVerifier(verifier) {
|
|
|
114
114
|
throw convexError("PASSKEY_MISSING_VERIFIER", "Missing verifier for passkey operation.");
|
|
115
115
|
}
|
|
116
116
|
async function requireAuthenticatedUserId(ctx) {
|
|
117
|
-
let identity;
|
|
118
117
|
try {
|
|
119
|
-
|
|
118
|
+
const userId = await getAuthenticatedUserIdOrNull(ctx);
|
|
119
|
+
if (userId === null) throw convexError("PASSKEY_AUTH_REQUIRED", "Sign in first, then add a passkey to your account.");
|
|
120
|
+
return userId;
|
|
120
121
|
} catch {
|
|
121
122
|
throw convexError("PASSKEY_AUTH_REQUIRED", "Sign in first, then add a passkey to your account.");
|
|
122
123
|
}
|
|
123
|
-
if (identity === null) throw convexError("PASSKEY_AUTH_REQUIRED", "Sign in first, then add a passkey to your account.");
|
|
124
|
-
return userIdFromIdentitySubject(identity.subject);
|
|
125
124
|
}
|
|
126
125
|
function resolveRegistrationPublicKeyBytes(publicKey, algorithm) {
|
|
127
126
|
if (algorithm === coseAlgorithmES256) {
|
|
@@ -272,12 +271,12 @@ async function handlePasskeyFx(ctx, provider, args) {
|
|
|
272
271
|
userId,
|
|
273
272
|
generateTokens: true
|
|
274
273
|
});
|
|
275
|
-
} catch {
|
|
276
|
-
throw
|
|
274
|
+
} catch (error) {
|
|
275
|
+
throw asConvexError(error, "INTERNAL_ERROR", "Failed to finalize passkey registration.");
|
|
277
276
|
}
|
|
278
277
|
return {
|
|
279
278
|
kind: "signedIn",
|
|
280
|
-
|
|
279
|
+
session: signInResult
|
|
281
280
|
};
|
|
282
281
|
},
|
|
283
282
|
authOptions: async () => {
|
|
@@ -354,8 +353,8 @@ async function handlePasskeyFx(ctx, provider, args) {
|
|
|
354
353
|
if (passkey.counter !== 0 && authenticatorData.signatureCounter !== 0 && authenticatorData.signatureCounter <= passkey.counter) throw convexError("PASSKEY_COUNTER_ERROR", "Authenticator counter did not increase — possible credential cloning detected.");
|
|
355
354
|
try {
|
|
356
355
|
await mutatePasskeyUpdateCounter(ctx, passkey._id, authenticatorData.signatureCounter, Date.now());
|
|
357
|
-
} catch {
|
|
358
|
-
throw
|
|
356
|
+
} catch (error) {
|
|
357
|
+
throw asConvexError(error, "INTERNAL_ERROR", "Failed to update passkey counter.");
|
|
359
358
|
}
|
|
360
359
|
let signInResult;
|
|
361
360
|
try {
|
|
@@ -363,12 +362,12 @@ async function handlePasskeyFx(ctx, provider, args) {
|
|
|
363
362
|
userId: passkey.userId,
|
|
364
363
|
generateTokens: true
|
|
365
364
|
});
|
|
366
|
-
} catch {
|
|
367
|
-
throw
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw asConvexError(error, "INTERNAL_ERROR", "Failed to finalize passkey sign-in.");
|
|
368
367
|
}
|
|
369
368
|
return {
|
|
370
369
|
kind: "signedIn",
|
|
371
|
-
|
|
370
|
+
session: signInResult
|
|
372
371
|
};
|
|
373
372
|
}
|
|
374
373
|
}[dispatch.flow];
|
package/dist/server/prefetch.js
CHANGED
|
@@ -185,7 +185,7 @@ function getProxyErrorBody(error) {
|
|
|
185
185
|
} : { error: error instanceof Error ? error.message : String(error) };
|
|
186
186
|
}
|
|
187
187
|
function extractSignedInTokens(result, context) {
|
|
188
|
-
if (result.kind === "signedIn") return result.
|
|
188
|
+
if (result.kind === "signedIn") return result.session;
|
|
189
189
|
throw new Error(`Invalid \`auth:signIn\` result for ${context}`);
|
|
190
190
|
}
|
|
191
191
|
function normalizeCookieNamespace(cookieNamespace) {
|
|
@@ -360,19 +360,19 @@ function server(options) {
|
|
|
360
360
|
verifier: result.verifier
|
|
361
361
|
}, host$1, cookieConfig, cookieNamespace));
|
|
362
362
|
if (result.kind === "signedIn") {
|
|
363
|
-
const nextCookies = result.
|
|
363
|
+
const nextCookies = result.session === null ? {
|
|
364
364
|
token: currentCookies$1.token,
|
|
365
365
|
refreshToken: currentCookies$1.refreshToken,
|
|
366
366
|
verifier: null
|
|
367
367
|
} : {
|
|
368
|
-
token: result.
|
|
369
|
-
refreshToken: result.
|
|
368
|
+
token: result.session.token,
|
|
369
|
+
refreshToken: result.session.refreshToken,
|
|
370
370
|
verifier: null
|
|
371
371
|
};
|
|
372
372
|
return appendCookieHeaders(jsonResponse({
|
|
373
373
|
kind: "signedIn",
|
|
374
|
-
|
|
375
|
-
token: result.
|
|
374
|
+
session: result.session === null ? null : {
|
|
375
|
+
token: result.session.token,
|
|
376
376
|
refreshToken: "dummy"
|
|
377
377
|
}
|
|
378
378
|
}), serializeAuthCookies(nextCookies, host$1, cookieConfig, cookieNamespace));
|
|
@@ -416,11 +416,11 @@ function server(options) {
|
|
|
416
416
|
const action = body.action;
|
|
417
417
|
const args = typeof body.args === "object" && body.args !== null ? { ...body.args } : {};
|
|
418
418
|
if (args.refreshToken === null) args.refreshToken = void 0;
|
|
419
|
-
const actionDispatch = action === "auth:signIn" ? { action: "
|
|
419
|
+
const actionDispatch = action === "auth:signIn" ? { action: "signInStart" } : action === "auth:signOut" ? { action: "sessionStop" } : null;
|
|
420
420
|
if (actionDispatch === null) return new Response("Invalid action", { status: 400 });
|
|
421
421
|
const host = request.headers.get("host") ?? new URL(request.url).host;
|
|
422
422
|
const currentCookies = parseAuthCookies(request.headers.get("cookie"), host, cookieNamespace);
|
|
423
|
-
if (actionDispatch.action === "
|
|
423
|
+
if (actionDispatch.action === "signInStart") {
|
|
424
424
|
let refreshResponse = null;
|
|
425
425
|
if (args.refreshToken === void 0) refreshResponse = null;
|
|
426
426
|
else if (currentCookies.refreshToken === null) {
|
package/dist/server/runtime.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { ComponentCtx, ComponentReadCtx } from "./componentContext.js";
|
|
2
1
|
import { AuthProviderConfig, ConvexAuthConfig, CorsConfig, Doc, HttpKeyContext, KeyDoc, KeyScope, ScopeChecker, UserOrderBy, UserWhere } from "./types.js";
|
|
2
|
+
import { ComponentCtx, ComponentReadCtx } from "./componentContext.js";
|
|
3
3
|
import { AuthProfile, SignInParams } from "./payloads.js";
|
|
4
4
|
import { HttpAuthContext, HttpAuthContextConfig, OptionalHttpAuthContext } from "./http.js";
|
|
5
5
|
import { createGroupConnectionDomain } from "./sso/domain.js";
|
|
6
|
-
import * as
|
|
7
|
-
import * as
|
|
6
|
+
import * as convex_values86 from "convex/values";
|
|
7
|
+
import * as convex_server80 from "convex/server";
|
|
8
8
|
import { GenericActionCtx, GenericDataModel, HttpRouter } from "convex/server";
|
|
9
9
|
|
|
10
10
|
//#region src/server/runtime.d.ts
|
|
@@ -44,7 +44,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
44
44
|
order?: "asc" | "desc";
|
|
45
45
|
}) => Promise<any>;
|
|
46
46
|
viewer: (ctx: ComponentReadCtx & {
|
|
47
|
-
auth:
|
|
47
|
+
auth: convex_server80.Auth;
|
|
48
48
|
}) => Promise<Doc<"User"> | null>;
|
|
49
49
|
update: (ctx: ComponentCtx, userId: string, data: Record<string, unknown>) => Promise<{
|
|
50
50
|
userId: string;
|
|
@@ -69,18 +69,12 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
69
69
|
}>;
|
|
70
70
|
};
|
|
71
71
|
session: {
|
|
72
|
-
current: (ctx: {
|
|
73
|
-
auth: convex_server108.Auth;
|
|
74
|
-
}) => Promise<convex_values1137.GenericId<"Session"> | null>;
|
|
75
|
-
userId: (ctx: {
|
|
76
|
-
auth: convex_server108.Auth;
|
|
77
|
-
}) => Promise<convex_values1137.GenericId<"User"> | null>;
|
|
78
72
|
invalidate: <DataModel extends GenericDataModel>(ctx: GenericActionCtx<DataModel>, args: {
|
|
79
|
-
userId:
|
|
80
|
-
except?:
|
|
73
|
+
userId: convex_values86.GenericId<"User">;
|
|
74
|
+
except?: convex_values86.GenericId<"Session">[];
|
|
81
75
|
}) => Promise<{
|
|
82
|
-
userId:
|
|
83
|
-
except:
|
|
76
|
+
userId: convex_values86.GenericId<"User">;
|
|
77
|
+
except: convex_values86.GenericId<"Session">[];
|
|
84
78
|
}>;
|
|
85
79
|
get: (ctx: ComponentReadCtx, sessionId: string) => Promise<any>;
|
|
86
80
|
list: (ctx: ComponentReadCtx, opts: {
|
|
@@ -149,7 +143,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
149
143
|
};
|
|
150
144
|
provider: {
|
|
151
145
|
signIn: (<DataModel extends GenericDataModel>(ctx: GenericActionCtx<DataModel>, providerConfig: AuthProviderConfig, args: {
|
|
152
|
-
accountId?:
|
|
146
|
+
accountId?: convex_values86.GenericId<"Account">;
|
|
153
147
|
params?: SignInParams;
|
|
154
148
|
}) => Promise<{
|
|
155
149
|
userId: string;
|
|
@@ -457,22 +451,22 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
457
451
|
context: {
|
|
458
452
|
<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends {
|
|
459
453
|
auth: {
|
|
460
|
-
getUserIdentity: () => Promise<
|
|
454
|
+
getUserIdentity: () => Promise<convex_server80.UserIdentity | null>;
|
|
461
455
|
};
|
|
462
456
|
} & ComponentReadCtx = {
|
|
463
457
|
auth: {
|
|
464
|
-
getUserIdentity: () => Promise<
|
|
458
|
+
getUserIdentity: () => Promise<convex_server80.UserIdentity | null>;
|
|
465
459
|
};
|
|
466
460
|
} & ComponentReadCtx>(ctx: TCtx, request: Request, config: HttpAuthContextConfig<TResolve, TCtx> & {
|
|
467
461
|
optional: true;
|
|
468
462
|
}): Promise<OptionalHttpAuthContext & TResolve>;
|
|
469
463
|
<TResolve extends Record<string, unknown> = Record<string, never>, TCtx extends {
|
|
470
464
|
auth: {
|
|
471
|
-
getUserIdentity: () => Promise<
|
|
465
|
+
getUserIdentity: () => Promise<convex_server80.UserIdentity | null>;
|
|
472
466
|
};
|
|
473
467
|
} & ComponentReadCtx = {
|
|
474
468
|
auth: {
|
|
475
|
-
getUserIdentity: () => Promise<
|
|
469
|
+
getUserIdentity: () => Promise<convex_server80.UserIdentity | null>;
|
|
476
470
|
};
|
|
477
471
|
} & ComponentReadCtx>(ctx: TCtx, request: Request, config?: HttpAuthContextConfig<TResolve, TCtx>): Promise<HttpAuthContext & TResolve>;
|
|
478
472
|
};
|
|
@@ -506,7 +500,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
506
500
|
action: string;
|
|
507
501
|
};
|
|
508
502
|
cors?: CorsConfig;
|
|
509
|
-
}) =>
|
|
503
|
+
}) => convex_server80.PublicHttpAction;
|
|
510
504
|
/**
|
|
511
505
|
* Register a Bearer-authenticated route **and** its OPTIONS preflight
|
|
512
506
|
* in a single call.
|
|
@@ -552,7 +546,7 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
552
546
|
*
|
|
553
547
|
* Also used for refreshing the session.
|
|
554
548
|
*/
|
|
555
|
-
signIn:
|
|
549
|
+
signIn: convex_server80.RegisteredAction<"public", {
|
|
556
550
|
provider?: string | undefined;
|
|
557
551
|
verifier?: string | undefined;
|
|
558
552
|
params?: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null> | undefined;
|
|
@@ -562,12 +556,12 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
562
556
|
/**
|
|
563
557
|
* Action called by the client to invalidate the current session.
|
|
564
558
|
*/
|
|
565
|
-
signOut:
|
|
559
|
+
signOut: convex_server80.RegisteredAction<"public", {}, Promise<void>>;
|
|
566
560
|
/**
|
|
567
561
|
* Internal mutation used by the library to read and write
|
|
568
562
|
* to the database during signin and signout.
|
|
569
563
|
*/
|
|
570
|
-
store:
|
|
564
|
+
store: convex_server80.RegisteredMutation<"internal", {
|
|
571
565
|
args: {
|
|
572
566
|
sessionId?: string | undefined;
|
|
573
567
|
type: "signIn";
|
|
@@ -610,11 +604,11 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
610
604
|
sessionIndex?: string | undefined;
|
|
611
605
|
} | undefined;
|
|
612
606
|
} | undefined;
|
|
607
|
+
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
613
608
|
type: "userOAuth";
|
|
614
609
|
provider: string;
|
|
615
610
|
providerAccountId: string;
|
|
616
611
|
signature: string;
|
|
617
|
-
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
618
612
|
} | {
|
|
619
613
|
email?: string | undefined;
|
|
620
614
|
phone?: string | undefined;
|
|
@@ -627,9 +621,9 @@ declare function Auth(config_: ConvexAuthConfig): {
|
|
|
627
621
|
} | {
|
|
628
622
|
shouldLinkViaEmail?: boolean | undefined;
|
|
629
623
|
shouldLinkViaPhone?: boolean | undefined;
|
|
624
|
+
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
630
625
|
type: "createAccountFromCredentials";
|
|
631
626
|
provider: string;
|
|
632
|
-
profile: Record<string, string | number | boolean | (string | number | boolean | null)[] | Record<string, string | number | boolean | (string | number | boolean | null)[] | null> | null>;
|
|
633
627
|
account: {
|
|
634
628
|
secret?: string | undefined;
|
|
635
629
|
id: string;
|
package/dist/server/runtime.js
CHANGED
|
@@ -227,49 +227,16 @@ function Auth(config_) {
|
|
|
227
227
|
resolveSsoProtocol: group.resolveGroupConnectionSsoProtocolOrThrow
|
|
228
228
|
});
|
|
229
229
|
const handler = {
|
|
230
|
-
redirect: (r) =>
|
|
231
|
-
kind: "redirect",
|
|
232
|
-
redirect: r.redirect,
|
|
233
|
-
verifier: r.verifier
|
|
234
|
-
}),
|
|
230
|
+
redirect: (r) => r,
|
|
235
231
|
signedIn: (r) => ({
|
|
236
232
|
kind: "signedIn",
|
|
237
|
-
|
|
238
|
-
}),
|
|
239
|
-
refreshTokens: (r) => ({
|
|
240
|
-
kind: "signedIn",
|
|
241
|
-
tokens: r.signedIn?.tokens ?? null
|
|
242
|
-
}),
|
|
243
|
-
started: () => ({ kind: "started" }),
|
|
244
|
-
passkeyOptions: (r) => ({
|
|
245
|
-
kind: "passkeyOptions",
|
|
246
|
-
options: r.options,
|
|
247
|
-
verifier: r.verifier
|
|
248
|
-
}),
|
|
249
|
-
totpRequired: (r) => ({
|
|
250
|
-
kind: "totpRequired",
|
|
251
|
-
verifier: r.verifier
|
|
252
|
-
}),
|
|
253
|
-
totpSetup: (r) => ({
|
|
254
|
-
kind: "totpSetup",
|
|
255
|
-
totpSetup: {
|
|
256
|
-
uri: r.uri,
|
|
257
|
-
secret: r.secret,
|
|
258
|
-
totpId: r.totpId
|
|
259
|
-
},
|
|
260
|
-
verifier: r.verifier
|
|
233
|
+
session: r.session?.tokens ?? null
|
|
261
234
|
}),
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
verificationUri: r.verificationUri,
|
|
268
|
-
verificationUriComplete: r.verificationUriComplete,
|
|
269
|
-
expiresIn: r.expiresIn,
|
|
270
|
-
interval: r.interval
|
|
271
|
-
}
|
|
272
|
-
})
|
|
235
|
+
started: (r) => r,
|
|
236
|
+
passkeyOptions: (r) => r,
|
|
237
|
+
totpRequired: (r) => r,
|
|
238
|
+
totpSetup: (r) => r,
|
|
239
|
+
deviceCode: (r) => r
|
|
273
240
|
}[result.kind];
|
|
274
241
|
if (!handler) throw new Error(`Unexpected sign-in result kind: ${result.kind}`);
|
|
275
242
|
return handler(result);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import "./types.js";
|
|
1
|
+
import { SessionTokenIdentityClaims } from "./types.js";
|
|
2
2
|
import { GenericId } from "convex/values";
|
|
3
3
|
|
|
4
4
|
//#region src/server/sessions.d.ts
|
|
@@ -10,6 +10,7 @@ import { GenericId } from "convex/values";
|
|
|
10
10
|
type SessionIssuance = {
|
|
11
11
|
userId: GenericId<"User">;
|
|
12
12
|
sessionId: GenericId<"Session">;
|
|
13
|
+
identity: SessionTokenIdentityClaims;
|
|
13
14
|
/**
|
|
14
15
|
* Encoded refresh token (`${refreshTokenId}|${sessionId}`), or `null` when
|
|
15
16
|
* the caller opted out of refresh-token issuance (e.g. TOTP step-up).
|
package/dist/server/sessions.js
CHANGED
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
import { LOG_LEVELS } from "../shared/log.js";
|
|
2
|
+
import { getAuthenticatedSessionIdOrNull } from "./identity.js";
|
|
2
3
|
import { authDb } from "./db.js";
|
|
3
4
|
import { envOptionalNumber, readConfigSync } from "./env.js";
|
|
4
5
|
import { log, maybeRedact } from "./log.js";
|
|
5
6
|
import { REFRESH_TOKEN_DIVIDER, refreshTokenExpirationTime } from "./refresh.js";
|
|
6
7
|
import { withSpan } from "./utils/span.js";
|
|
7
|
-
import {
|
|
8
|
+
import { generateToken } from "./tokens.js";
|
|
8
9
|
|
|
9
10
|
//#region src/server/sessions.ts
|
|
10
11
|
const DEFAULT_SESSION_TOTAL_DURATION_MS = 1e3 * 60 * 60 * 24 * 30;
|
|
11
12
|
const sessionExpirationTime = (config, now = Date.now()) => now + (config.session?.totalDurationMs ?? readConfigSync(envOptionalNumber("AUTH_SESSION_TOTAL_DURATION_MS")) ?? DEFAULT_SESSION_TOTAL_DURATION_MS);
|
|
12
13
|
const encodeRefreshToken = (refreshTokenId, sessionId) => `${refreshTokenId}${REFRESH_TOKEN_DIVIDER}${sessionId}`;
|
|
14
|
+
function buildSessionIdentity(userId, sessionId, user) {
|
|
15
|
+
return {
|
|
16
|
+
subject: userId,
|
|
17
|
+
sessionId,
|
|
18
|
+
...typeof user?.name === "string" ? { name: user.name } : null,
|
|
19
|
+
...typeof user?.email === "string" ? { email: user.email } : null,
|
|
20
|
+
...user?.emailVerificationTime !== void 0 ? { emailVerified: true } : user?.email !== void 0 ? { emailVerified: false } : null,
|
|
21
|
+
...typeof user?.image === "string" ? { picture: user.image } : null,
|
|
22
|
+
...typeof user?.phone === "string" ? { phoneNumber: user.phone } : null,
|
|
23
|
+
...user?.phoneVerificationTime !== void 0 ? { phoneNumberVerified: true } : user?.phone !== void 0 ? { phoneNumberVerified: false } : null
|
|
24
|
+
};
|
|
25
|
+
}
|
|
13
26
|
/**
|
|
14
27
|
* Convert a {@link SessionIssuance} returned from a mutation into the
|
|
15
28
|
* external `SessionInfo` shape by signing the JWT on the action side.
|
|
@@ -26,10 +39,7 @@ async function finalizeSessionIssuance(config, issuance) {
|
|
|
26
39
|
sessionId: issuance.sessionId,
|
|
27
40
|
tokens: null
|
|
28
41
|
};
|
|
29
|
-
const token = await generateToken({
|
|
30
|
-
userId: issuance.userId,
|
|
31
|
-
sessionId: issuance.sessionId
|
|
32
|
-
}, config);
|
|
42
|
+
const token = await generateToken({ identity: issuance.identity }, config);
|
|
33
43
|
log(LOG_LEVELS.DEBUG, `Generated token ${maybeRedact(token)} and refresh token ${maybeRedact(issuance.refreshToken)} for session ${maybeRedact(issuance.sessionId)}`);
|
|
34
44
|
return {
|
|
35
45
|
userId: issuance.userId,
|
|
@@ -43,7 +53,8 @@ async function finalizeSessionIssuance(config, issuance) {
|
|
|
43
53
|
}
|
|
44
54
|
/** @internal */
|
|
45
55
|
async function issueSession(ctx, config, args) {
|
|
46
|
-
const
|
|
56
|
+
const db = authDb(ctx, config);
|
|
57
|
+
const issued = await db.sessions.issue({
|
|
47
58
|
userId: args.userId,
|
|
48
59
|
sessionId: args.existingSessionId,
|
|
49
60
|
replaceSessionId: args.replaceSessionId,
|
|
@@ -52,9 +63,11 @@ async function issueSession(ctx, config, args) {
|
|
|
52
63
|
});
|
|
53
64
|
const sessionId = issued.sessionId;
|
|
54
65
|
const refreshTokenId = issued.refreshTokenId;
|
|
66
|
+
const user = await db.users.getById(issued.userId);
|
|
55
67
|
return {
|
|
56
68
|
userId: issued.userId,
|
|
57
69
|
sessionId,
|
|
70
|
+
identity: buildSessionIdentity(issued.userId, sessionId, user),
|
|
58
71
|
refreshToken: args.generateTokens && refreshTokenId !== void 0 ? encodeRefreshToken(refreshTokenId, sessionId) : null
|
|
59
72
|
};
|
|
60
73
|
}
|
|
@@ -67,14 +80,11 @@ async function deleteSession(ctx, session, config) {
|
|
|
67
80
|
/**
|
|
68
81
|
* Return the current session ID from the auth identity subject.
|
|
69
82
|
*
|
|
70
|
-
* Internal helper used by auth runtime internals
|
|
83
|
+
* Internal helper used by auth runtime internals.
|
|
71
84
|
*/
|
|
72
85
|
/** @internal */
|
|
73
86
|
async function getAuthSessionId(ctx) {
|
|
74
|
-
|
|
75
|
-
if (identity === null) return null;
|
|
76
|
-
const [, sessionId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
|
|
77
|
-
return sessionId;
|
|
87
|
+
return await getAuthenticatedSessionIdOrNull(ctx);
|
|
78
88
|
}
|
|
79
89
|
|
|
80
90
|
//#endregion
|