@robelest/convex-auth 0.0.4-preview.32 → 0.0.4-preview.34
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/component/_generated/component.d.ts +1611 -2039
- package/dist/component/account.js +3 -0
- package/dist/component/convex.config.d.ts +2 -2
- package/dist/component/factor/device.js +3 -0
- package/dist/component/factor/passkey.js +3 -0
- package/dist/component/factor/totp.js +3 -0
- package/dist/component/group/invite.js +3 -0
- package/dist/component/group/member.js +3 -0
- package/dist/component/group.js +3 -0
- package/dist/component/model.d.ts +342 -30
- package/dist/component/model.js +22 -4
- package/dist/component/modules.js +24 -21
- package/dist/component/public/factors/devices.js +37 -106
- package/dist/component/public/factors/passkeys.js +29 -149
- package/dist/component/public/factors/totp.js +32 -159
- package/dist/component/public/groups/core.js +19 -82
- package/dist/component/public/groups/invites.js +15 -104
- package/dist/component/public/groups/members.js +26 -149
- package/dist/component/public/identity/accounts.js +12 -94
- package/dist/component/public/identity/codes.js +13 -73
- package/dist/component/public/identity/sessions.js +5 -107
- package/dist/component/public/identity/tokens.js +13 -103
- package/dist/component/public/identity/users.js +188 -185
- package/dist/component/public/identity/verifiers.js +17 -80
- package/dist/component/public/security/keys.js +13 -120
- package/dist/component/public/security/limits.js +0 -43
- package/dist/component/public/sso/audit.js +0 -28
- package/dist/component/public/sso/core.js +31 -104
- package/dist/component/public/sso/domains.js +0 -71
- package/dist/component/public/sso/scim.js +63 -239
- package/dist/component/public/sso/secrets.js +0 -30
- package/dist/component/public/sso/webhooks.js +23 -128
- package/dist/component/rateLimit.js +3 -0
- package/dist/component/schema.d.ts +378 -342
- package/dist/component/schema.js +11 -1
- package/dist/component/session.js +3 -0
- package/dist/component/sso/audit.js +3 -0
- package/dist/component/sso/connection/domain/verification.js +3 -0
- package/dist/component/sso/connection/domain.js +3 -0
- package/dist/component/sso/connection/scim/config.js +3 -0
- package/dist/component/sso/connection/scim/identity.js +3 -0
- package/dist/component/sso/connection/secret.js +3 -0
- package/dist/component/sso/connection.js +3 -0
- package/dist/component/sso/webhook/delivery.js +3 -0
- package/dist/component/sso/webhook/endpoint.js +3 -0
- package/dist/component/token/pkce.js +3 -0
- package/dist/component/token/refresh.js +3 -0
- package/dist/component/token/verification.js +3 -0
- package/dist/component/user/email.js +3 -0
- package/dist/component/user/key.js +3 -0
- package/dist/component/user.js +62 -0
- package/dist/core/index.d.ts +131 -28
- package/dist/core/index.js +2 -0
- package/dist/model.js +391 -0
- package/dist/providers/credentials.d.ts +1 -1
- package/dist/providers/github.js +6 -0
- package/dist/providers/password.js +1 -2
- package/dist/server/auth.d.ts +73 -7
- package/dist/server/auth.js +4 -1
- package/dist/server/context.js +30 -3
- package/dist/server/contract.js +42 -42
- package/dist/server/core.js +224 -86
- package/dist/server/db.js +45 -37
- package/dist/server/facade.d.ts +39 -0
- package/dist/server/facade.js +16 -0
- package/dist/server/index.d.ts +5 -3
- package/dist/server/index.js +3 -1
- package/dist/server/mounts.d.ts +101 -101
- package/dist/server/mutations/credentials/signin.js +3 -7
- package/dist/server/mutations/oauth.js +9 -6
- package/dist/server/runtime.d.ts +147 -46
- package/dist/server/runtime.js +10 -8
- package/dist/server/services/group.js +9 -9
- package/dist/server/sso/domain.d.ts +1 -1
- package/dist/server/sso/domain.js +40 -40
- package/dist/server/sso/http.js +18 -18
- package/dist/server/sso/oidc.js +1 -1
- package/dist/server/sso/policies.js +3 -3
- package/dist/server/sso/policy.js +12 -4
- package/dist/server/sso/provision.js +9 -9
- package/dist/server/sso/validators.js +2 -2
- package/dist/server/sso/webhook.js +8 -8
- package/dist/server/types.d.ts +185 -124
- package/dist/server/types.js +29 -24
- package/dist/server/users.js +49 -2
- package/dist/server/validators.d.ts +745 -0
- package/dist/server/validators.js +60 -0
- package/package.json +1 -1
- package/dist/component/public.js +0 -22
|
@@ -14,18 +14,8 @@ import { ConvexError, v } from "convex/values";
|
|
|
14
14
|
* @returns An array of account documents associated with the user. Each document
|
|
15
15
|
* includes fields such as `provider`, `providerAccountId`, `secret`, and `extend`.
|
|
16
16
|
*
|
|
17
|
-
* @example
|
|
18
|
-
* ```ts
|
|
19
|
-
* const accounts = await ctx.runQuery(
|
|
20
|
-
* component.identity.accounts.accountListByUser,
|
|
21
|
-
* { userId: user._id },
|
|
22
|
-
* );
|
|
23
|
-
* for (const account of accounts) {
|
|
24
|
-
* console.log(`Provider: ${account.provider}, ID: ${account.providerAccountId}`);
|
|
25
|
-
* }
|
|
26
|
-
* ```
|
|
27
17
|
*/
|
|
28
|
-
const
|
|
18
|
+
const accountList = query({
|
|
29
19
|
args: { userId: v.id("User") },
|
|
30
20
|
returns: v.array(vAccountDoc),
|
|
31
21
|
handler: async (ctx, { userId }) => {
|
|
@@ -33,64 +23,21 @@ const accountListByUser = query({
|
|
|
33
23
|
}
|
|
34
24
|
});
|
|
35
25
|
/**
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
* the given provider and external account identifier. This is the primary way
|
|
40
|
-
* to resolve an incoming authentication event (e.g. an OAuth callback) to an
|
|
41
|
-
* existing account in the system.
|
|
42
|
-
*
|
|
43
|
-
* @param args.provider - The name of the authentication provider (e.g. `"google"`, `"github"`, `"credentials"`).
|
|
44
|
-
* @param args.providerAccountId - The unique identifier assigned to the user by the external provider.
|
|
45
|
-
* @returns The matching account document, or `null` if no account exists for the
|
|
46
|
-
* given provider and provider account ID combination.
|
|
47
|
-
*
|
|
48
|
-
* @example
|
|
49
|
-
* ```ts
|
|
50
|
-
* const account = await ctx.runQuery(
|
|
51
|
-
* component.identity.accounts.accountGet,
|
|
52
|
-
* { provider: "google", providerAccountId: "1184210396400123" },
|
|
53
|
-
* );
|
|
54
|
-
* if (account !== null) {
|
|
55
|
-
* console.log(`Found account for user: ${account.userId}`);
|
|
56
|
-
* }
|
|
57
|
-
* ```
|
|
26
|
+
* Read an account by identity — one function, all-optional args, unioned
|
|
27
|
+
* return: `{ id }` (point lookup) or `{ provider, providerAccountId }`
|
|
28
|
+
* (unique provider index).
|
|
58
29
|
*/
|
|
59
30
|
const accountGet = query({
|
|
60
31
|
args: {
|
|
61
|
-
|
|
62
|
-
|
|
32
|
+
id: v.optional(v.id("Account")),
|
|
33
|
+
provider: v.optional(v.string()),
|
|
34
|
+
providerAccountId: v.optional(v.string())
|
|
63
35
|
},
|
|
64
36
|
returns: v.union(vAccountDoc, v.null()),
|
|
65
|
-
handler: async (ctx,
|
|
66
|
-
return await ctx.db.query("Account").withIndex("provider_account_id", (q) => q.eq("provider", provider).eq("providerAccountId", providerAccountId)).unique();
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Retrieve a single account by its Convex document ID.
|
|
71
|
-
*
|
|
72
|
-
* Performs a direct point lookup on the `Account` table. Returns `null` if the
|
|
73
|
-
* document has been deleted or never existed.
|
|
74
|
-
*
|
|
75
|
-
* @param args.accountId - The Convex document ID (`Id<"Account">`) of the account to retrieve.
|
|
76
|
-
* @returns The account document if it exists, or `null` otherwise.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* const account = await ctx.runQuery(
|
|
81
|
-
* component.identity.accounts.accountGetById,
|
|
82
|
-
* { accountId: existingAccountId },
|
|
83
|
-
* );
|
|
84
|
-
* if (account !== null) {
|
|
85
|
-
* console.log(`Provider: ${account.provider}`);
|
|
86
|
-
* }
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
|
-
const accountGetById = query({
|
|
90
|
-
args: { accountId: v.id("Account") },
|
|
91
|
-
returns: v.union(vAccountDoc, v.null()),
|
|
92
|
-
handler: async (ctx, { accountId }) => {
|
|
93
|
-
return await ctx.db.get("Account", accountId);
|
|
37
|
+
handler: async (ctx, args) => {
|
|
38
|
+
if (args.provider !== void 0 && args.providerAccountId !== void 0) return await ctx.db.query("Account").withIndex("provider_account_id", (q) => q.eq("provider", args.provider).eq("providerAccountId", args.providerAccountId)).unique();
|
|
39
|
+
if (args.id === void 0) return null;
|
|
40
|
+
return await ctx.db.get("Account", args.id);
|
|
94
41
|
}
|
|
95
42
|
});
|
|
96
43
|
/**
|
|
@@ -108,18 +55,6 @@ const accountGetById = query({
|
|
|
108
55
|
* @param args.extend - Optional arbitrary data to store alongside the account for application-specific needs.
|
|
109
56
|
* @returns The document ID of the newly created account.
|
|
110
57
|
*
|
|
111
|
-
* @example
|
|
112
|
-
* ```ts
|
|
113
|
-
* const accountId = await ctx.runMutation(
|
|
114
|
-
* component.identity.accounts.accountInsert,
|
|
115
|
-
* {
|
|
116
|
-
* userId: user._id,
|
|
117
|
-
* provider: "credentials",
|
|
118
|
-
* providerAccountId: "user@example.com",
|
|
119
|
-
* secret: hashedPassword,
|
|
120
|
-
* },
|
|
121
|
-
* );
|
|
122
|
-
* ```
|
|
123
58
|
*/
|
|
124
59
|
const accountInsert = mutation({
|
|
125
60
|
args: {
|
|
@@ -145,16 +80,6 @@ const accountInsert = mutation({
|
|
|
145
80
|
* @param args.data - A partial object containing the fields to merge into the account document.
|
|
146
81
|
* @returns `null` on success.
|
|
147
82
|
*
|
|
148
|
-
* @example
|
|
149
|
-
* ```ts
|
|
150
|
-
* await ctx.runMutation(
|
|
151
|
-
* component.identity.accounts.accountPatch,
|
|
152
|
-
* {
|
|
153
|
-
* accountId: account._id,
|
|
154
|
-
* data: { secret: newHashedPassword },
|
|
155
|
-
* },
|
|
156
|
-
* );
|
|
157
|
-
* ```
|
|
158
83
|
*/
|
|
159
84
|
const accountPatch = mutation({
|
|
160
85
|
args: {
|
|
@@ -178,13 +103,6 @@ const accountPatch = mutation({
|
|
|
178
103
|
* @param args.accountId - The document ID of the account to delete.
|
|
179
104
|
* @returns `null` on success.
|
|
180
105
|
*
|
|
181
|
-
* @example
|
|
182
|
-
* ```ts
|
|
183
|
-
* await ctx.runMutation(
|
|
184
|
-
* component.identity.accounts.accountDelete,
|
|
185
|
-
* { accountId: account._id },
|
|
186
|
-
* );
|
|
187
|
-
* ```
|
|
188
106
|
*/
|
|
189
107
|
const accountDelete = mutation({
|
|
190
108
|
args: {
|
|
@@ -215,5 +133,5 @@ const accountDelete = mutation({
|
|
|
215
133
|
});
|
|
216
134
|
|
|
217
135
|
//#endregion
|
|
218
|
-
export { accountDelete, accountGet,
|
|
136
|
+
export { accountDelete, accountGet, accountInsert, accountList, accountPatch };
|
|
219
137
|
//# sourceMappingURL=accounts.js.map
|
|
@@ -4,59 +4,20 @@ import { v } from "convex/values";
|
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/identity/codes.ts
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* the unique verification code linked to the given account. Each account has at
|
|
11
|
-
* most one active verification code at a time.
|
|
12
|
-
*
|
|
13
|
-
* @param args.accountId - The document ID of the account whose verification code should be retrieved.
|
|
14
|
-
* @returns The verification code document if one exists for the account, or `null` otherwise.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* const code = await ctx.runQuery(
|
|
19
|
-
* component.identity.codes.verificationCodeGetByAccountId,
|
|
20
|
-
* { accountId: account._id },
|
|
21
|
-
* );
|
|
22
|
-
* if (code !== null && code.expirationTime > Date.now()) {
|
|
23
|
-
* console.log("Active verification code exists");
|
|
24
|
-
* }
|
|
25
|
-
* ```
|
|
26
|
-
*/
|
|
27
|
-
const verificationCodeGetByAccountId = query({
|
|
28
|
-
args: { accountId: v.id("Account") },
|
|
29
|
-
returns: v.union(vVerificationCodeDoc, v.null()),
|
|
30
|
-
handler: async (ctx, { accountId }) => {
|
|
31
|
-
return await ctx.db.query("VerificationCode").withIndex("account_id", (q) => q.eq("accountId", accountId)).unique();
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
/**
|
|
35
|
-
* Find a verification code by its code string value.
|
|
36
|
-
*
|
|
37
|
-
* Queries the `VerificationCode` table using the `code` index to locate the
|
|
38
|
-
* unique verification code document matching the given code string. This is
|
|
39
|
-
* the primary lookup used when a user submits an OTP or clicks a magic link.
|
|
40
|
-
*
|
|
41
|
-
* @param args.code - The verification code string to look up (e.g. a 6-digit OTP or a magic-link token).
|
|
42
|
-
* @returns The verification code document if a match is found, or `null` otherwise.
|
|
43
|
-
*
|
|
44
|
-
* @example
|
|
45
|
-
* ```ts
|
|
46
|
-
* const codeDoc = await ctx.runQuery(
|
|
47
|
-
* component.identity.codes.verificationCodeGetByCode,
|
|
48
|
-
* { code: "482910" },
|
|
49
|
-
* );
|
|
50
|
-
* if (codeDoc !== null && codeDoc.expirationTime > Date.now()) {
|
|
51
|
-
* console.log(`Code is valid for account: ${codeDoc.accountId}`);
|
|
52
|
-
* }
|
|
53
|
-
* ```
|
|
7
|
+
* Read a verification code by identity — one function, all-optional
|
|
8
|
+
* args, unioned return: `{ accountId }` (unique per account) or
|
|
9
|
+
* `{ code }` (code index).
|
|
54
10
|
*/
|
|
55
|
-
const
|
|
56
|
-
args: {
|
|
11
|
+
const verificationCodeGet = query({
|
|
12
|
+
args: {
|
|
13
|
+
accountId: v.optional(v.id("Account")),
|
|
14
|
+
code: v.optional(v.string())
|
|
15
|
+
},
|
|
57
16
|
returns: v.union(vVerificationCodeDoc, v.null()),
|
|
58
|
-
handler: async (ctx,
|
|
59
|
-
return await ctx.db.query("VerificationCode").withIndex("code", (q) => q.eq("code", code)).first();
|
|
17
|
+
handler: async (ctx, args) => {
|
|
18
|
+
if (args.code !== void 0) return await ctx.db.query("VerificationCode").withIndex("code", (q) => q.eq("code", args.code)).first();
|
|
19
|
+
if (args.accountId === void 0) return null;
|
|
20
|
+
return await ctx.db.query("VerificationCode").withIndex("account_id", (q) => q.eq("accountId", args.accountId)).unique();
|
|
60
21
|
}
|
|
61
22
|
});
|
|
62
23
|
/**
|
|
@@ -78,19 +39,6 @@ const verificationCodeGetByCode = query({
|
|
|
78
39
|
* code redemption.
|
|
79
40
|
* @returns The document ID of the newly created verification code.
|
|
80
41
|
*
|
|
81
|
-
* @example
|
|
82
|
-
* ```ts
|
|
83
|
-
* const codeId = await ctx.runMutation(
|
|
84
|
-
* component.identity.codes.verificationCodeCreate,
|
|
85
|
-
* {
|
|
86
|
-
* accountId: account._id,
|
|
87
|
-
* provider: "resend-otp",
|
|
88
|
-
* code: "482910",
|
|
89
|
-
* expirationTime: Date.now() + 10 * 60 * 1000, // 10 minutes
|
|
90
|
-
* emailVerified: "alice@example.com",
|
|
91
|
-
* },
|
|
92
|
-
* );
|
|
93
|
-
* ```
|
|
94
42
|
*/
|
|
95
43
|
const verificationCodeCreate = mutation({
|
|
96
44
|
args: {
|
|
@@ -117,14 +65,6 @@ const verificationCodeCreate = mutation({
|
|
|
117
65
|
* @param args.verificationCodeId - The document ID of the verification code to delete.
|
|
118
66
|
* @returns `null` on success.
|
|
119
67
|
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```ts
|
|
122
|
-
* // Delete the code after successful verification
|
|
123
|
-
* await ctx.runMutation(
|
|
124
|
-
* component.identity.codes.verificationCodeDelete,
|
|
125
|
-
* { verificationCodeId: codeDoc._id },
|
|
126
|
-
* );
|
|
127
|
-
* ```
|
|
128
68
|
*/
|
|
129
69
|
const verificationCodeDelete = mutation({
|
|
130
70
|
args: { verificationCodeId: v.id("VerificationCode") },
|
|
@@ -136,5 +76,5 @@ const verificationCodeDelete = mutation({
|
|
|
136
76
|
});
|
|
137
77
|
|
|
138
78
|
//#endregion
|
|
139
|
-
export { verificationCodeCreate, verificationCodeDelete,
|
|
79
|
+
export { verificationCodeCreate, verificationCodeDelete, verificationCodeGet };
|
|
140
80
|
//# sourceMappingURL=codes.js.map
|
|
@@ -1,70 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { vSessionDoc } from "../../model.js";
|
|
2
2
|
import { mutation, query } from "../../functions.js";
|
|
3
3
|
import { v } from "convex/values";
|
|
4
4
|
|
|
5
5
|
//#region src/component/public/identity/sessions.ts
|
|
6
6
|
/**
|
|
7
|
-
* List sessions with optional filtering and cursor-based pagination.
|
|
8
|
-
*
|
|
9
|
-
* Supports filtering by `userId` to retrieve only sessions belonging to a
|
|
10
|
-
* specific user. When a `userId` filter is provided, the `user_id` index is
|
|
11
|
-
* used for efficient lookup. Results are returned as a paginated response
|
|
12
|
-
* `{ items, nextCursor }` -- pass `nextCursor` back as `cursor` to fetch the
|
|
13
|
-
* next page, or receive `null` when all results have been exhausted.
|
|
14
|
-
*
|
|
15
|
-
* @param args.where - Optional filter object. Currently supports `userId` to
|
|
16
|
-
* restrict results to sessions for a specific user.
|
|
17
|
-
* @param args.limit - Maximum number of sessions to return per page (1--100, default 50).
|
|
18
|
-
* @param args.cursor - An opaque cursor string from a previous response's `nextCursor`
|
|
19
|
-
* to continue pagination, or `null` / omitted to start from the beginning.
|
|
20
|
-
* @param args.order - Sort direction: `"asc"` or `"desc"` (default `"desc"`).
|
|
21
|
-
* @returns An object with `items` (array of session documents) and `nextCursor`
|
|
22
|
-
* (`string | null`) for fetching subsequent pages.
|
|
23
|
-
*
|
|
24
|
-
* @example
|
|
25
|
-
* ```ts
|
|
26
|
-
* // List the 10 most recent sessions for a user
|
|
27
|
-
* const page = await ctx.runQuery(
|
|
28
|
-
* component.identity.sessions.sessionList,
|
|
29
|
-
* { where: { userId: user._id }, limit: 10, order: "desc" },
|
|
30
|
-
* );
|
|
31
|
-
* for (const session of page.items) {
|
|
32
|
-
* console.log(`Session ${session._id} expires at ${session.expirationTime}`);
|
|
33
|
-
* }
|
|
34
|
-
* ```
|
|
35
|
-
*/
|
|
36
|
-
const sessionList = query({
|
|
37
|
-
args: {
|
|
38
|
-
where: v.optional(v.object({ userId: v.optional(v.id("User")) })),
|
|
39
|
-
limit: v.optional(v.number()),
|
|
40
|
-
cursor: v.optional(v.union(v.string(), v.null())),
|
|
41
|
-
order: v.optional(v.union(v.literal("asc"), v.literal("desc")))
|
|
42
|
-
},
|
|
43
|
-
returns: vPaginated(vSessionDoc),
|
|
44
|
-
handler: async (ctx, args) => {
|
|
45
|
-
const where = args.where ?? {};
|
|
46
|
-
const limit = Math.min(Math.max(args.limit ?? 50, 1), 100);
|
|
47
|
-
const order = args.order ?? "desc";
|
|
48
|
-
let q;
|
|
49
|
-
if (where.userId !== void 0) q = ctx.db.query("Session").withIndex("user_id", (idx) => idx.eq("userId", where.userId));
|
|
50
|
-
else q = ctx.db.query("Session");
|
|
51
|
-
q = q.order(order);
|
|
52
|
-
const all = await q.collect();
|
|
53
|
-
let startIdx = 0;
|
|
54
|
-
if (args.cursor) {
|
|
55
|
-
const cursorIdx = all.findIndex((doc) => doc._id === args.cursor);
|
|
56
|
-
if (cursorIdx !== -1) startIdx = cursorIdx + 1;
|
|
57
|
-
}
|
|
58
|
-
const page = all.slice(startIdx, startIdx + limit + 1);
|
|
59
|
-
const hasMore = page.length > limit;
|
|
60
|
-
const items = hasMore ? page.slice(0, limit) : page;
|
|
61
|
-
return {
|
|
62
|
-
items,
|
|
63
|
-
nextCursor: hasMore ? items[items.length - 1]._id : null
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
/**
|
|
68
7
|
* Create a new session for a user with a specified expiration time.
|
|
69
8
|
*
|
|
70
9
|
* Inserts a new document into the `Session` table, linking it to the given user.
|
|
@@ -75,16 +14,6 @@ const sessionList = query({
|
|
|
75
14
|
* @param args.expirationTime - The Unix timestamp (in milliseconds) at which this session expires.
|
|
76
15
|
* @returns The document ID of the newly created session.
|
|
77
16
|
*
|
|
78
|
-
* @example
|
|
79
|
-
* ```ts
|
|
80
|
-
* const sessionId = await ctx.runMutation(
|
|
81
|
-
* component.identity.sessions.sessionCreate,
|
|
82
|
-
* {
|
|
83
|
-
* userId: user._id,
|
|
84
|
-
* expirationTime: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
|
|
85
|
-
* },
|
|
86
|
-
* );
|
|
87
|
-
* ```
|
|
88
17
|
*/
|
|
89
18
|
const sessionCreate = mutation({
|
|
90
19
|
args: {
|
|
@@ -147,16 +76,6 @@ const sessionIssue = mutation({
|
|
|
147
76
|
* @param args.sessionId - The Convex document ID (`Id<"Session">`) of the session to retrieve.
|
|
148
77
|
* @returns The session document if it exists, or `null` otherwise.
|
|
149
78
|
*
|
|
150
|
-
* @example
|
|
151
|
-
* ```ts
|
|
152
|
-
* const session = await ctx.runQuery(
|
|
153
|
-
* component.identity.sessions.sessionGetById,
|
|
154
|
-
* { sessionId: refreshToken.sessionId },
|
|
155
|
-
* );
|
|
156
|
-
* if (session !== null && session.expirationTime > Date.now()) {
|
|
157
|
-
* console.log("Session is still active");
|
|
158
|
-
* }
|
|
159
|
-
* ```
|
|
160
79
|
*/
|
|
161
80
|
const sessionGetById = query({
|
|
162
81
|
args: { sessionId: v.id("Session") },
|
|
@@ -176,18 +95,6 @@ const sessionGetById = query({
|
|
|
176
95
|
* @param args.sessionId - The document ID of the session to delete.
|
|
177
96
|
* @returns `null` on success (including when the session was already absent).
|
|
178
97
|
*
|
|
179
|
-
* @example
|
|
180
|
-
* ```ts
|
|
181
|
-
* // Revoke a session and its tokens
|
|
182
|
-
* await ctx.runMutation(
|
|
183
|
-
* component.identity.sessions.sessionDelete,
|
|
184
|
-
* { sessionId: session._id },
|
|
185
|
-
* );
|
|
186
|
-
* await ctx.runMutation(
|
|
187
|
-
* component.identity.tokens.refreshTokenDeleteAll,
|
|
188
|
-
* { sessionId: session._id },
|
|
189
|
-
* );
|
|
190
|
-
* ```
|
|
191
98
|
*/
|
|
192
99
|
const sessionDelete = mutation({
|
|
193
100
|
args: { sessionId: v.id("Session") },
|
|
@@ -200,23 +107,14 @@ const sessionDelete = mutation({
|
|
|
200
107
|
/**
|
|
201
108
|
* List all sessions belonging to a specific user.
|
|
202
109
|
*
|
|
203
|
-
* Queries the `Session` table using the `user_id` index to
|
|
204
|
-
* every session document for the given user
|
|
205
|
-
* all matching sessions without pagination.
|
|
110
|
+
* Queries the `Session` table using the `user_id` index to retrieve
|
|
111
|
+
* every session document for the given user, as a flat array.
|
|
206
112
|
*
|
|
207
113
|
* @param args.userId - The document ID of the user whose sessions should be retrieved.
|
|
208
114
|
* @returns An array of session documents for the specified user.
|
|
209
115
|
*
|
|
210
|
-
* @example
|
|
211
|
-
* ```ts
|
|
212
|
-
* const sessions = await ctx.runQuery(
|
|
213
|
-
* component.identity.sessions.sessionListByUser,
|
|
214
|
-
* { userId: user._id },
|
|
215
|
-
* );
|
|
216
|
-
* console.log(`User has ${sessions.length} active session(s)`);
|
|
217
|
-
* ```
|
|
218
116
|
*/
|
|
219
|
-
const
|
|
117
|
+
const sessionList = query({
|
|
220
118
|
args: { userId: v.id("User") },
|
|
221
119
|
returns: v.array(vSessionDoc),
|
|
222
120
|
handler: async (ctx, { userId }) => {
|
|
@@ -225,5 +123,5 @@ const sessionListByUser = query({
|
|
|
225
123
|
});
|
|
226
124
|
|
|
227
125
|
//#endregion
|
|
228
|
-
export { sessionCreate, sessionDelete, sessionGetById, sessionIssue, sessionList
|
|
126
|
+
export { sessionCreate, sessionDelete, sessionGetById, sessionIssue, sessionList };
|
|
229
127
|
//# sourceMappingURL=sessions.js.map
|
|
@@ -17,16 +17,6 @@ import { v } from "convex/values";
|
|
|
17
17
|
* exchanged to create this one. Omitted for the initial token in a session.
|
|
18
18
|
* @returns The document ID of the newly created refresh token.
|
|
19
19
|
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```ts
|
|
22
|
-
* const tokenId = await ctx.runMutation(
|
|
23
|
-
* component.identity.tokens.refreshTokenCreate,
|
|
24
|
-
* {
|
|
25
|
-
* sessionId: session._id,
|
|
26
|
-
* expirationTime: Date.now() + 7 * 24 * 60 * 60 * 1000, // 7 days
|
|
27
|
-
* },
|
|
28
|
-
* );
|
|
29
|
-
* ```
|
|
30
20
|
*/
|
|
31
21
|
const refreshTokenCreate = mutation({
|
|
32
22
|
args: {
|
|
@@ -40,30 +30,20 @@ const refreshTokenCreate = mutation({
|
|
|
40
30
|
}
|
|
41
31
|
});
|
|
42
32
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
* the token has been deleted or never existed.
|
|
47
|
-
*
|
|
48
|
-
* @param args.refreshTokenId - The Convex document ID (`Id<"RefreshToken">`) of the token to retrieve.
|
|
49
|
-
* @returns The refresh token document if it exists, or `null` otherwise.
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* ```ts
|
|
53
|
-
* const token = await ctx.runQuery(
|
|
54
|
-
* component.identity.tokens.refreshTokenGetById,
|
|
55
|
-
* { refreshTokenId: storedTokenId },
|
|
56
|
-
* );
|
|
57
|
-
* if (token !== null && token.expirationTime > Date.now()) {
|
|
58
|
-
* console.log("Refresh token is still valid");
|
|
59
|
-
* }
|
|
60
|
-
* ```
|
|
33
|
+
* Read a refresh token by identity — one function, all-optional args,
|
|
34
|
+
* unioned return: `{ id }` (point lookup) or `{ activeForSession }`
|
|
35
|
+
* (newest unused token for a session).
|
|
61
36
|
*/
|
|
62
|
-
const
|
|
63
|
-
args: {
|
|
37
|
+
const refreshTokenGet = query({
|
|
38
|
+
args: {
|
|
39
|
+
id: v.optional(v.id("RefreshToken")),
|
|
40
|
+
activeForSession: v.optional(v.id("Session"))
|
|
41
|
+
},
|
|
64
42
|
returns: v.union(vRefreshTokenDoc, v.null()),
|
|
65
|
-
handler: async (ctx,
|
|
66
|
-
return await ctx.db.
|
|
43
|
+
handler: async (ctx, args) => {
|
|
44
|
+
if (args.activeForSession !== void 0) return await ctx.db.query("RefreshToken").withIndex("session_id_first_used", (q) => q.eq("sessionId", args.activeForSession).eq("firstUsedTime", void 0)).order("desc").first();
|
|
45
|
+
if (args.id === void 0) return null;
|
|
46
|
+
return await ctx.db.get("RefreshToken", args.id);
|
|
67
47
|
}
|
|
68
48
|
});
|
|
69
49
|
/**
|
|
@@ -77,17 +57,6 @@ const refreshTokenGetById = query({
|
|
|
77
57
|
* @param args.data - A partial object containing the fields to merge (e.g. `{ firstUsedTime: number }`).
|
|
78
58
|
* @returns `null` on success.
|
|
79
59
|
*
|
|
80
|
-
* @example
|
|
81
|
-
* ```ts
|
|
82
|
-
* // Mark the refresh token as used
|
|
83
|
-
* await ctx.runMutation(
|
|
84
|
-
* component.identity.tokens.refreshTokenPatch,
|
|
85
|
-
* {
|
|
86
|
-
* refreshTokenId: token._id,
|
|
87
|
-
* data: { firstUsedTime: Date.now() },
|
|
88
|
-
* },
|
|
89
|
-
* );
|
|
90
|
-
* ```
|
|
91
60
|
*/
|
|
92
61
|
const refreshTokenPatch = mutation({
|
|
93
62
|
args: {
|
|
@@ -112,19 +81,6 @@ const refreshTokenPatch = mutation({
|
|
|
112
81
|
* @param args.parentRefreshTokenId - The document ID of the parent refresh token whose children to retrieve.
|
|
113
82
|
* @returns An array of refresh token documents that were derived from the specified parent token.
|
|
114
83
|
*
|
|
115
|
-
* @example
|
|
116
|
-
* ```ts
|
|
117
|
-
* const children = await ctx.runQuery(
|
|
118
|
-
* component.identity.tokens.refreshTokenGetChildren,
|
|
119
|
-
* {
|
|
120
|
-
* sessionId: session._id,
|
|
121
|
-
* parentRefreshTokenId: parentToken._id,
|
|
122
|
-
* },
|
|
123
|
-
* );
|
|
124
|
-
* if (children.length > 1) {
|
|
125
|
-
* console.warn("Possible token reuse detected!");
|
|
126
|
-
* }
|
|
127
|
-
* ```
|
|
128
84
|
*/
|
|
129
85
|
const refreshTokenGetChildren = query({
|
|
130
86
|
args: {
|
|
@@ -146,14 +102,6 @@ const refreshTokenGetChildren = query({
|
|
|
146
102
|
* @param args.sessionId - The document ID of the session whose refresh tokens should be retrieved.
|
|
147
103
|
* @returns An array of all refresh token documents for the specified session.
|
|
148
104
|
*
|
|
149
|
-
* @example
|
|
150
|
-
* ```ts
|
|
151
|
-
* const tokens = await ctx.runQuery(
|
|
152
|
-
* component.identity.tokens.refreshTokenListBySession,
|
|
153
|
-
* { sessionId: session._id },
|
|
154
|
-
* );
|
|
155
|
-
* console.log(`Session has ${tokens.length} refresh token(s)`);
|
|
156
|
-
* ```
|
|
157
105
|
*/
|
|
158
106
|
const refreshTokenListBySession = query({
|
|
159
107
|
args: { sessionId: v.id("Session") },
|
|
@@ -173,14 +121,6 @@ const refreshTokenListBySession = query({
|
|
|
173
121
|
* @param args.sessionId - The document ID of the session whose refresh tokens should be deleted.
|
|
174
122
|
* @returns `null` on success.
|
|
175
123
|
*
|
|
176
|
-
* @example
|
|
177
|
-
* ```ts
|
|
178
|
-
* // Invalidate all tokens for a compromised session
|
|
179
|
-
* await ctx.runMutation(
|
|
180
|
-
* component.identity.tokens.refreshTokenDeleteAll,
|
|
181
|
-
* { sessionId: session._id },
|
|
182
|
-
* );
|
|
183
|
-
* ```
|
|
184
124
|
*/
|
|
185
125
|
const refreshTokenDeleteAll = mutation({
|
|
186
126
|
args: { sessionId: v.id("Session") },
|
|
@@ -270,37 +210,7 @@ const refreshTokenExchange = mutation({
|
|
|
270
210
|
return null;
|
|
271
211
|
}
|
|
272
212
|
});
|
|
273
|
-
/**
|
|
274
|
-
* Get the active (unused) refresh token for a session.
|
|
275
|
-
*
|
|
276
|
-
* Queries the `RefreshToken` table using the `session_id_first_used` index to
|
|
277
|
-
* find the most recently created token for the session that has not yet been
|
|
278
|
-
* exchanged (i.e. `firstUsedTime` is `undefined`). This represents the current
|
|
279
|
-
* valid refresh token the client should be holding.
|
|
280
|
-
*
|
|
281
|
-
* @param args.sessionId - The document ID of the session whose active refresh token should be retrieved.
|
|
282
|
-
* @returns The most recent unused refresh token document, or `null` if no active token exists
|
|
283
|
-
* (e.g. all tokens have been consumed or the session has no tokens).
|
|
284
|
-
*
|
|
285
|
-
* @example
|
|
286
|
-
* ```ts
|
|
287
|
-
* const activeToken = await ctx.runQuery(
|
|
288
|
-
* component.identity.tokens.refreshTokenGetActive,
|
|
289
|
-
* { sessionId: session._id },
|
|
290
|
-
* );
|
|
291
|
-
* if (activeToken !== null) {
|
|
292
|
-
* console.log(`Active token expires at: ${activeToken.expirationTime}`);
|
|
293
|
-
* }
|
|
294
|
-
* ```
|
|
295
|
-
*/
|
|
296
|
-
const refreshTokenGetActive = query({
|
|
297
|
-
args: { sessionId: v.id("Session") },
|
|
298
|
-
returns: v.union(vRefreshTokenDoc, v.null()),
|
|
299
|
-
handler: async (ctx, { sessionId }) => {
|
|
300
|
-
return await ctx.db.query("RefreshToken").withIndex("session_id_first_used", (q) => q.eq("sessionId", sessionId).eq("firstUsedTime", void 0)).order("desc").first();
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
213
|
|
|
304
214
|
//#endregion
|
|
305
|
-
export { refreshTokenCreate, refreshTokenDeleteAll, refreshTokenExchange,
|
|
215
|
+
export { refreshTokenCreate, refreshTokenDeleteAll, refreshTokenExchange, refreshTokenGet, refreshTokenGetChildren, refreshTokenListBySession, refreshTokenPatch };
|
|
306
216
|
//# sourceMappingURL=tokens.js.map
|