convex-zen 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/dist/cli/generate.d.ts +14 -0
  2. package/dist/cli/generate.d.ts.map +1 -0
  3. package/dist/cli/generate.js +297 -0
  4. package/dist/cli/generate.js.map +1 -0
  5. package/dist/cli/index.d.ts +3 -0
  6. package/dist/cli/index.d.ts.map +1 -0
  7. package/dist/cli/index.js +111 -0
  8. package/dist/cli/index.js.map +1 -0
  9. package/dist/client/index.d.ts +300 -0
  10. package/dist/client/index.d.ts.map +1 -0
  11. package/dist/client/index.js +434 -0
  12. package/dist/client/index.js.map +1 -0
  13. package/dist/client/plugins/admin.d.ts +92 -0
  14. package/dist/client/plugins/admin.d.ts.map +1 -0
  15. package/dist/client/plugins/admin.js +165 -0
  16. package/dist/client/plugins/admin.js.map +1 -0
  17. package/dist/client/primitives.d.ts +57 -0
  18. package/dist/client/primitives.d.ts.map +1 -0
  19. package/dist/client/primitives.js +64 -0
  20. package/dist/client/primitives.js.map +1 -0
  21. package/dist/client/providers.d.ts +14 -0
  22. package/dist/client/providers.d.ts.map +1 -0
  23. package/dist/client/providers.js +25 -0
  24. package/dist/client/providers.js.map +1 -0
  25. package/dist/client/react.d.ts +23 -0
  26. package/dist/client/react.d.ts.map +1 -0
  27. package/dist/client/react.js +48 -0
  28. package/dist/client/react.js.map +1 -0
  29. package/dist/client/tanstack-start-client-plugins.d.ts +34 -0
  30. package/dist/client/tanstack-start-client-plugins.d.ts.map +1 -0
  31. package/dist/client/tanstack-start-client-plugins.js +32 -0
  32. package/dist/client/tanstack-start-client-plugins.js.map +1 -0
  33. package/dist/client/tanstack-start-client.d.ts +52 -0
  34. package/dist/client/tanstack-start-client.d.ts.map +1 -0
  35. package/dist/client/tanstack-start-client.js +130 -0
  36. package/dist/client/tanstack-start-client.js.map +1 -0
  37. package/dist/client/tanstack-start-plugins.d.ts +27 -0
  38. package/dist/client/tanstack-start-plugins.d.ts.map +1 -0
  39. package/dist/client/tanstack-start-plugins.js +145 -0
  40. package/dist/client/tanstack-start-plugins.js.map +1 -0
  41. package/dist/client/tanstack-start.d.ts +130 -0
  42. package/dist/client/tanstack-start.d.ts.map +1 -0
  43. package/dist/client/tanstack-start.js +331 -0
  44. package/dist/client/tanstack-start.js.map +1 -0
  45. package/dist/component/_generated/api.d.ts +50 -0
  46. package/dist/component/_generated/api.d.ts.map +1 -0
  47. package/dist/component/_generated/api.js +31 -0
  48. package/dist/component/_generated/api.js.map +1 -0
  49. package/dist/component/_generated/component.d.ts +92 -0
  50. package/dist/component/_generated/component.d.ts.map +1 -0
  51. package/dist/component/_generated/component.js +11 -0
  52. package/dist/component/_generated/component.js.map +1 -0
  53. package/dist/component/_generated/dataModel.d.ts +46 -0
  54. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  55. package/dist/component/_generated/dataModel.js +11 -0
  56. package/dist/component/_generated/dataModel.js.map +1 -0
  57. package/dist/component/_generated/server.d.ts +121 -0
  58. package/dist/component/_generated/server.d.ts.map +1 -0
  59. package/dist/component/_generated/server.js +78 -0
  60. package/dist/component/_generated/server.js.map +1 -0
  61. package/dist/component/convex.config.d.ts +3 -0
  62. package/dist/component/convex.config.d.ts.map +1 -0
  63. package/dist/component/convex.config.js +4 -0
  64. package/dist/component/convex.config.js.map +1 -0
  65. package/dist/component/core/sessions.d.ts +33 -0
  66. package/dist/component/core/sessions.d.ts.map +1 -0
  67. package/dist/component/core/sessions.js +186 -0
  68. package/dist/component/core/sessions.js.map +1 -0
  69. package/dist/component/core/users.d.ts +19 -0
  70. package/dist/component/core/users.d.ts.map +1 -0
  71. package/dist/component/core/users.js +154 -0
  72. package/dist/component/core/users.js.map +1 -0
  73. package/dist/component/core/verifications.d.ts +34 -0
  74. package/dist/component/core/verifications.d.ts.map +1 -0
  75. package/dist/component/core/verifications.js +135 -0
  76. package/dist/component/core/verifications.js.map +1 -0
  77. package/dist/component/gateway.d.ts +16 -0
  78. package/dist/component/gateway.d.ts.map +1 -0
  79. package/dist/component/gateway.js +229 -0
  80. package/dist/component/gateway.js.map +1 -0
  81. package/dist/component/lib/crypto.d.ts +24 -0
  82. package/dist/component/lib/crypto.d.ts.map +1 -0
  83. package/dist/component/lib/crypto.js +57 -0
  84. package/dist/component/lib/crypto.js.map +1 -0
  85. package/dist/component/lib/rateLimit.d.ts +26 -0
  86. package/dist/component/lib/rateLimit.d.ts.map +1 -0
  87. package/dist/component/lib/rateLimit.js +96 -0
  88. package/dist/component/lib/rateLimit.js.map +1 -0
  89. package/dist/component/lib/validators.d.ts +19 -0
  90. package/dist/component/lib/validators.d.ts.map +1 -0
  91. package/dist/component/lib/validators.js +12 -0
  92. package/dist/component/lib/validators.js.map +1 -0
  93. package/dist/component/plugins/admin.d.ts +72 -0
  94. package/dist/component/plugins/admin.d.ts.map +1 -0
  95. package/dist/component/plugins/admin.js +152 -0
  96. package/dist/component/plugins/admin.js.map +1 -0
  97. package/dist/component/providers/emailPassword.d.ts +49 -0
  98. package/dist/component/providers/emailPassword.d.ts.map +1 -0
  99. package/dist/component/providers/emailPassword.js +316 -0
  100. package/dist/component/providers/emailPassword.js.map +1 -0
  101. package/dist/component/providers/oauth.d.ts +33 -0
  102. package/dist/component/providers/oauth.d.ts.map +1 -0
  103. package/dist/component/providers/oauth.js +256 -0
  104. package/dist/component/providers/oauth.js.map +1 -0
  105. package/dist/component/schema.d.ts +132 -0
  106. package/dist/component/schema.d.ts.map +1 -0
  107. package/dist/component/schema.js +82 -0
  108. package/dist/component/schema.js.map +1 -0
  109. package/dist/types.d.ts +67 -0
  110. package/dist/types.d.ts.map +1 -0
  111. package/dist/types.js +5 -0
  112. package/dist/types.js.map +1 -0
  113. package/package.json +121 -0
  114. package/src/cli/generate.ts +360 -0
  115. package/src/cli/index.ts +133 -0
  116. package/src/client/index.ts +707 -0
  117. package/src/client/plugins/admin.ts +205 -0
  118. package/src/client/primitives.ts +100 -0
  119. package/src/client/providers.ts +35 -0
  120. package/src/client/react.ts +97 -0
  121. package/src/client/tanstack-start-client-plugins.ts +113 -0
  122. package/src/client/tanstack-start-client.ts +259 -0
  123. package/src/client/tanstack-start-plugins.ts +203 -0
  124. package/src/client/tanstack-start.ts +535 -0
  125. package/src/component/_generated/api.ts +70 -0
  126. package/src/component/_generated/component.ts +184 -0
  127. package/src/component/_generated/dataModel.ts +60 -0
  128. package/src/component/_generated/server.ts +156 -0
  129. package/src/component/convex.config.ts +5 -0
  130. package/src/component/core/sessions.ts +228 -0
  131. package/src/component/core/users.ts +199 -0
  132. package/src/component/core/verifications.ts +173 -0
  133. package/src/component/gateway.ts +321 -0
  134. package/src/component/lib/crypto.ts +63 -0
  135. package/src/component/lib/internalApi.ts +66 -0
  136. package/src/component/lib/rateLimit.ts +111 -0
  137. package/src/component/lib/validators.ts +12 -0
  138. package/src/component/plugins/admin.ts +178 -0
  139. package/src/component/providers/emailPassword.ts +374 -0
  140. package/src/component/providers/oauth.ts +324 -0
  141. package/src/component/schema.ts +88 -0
  142. package/src/types.ts +68 -0
@@ -0,0 +1,316 @@
1
+ import { argon2id, argon2Verify } from "hash-wasm";
2
+ import { v } from "convex/values";
3
+ import { internalAction, internalMutation } from "../_generated/server";
4
+ import { internal } from "../_generated/api";
5
+ async function hashPassword(password) {
6
+ return argon2id({
7
+ password,
8
+ salt: crypto.getRandomValues(new Uint8Array(16)),
9
+ parallelism: 1,
10
+ iterations: 2,
11
+ memorySize: 19456, // 19 MB in KB
12
+ hashLength: 32,
13
+ outputType: "encoded", // PHC string — includes salt + params
14
+ });
15
+ }
16
+ /** Simple email validation — no ReDoS-prone regex. */
17
+ function isValidEmail(email) {
18
+ if (email.length > 255)
19
+ return false;
20
+ const atIndex = email.indexOf("@");
21
+ if (atIndex < 1)
22
+ return false;
23
+ const domain = email.slice(atIndex + 1);
24
+ if (domain.length < 3)
25
+ return false;
26
+ if (!domain.includes("."))
27
+ return false;
28
+ return true;
29
+ }
30
+ /**
31
+ * Sign up with email and password.
32
+ *
33
+ * Returns the verification code so the host app (via ConvexAuth client)
34
+ * can send the email. Functions cannot be passed as Convex args.
35
+ *
36
+ * Flow:
37
+ * 1. Validate email
38
+ * 2. Check IP rate limit
39
+ * 3. Check email not already registered
40
+ * 4. Hash password with Argon2id
41
+ * 5. Create user + account
42
+ * 6. Generate verification code
43
+ * 7. Return { status: "verification_required", verificationCode }
44
+ */
45
+ export const signUp = internalAction({
46
+ args: {
47
+ email: v.string(),
48
+ password: v.string(),
49
+ name: v.optional(v.string()),
50
+ ipAddress: v.optional(v.string()),
51
+ },
52
+ handler: async (ctx, args) => {
53
+ const { email, password, name, ipAddress } = args;
54
+ // 1. Validate email
55
+ if (!isValidEmail(email)) {
56
+ throw new Error("Invalid email address");
57
+ }
58
+ // 2. Check IP rate limit
59
+ if (ipAddress) {
60
+ const key = `signup:ip:${ipAddress}`;
61
+ const rateCheck = await ctx.runQuery(internal.lib.rateLimit.check, {
62
+ key,
63
+ });
64
+ if (rateCheck.limited) {
65
+ throw new Error("Too many requests. Please try again later.");
66
+ }
67
+ }
68
+ // 3. Check email not already registered
69
+ const existingUser = await ctx.runQuery(internal.core.users.getByEmail, {
70
+ email: email.toLowerCase(),
71
+ });
72
+ if (existingUser) {
73
+ // Increment rate limit to avoid timing-based email enumeration
74
+ if (ipAddress) {
75
+ await ctx.runMutation(internal.lib.rateLimit.increment, {
76
+ key: `signup:ip:${ipAddress}`,
77
+ });
78
+ }
79
+ throw new Error("Email already registered");
80
+ }
81
+ // 4. Hash password with Argon2id
82
+ const passwordHash = await hashPassword(password);
83
+ // 5. Create user + account
84
+ const userId = await ctx.runMutation(internal.core.users.create, {
85
+ email: email.toLowerCase(),
86
+ emailVerified: false,
87
+ name,
88
+ });
89
+ await ctx.runMutation(internal.core.users.createAccount, {
90
+ userId,
91
+ providerId: "credential",
92
+ accountId: email.toLowerCase(),
93
+ passwordHash,
94
+ });
95
+ // 6. Generate verification code (returned to caller for email sending)
96
+ const { code, expiresAt } = await ctx.runMutation(internal.core.verifications.create, {
97
+ identifier: email.toLowerCase(),
98
+ type: "email-verification",
99
+ });
100
+ // Schedule cleanup at expiry
101
+ await ctx.scheduler.runAt(expiresAt, internal.core.verifications.cleanup, {});
102
+ return { status: "verification_required", verificationCode: code };
103
+ },
104
+ });
105
+ /**
106
+ * Sign in with email and password.
107
+ *
108
+ * Flow:
109
+ * 1. Check rate limits (IP + email)
110
+ * 2. Look up account by email
111
+ * 3. Verify Argon2id hash
112
+ * 4. Check banned status
113
+ * 5. Create session
114
+ * 6. Return { sessionToken, userId }
115
+ */
116
+ export const signIn = internalAction({
117
+ args: {
118
+ email: v.string(),
119
+ password: v.string(),
120
+ ipAddress: v.optional(v.string()),
121
+ userAgent: v.optional(v.string()),
122
+ requireEmailVerified: v.optional(v.boolean()),
123
+ },
124
+ handler: async (ctx, args) => {
125
+ const { email, password, ipAddress, userAgent, requireEmailVerified } = args;
126
+ const normalizedEmail = email.toLowerCase();
127
+ // 1. Check rate limits
128
+ const rateLimitKeys = [];
129
+ if (ipAddress)
130
+ rateLimitKeys.push(`signin:ip:${ipAddress}`);
131
+ rateLimitKeys.push(`signin:email:${normalizedEmail}`);
132
+ for (const key of rateLimitKeys) {
133
+ const rateCheck = await ctx.runQuery(internal.lib.rateLimit.check, {
134
+ key,
135
+ });
136
+ if (rateCheck.limited) {
137
+ throw new Error("Too many failed attempts. Please try again later.");
138
+ }
139
+ }
140
+ // Helper to record failure
141
+ const recordFailure = async () => {
142
+ for (const key of rateLimitKeys) {
143
+ await ctx.runMutation(internal.lib.rateLimit.increment, { key });
144
+ }
145
+ };
146
+ // 2. Look up account by email
147
+ const account = await ctx.runQuery(internal.core.users.getAccount, {
148
+ providerId: "credential",
149
+ accountId: normalizedEmail,
150
+ });
151
+ if (!account || !account.passwordHash) {
152
+ await recordFailure();
153
+ throw new Error("Invalid email or password");
154
+ }
155
+ // 3. Verify Argon2id hash
156
+ const isValid = await argon2Verify({
157
+ password,
158
+ hash: account.passwordHash,
159
+ });
160
+ if (!isValid) {
161
+ await recordFailure();
162
+ throw new Error("Invalid email or password");
163
+ }
164
+ // 4. Check email verified if required
165
+ const user = await ctx.runQuery(internal.core.users.getById, {
166
+ userId: account.userId,
167
+ });
168
+ if (requireEmailVerified && !user?.emailVerified) {
169
+ throw new Error("Email address not verified");
170
+ }
171
+ // 4b. Check banned status
172
+ if (user?.banned) {
173
+ const now = Date.now();
174
+ if (user.banExpires === undefined || user.banExpires > now) {
175
+ throw new Error(`Account banned${user.banReason ? ": " + user.banReason : ""}`);
176
+ }
177
+ }
178
+ // 5. Reset rate limits on success
179
+ for (const key of rateLimitKeys) {
180
+ await ctx.runMutation(internal.lib.rateLimit.reset, { key });
181
+ }
182
+ // 6. Create session
183
+ const sessionToken = await ctx.runMutation(internal.core.sessions.create, {
184
+ userId: account.userId,
185
+ ipAddress,
186
+ userAgent,
187
+ });
188
+ return { sessionToken, userId: account.userId };
189
+ },
190
+ });
191
+ /**
192
+ * Verify email address with a verification code.
193
+ */
194
+ export const verifyEmail = internalAction({
195
+ args: {
196
+ email: v.string(),
197
+ code: v.string(),
198
+ },
199
+ handler: async (ctx, { email, code }) => {
200
+ const normalizedEmail = email.toLowerCase();
201
+ const result = await ctx.runMutation(internal.core.verifications.verify, {
202
+ identifier: normalizedEmail,
203
+ type: "email-verification",
204
+ code,
205
+ });
206
+ if (result.status !== "valid") {
207
+ return result;
208
+ }
209
+ // Mark user as verified
210
+ const user = await ctx.runQuery(internal.core.users.getByEmail, {
211
+ email: normalizedEmail,
212
+ });
213
+ if (user) {
214
+ await ctx.runMutation(internal.core.users.update, {
215
+ userId: user._id,
216
+ emailVerified: true,
217
+ });
218
+ }
219
+ return { status: "valid" };
220
+ },
221
+ });
222
+ /**
223
+ * Request a password reset code.
224
+ * Returns the reset code so the host app can send the email.
225
+ * Always returns { status: "sent" } to prevent email enumeration.
226
+ * Returns resetCode only when a real user was found.
227
+ */
228
+ export const requestPasswordReset = internalAction({
229
+ args: {
230
+ email: v.string(),
231
+ ipAddress: v.optional(v.string()),
232
+ },
233
+ handler: async (ctx, args) => {
234
+ const { email, ipAddress } = args;
235
+ const normalizedEmail = email.toLowerCase();
236
+ // Rate limit
237
+ if (ipAddress) {
238
+ const key = `reset:ip:${ipAddress}`;
239
+ const rateCheck = await ctx.runQuery(internal.lib.rateLimit.check, {
240
+ key,
241
+ });
242
+ if (rateCheck.limited) {
243
+ // Don't reveal if email exists; return success silently
244
+ return { status: "sent", resetCode: null };
245
+ }
246
+ await ctx.runMutation(internal.lib.rateLimit.increment, { key });
247
+ }
248
+ const user = await ctx.runQuery(internal.core.users.getByEmail, {
249
+ email: normalizedEmail,
250
+ });
251
+ if (!user) {
252
+ // Always return success to prevent email enumeration
253
+ return { status: "sent", resetCode: null };
254
+ }
255
+ const { code, expiresAt } = await ctx.runMutation(internal.core.verifications.create, {
256
+ identifier: normalizedEmail,
257
+ type: "password-reset",
258
+ });
259
+ await ctx.scheduler.runAt(expiresAt, internal.core.verifications.cleanup, {});
260
+ return { status: "sent", resetCode: code };
261
+ },
262
+ });
263
+ /**
264
+ * Reset password using a verification code.
265
+ */
266
+ export const resetPassword = internalAction({
267
+ args: {
268
+ email: v.string(),
269
+ code: v.string(),
270
+ newPassword: v.string(),
271
+ },
272
+ handler: async (ctx, { email, code, newPassword }) => {
273
+ const normalizedEmail = email.toLowerCase();
274
+ const result = await ctx.runMutation(internal.core.verifications.verify, {
275
+ identifier: normalizedEmail,
276
+ type: "password-reset",
277
+ code,
278
+ });
279
+ if (result.status !== "valid") {
280
+ return result;
281
+ }
282
+ // Hash new password
283
+ const passwordHash = await hashPassword(newPassword);
284
+ // Update account password hash
285
+ const account = await ctx.runQuery(internal.core.users.getAccount, {
286
+ providerId: "credential",
287
+ accountId: normalizedEmail,
288
+ });
289
+ if (!account) {
290
+ throw new Error("Account not found");
291
+ }
292
+ await ctx.runMutation(internal.providers.emailPassword.updatePasswordHash, {
293
+ accountId: account._id,
294
+ passwordHash,
295
+ });
296
+ // Invalidate all sessions (force re-login after password reset)
297
+ await ctx.runMutation(internal.core.sessions.invalidateAll, {
298
+ userId: account.userId,
299
+ });
300
+ return { status: "valid" };
301
+ },
302
+ });
303
+ /** Internal mutation to update password hash on an account. */
304
+ export const updatePasswordHash = internalMutation({
305
+ args: {
306
+ accountId: v.id("accounts"),
307
+ passwordHash: v.string(),
308
+ },
309
+ handler: async (ctx, { accountId, passwordHash }) => {
310
+ await ctx.db.patch(accountId, {
311
+ passwordHash,
312
+ updatedAt: Date.now(),
313
+ });
314
+ },
315
+ });
316
+ //# sourceMappingURL=emailPassword.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"emailPassword.js","sourceRoot":"","sources":["../../../src/component/providers/emailPassword.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import { type GenericId } from "convex/values";
2
+ /** Store OAuth state and code verifier for PKCE flow. */
3
+ export declare const storeOAuthState: import("convex/server").RegisteredMutation<"internal", {
4
+ redirectUrl?: string;
5
+ expiresAt: number;
6
+ stateHash: string;
7
+ codeVerifier: string;
8
+ provider: string;
9
+ }, Promise<void>>;
10
+ /** Retrieve and delete an OAuth state record by state hash (single-use). */
11
+ export declare const consumeOAuthState: import("convex/server").RegisteredMutation<"internal", {
12
+ stateHash: string;
13
+ }, Promise<{
14
+ _id: GenericId<"oauthStates">;
15
+ _creationTime: number;
16
+ redirectUrl?: string;
17
+ createdAt: number;
18
+ expiresAt: number;
19
+ stateHash: string;
20
+ codeVerifier: string;
21
+ provider: string;
22
+ } | null>>;
23
+ /**
24
+ * Initiate an OAuth authorization flow.
25
+ * Returns the authorization URL with PKCE + state parameters.
26
+ */
27
+ export declare const getAuthorizationUrl: any;
28
+ /**
29
+ * Handle the OAuth callback.
30
+ * Validates state, exchanges code for tokens, upserts user, creates session.
31
+ */
32
+ export declare const handleCallback: any;
33
+ //# sourceMappingURL=oauth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../../../src/component/providers/oauth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AA6ClD,yDAAyD;AACzD,eAAO,MAAM,eAAe;;;;;;iBAmB1B,CAAC;AAEH,4EAA4E;AAC5E,eAAO,MAAM,iBAAiB;;;;;;;;;;;UAmB5B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB,KAyC9B,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,cAAc,KA6KzB,CAAC"}
@@ -0,0 +1,256 @@
1
+ import { v } from "convex/values";
2
+ import { internalAction, internalMutation } from "../_generated/server";
3
+ import { internal } from "../_generated/api";
4
+ import { generateState, generateCodeVerifier, generateCodeChallenge, hashToken, } from "../lib/crypto";
5
+ import { oauthProviderConfigValidator } from "../lib/validators";
6
+ /** OAuth state TTL: 10 minutes. */
7
+ const STATE_TTL_MS = 10 * 60 * 1000;
8
+ function assertValidProviderConfig(provider) {
9
+ const required = [
10
+ provider.id,
11
+ provider.clientId,
12
+ provider.clientSecret,
13
+ provider.authorizationUrl,
14
+ provider.tokenUrl,
15
+ provider.userInfoUrl,
16
+ ];
17
+ if (required.some((value) => value.trim().length === 0)) {
18
+ throw new Error("Invalid OAuth provider configuration");
19
+ }
20
+ if (provider.scopes.length === 0) {
21
+ throw new Error("OAuth provider scopes must not be empty");
22
+ }
23
+ const urls = [provider.authorizationUrl, provider.tokenUrl, provider.userInfoUrl];
24
+ for (const rawUrl of urls) {
25
+ let parsed;
26
+ try {
27
+ parsed = new URL(rawUrl);
28
+ }
29
+ catch {
30
+ throw new Error("Invalid OAuth provider URL");
31
+ }
32
+ if (parsed.protocol !== "https:") {
33
+ throw new Error("OAuth provider URLs must use HTTPS");
34
+ }
35
+ }
36
+ }
37
+ /** Store OAuth state and code verifier for PKCE flow. */
38
+ export const storeOAuthState = internalMutation({
39
+ args: {
40
+ stateHash: v.string(),
41
+ codeVerifier: v.string(),
42
+ provider: v.string(),
43
+ redirectUrl: v.optional(v.string()),
44
+ expiresAt: v.number(),
45
+ },
46
+ handler: async (ctx, args) => {
47
+ const now = Date.now();
48
+ await ctx.db.insert("oauthStates", {
49
+ stateHash: args.stateHash,
50
+ codeVerifier: args.codeVerifier,
51
+ provider: args.provider,
52
+ redirectUrl: args.redirectUrl,
53
+ expiresAt: args.expiresAt,
54
+ createdAt: now,
55
+ });
56
+ },
57
+ });
58
+ /** Retrieve and delete an OAuth state record by state hash (single-use). */
59
+ export const consumeOAuthState = internalMutation({
60
+ args: { stateHash: v.string() },
61
+ handler: async (ctx, { stateHash }) => {
62
+ const record = await ctx.db
63
+ .query("oauthStates")
64
+ .withIndex("by_stateHash", (q) => q.eq("stateHash", stateHash))
65
+ .unique();
66
+ if (!record)
67
+ return null;
68
+ // Delete immediately (single-use)
69
+ await ctx.db.delete(record._id);
70
+ if (record.expiresAt < Date.now()) {
71
+ return null; // Expired
72
+ }
73
+ return record;
74
+ },
75
+ });
76
+ /**
77
+ * Initiate an OAuth authorization flow.
78
+ * Returns the authorization URL with PKCE + state parameters.
79
+ */
80
+ export const getAuthorizationUrl = internalAction({
81
+ args: {
82
+ provider: oauthProviderConfigValidator,
83
+ redirectUrl: v.optional(v.string()),
84
+ },
85
+ handler: async (ctx, args) => {
86
+ const provider = args.provider;
87
+ assertValidProviderConfig(provider);
88
+ // Generate state (32 bytes = 256 bit)
89
+ const state = generateState();
90
+ const stateHash = await hashToken(state);
91
+ // Generate PKCE code verifier and challenge
92
+ const codeVerifier = generateCodeVerifier();
93
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
94
+ const expiresAt = Date.now() + STATE_TTL_MS;
95
+ await ctx.runMutation(internal.providers.oauth.storeOAuthState, {
96
+ stateHash,
97
+ codeVerifier,
98
+ provider: provider.id,
99
+ redirectUrl: args.redirectUrl,
100
+ expiresAt,
101
+ });
102
+ // Build authorization URL
103
+ const url = new URL(provider.authorizationUrl);
104
+ url.searchParams.set("client_id", provider.clientId);
105
+ url.searchParams.set("response_type", "code");
106
+ url.searchParams.set("scope", provider.scopes.join(" "));
107
+ url.searchParams.set("state", state);
108
+ url.searchParams.set("code_challenge", codeChallenge);
109
+ url.searchParams.set("code_challenge_method", "S256");
110
+ if (args.redirectUrl) {
111
+ url.searchParams.set("redirect_uri", args.redirectUrl);
112
+ }
113
+ return { authorizationUrl: url.toString() };
114
+ },
115
+ });
116
+ /**
117
+ * Handle the OAuth callback.
118
+ * Validates state, exchanges code for tokens, upserts user, creates session.
119
+ */
120
+ export const handleCallback = internalAction({
121
+ args: {
122
+ provider: oauthProviderConfigValidator,
123
+ code: v.string(),
124
+ state: v.string(),
125
+ redirectUrl: v.optional(v.string()),
126
+ ipAddress: v.optional(v.string()),
127
+ userAgent: v.optional(v.string()),
128
+ },
129
+ handler: async (ctx, args) => {
130
+ const provider = args.provider;
131
+ assertValidProviderConfig(provider);
132
+ // Use global fetch; tests can mock it via vi.spyOn(globalThis, 'fetch')
133
+ const fetchFn = fetch;
134
+ // 1. Validate state against stored stateHash
135
+ const stateHash = await hashToken(args.state);
136
+ const stateRecord = await ctx.runMutation(internal.providers.oauth.consumeOAuthState, { stateHash });
137
+ if (!stateRecord) {
138
+ throw new Error("Invalid or expired OAuth state");
139
+ }
140
+ if (stateRecord.provider !== provider.id) {
141
+ throw new Error("Provider mismatch in OAuth state");
142
+ }
143
+ // 2. Exchange code + code_verifier for tokens
144
+ const tokenParams = new URLSearchParams({
145
+ grant_type: "authorization_code",
146
+ client_id: provider.clientId,
147
+ client_secret: provider.clientSecret,
148
+ code: args.code,
149
+ code_verifier: stateRecord.codeVerifier,
150
+ });
151
+ if (args.redirectUrl ?? stateRecord.redirectUrl) {
152
+ tokenParams.set("redirect_uri", (args.redirectUrl ?? stateRecord.redirectUrl));
153
+ }
154
+ const tokenRes = await fetchFn(provider.tokenUrl, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
157
+ body: tokenParams.toString(),
158
+ });
159
+ if (!tokenRes.ok) {
160
+ throw new Error(`Token exchange failed: ${tokenRes.status}`);
161
+ }
162
+ const tokens = await tokenRes.json();
163
+ // 3. Fetch user profile from provider
164
+ const userRes = await fetchFn(provider.userInfoUrl, {
165
+ headers: { Authorization: `Bearer ${tokens.access_token}` },
166
+ });
167
+ if (!userRes.ok) {
168
+ throw new Error(`User info fetch failed: ${userRes.status}`);
169
+ }
170
+ const profile = await userRes.json();
171
+ const providerId = provider.id;
172
+ const accountId = (profile.id ?? profile.sub)?.toString();
173
+ if (!accountId) {
174
+ throw new Error("Could not determine provider user ID");
175
+ }
176
+ const email = profile.email?.toLowerCase();
177
+ const name = profile.name ?? profile.login;
178
+ const image = profile.picture ?? profile.avatar_url;
179
+ const accessTokenExpiresAt = tokens.expires_in
180
+ ? Date.now() + tokens.expires_in * 1000
181
+ : undefined;
182
+ // 4. Find existing account or create user + account (account linking by email)
183
+ const existingAccount = await ctx.runQuery(internal.core.users.getAccount, {
184
+ providerId,
185
+ accountId,
186
+ });
187
+ let userId;
188
+ if (existingAccount) {
189
+ // Update tokens on existing account
190
+ await ctx.runMutation(internal.core.users.updateAccount, {
191
+ accountId: existingAccount._id,
192
+ accessToken: tokens.access_token,
193
+ refreshToken: tokens.refresh_token,
194
+ accessTokenExpiresAt,
195
+ });
196
+ userId = existingAccount.userId;
197
+ }
198
+ else {
199
+ // Try to link by email
200
+ const existingUser = email
201
+ ? await ctx.runQuery(internal.core.users.getByEmail, { email })
202
+ : null;
203
+ if (!existingUser && email) {
204
+ // Create new user
205
+ userId = await ctx.runMutation(internal.core.users.create, {
206
+ email,
207
+ emailVerified: true, // OAuth providers verify email
208
+ name,
209
+ image,
210
+ });
211
+ }
212
+ else if (existingUser) {
213
+ userId = existingUser._id;
214
+ // Update profile info from OAuth
215
+ await ctx.runMutation(internal.core.users.update, {
216
+ userId: existingUser._id,
217
+ emailVerified: true,
218
+ name: name ?? existingUser.name,
219
+ image: image ?? existingUser.image,
220
+ });
221
+ }
222
+ else {
223
+ throw new Error("OAuth provider did not return an email address");
224
+ }
225
+ // Create account entry
226
+ await ctx.runMutation(internal.core.users.createAccount, {
227
+ userId,
228
+ providerId,
229
+ accountId,
230
+ accessToken: tokens.access_token,
231
+ refreshToken: tokens.refresh_token,
232
+ accessTokenExpiresAt,
233
+ });
234
+ }
235
+ // 5. Check banned status
236
+ const user = await ctx.runQuery(internal.core.users.getById, { userId });
237
+ if (user?.banned) {
238
+ const now = Date.now();
239
+ if (user.banExpires === undefined || user.banExpires > now) {
240
+ throw new Error(`Account banned${user.banReason ? ": " + user.banReason : ""}`);
241
+ }
242
+ }
243
+ // 6. Create session
244
+ const sessionToken = await ctx.runMutation(internal.core.sessions.create, {
245
+ userId,
246
+ ipAddress: args.ipAddress,
247
+ userAgent: args.userAgent,
248
+ });
249
+ return {
250
+ sessionToken,
251
+ userId,
252
+ redirectUrl: stateRecord.redirectUrl,
253
+ };
254
+ },
255
+ });
256
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../src/component/providers/oauth.ts"],"names":[],"mappings":"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"}