@robelest/convex-auth 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 (280) hide show
  1. package/README.md +6 -0
  2. package/dist/bin.cjs +27733 -0
  3. package/dist/client/index.d.ts +49 -0
  4. package/dist/client/index.d.ts.map +1 -0
  5. package/dist/client/index.js +283 -0
  6. package/dist/client/index.js.map +1 -0
  7. package/dist/component/_generated/api.d.ts +36 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -0
  9. package/dist/component/_generated/api.js +31 -0
  10. package/dist/component/_generated/api.js.map +1 -0
  11. package/dist/component/_generated/component.d.ts +295 -0
  12. package/dist/component/_generated/component.d.ts.map +1 -0
  13. package/dist/component/_generated/component.js +11 -0
  14. package/dist/component/_generated/component.js.map +1 -0
  15. package/dist/component/_generated/dataModel.d.ts +46 -0
  16. package/dist/component/_generated/dataModel.d.ts.map +1 -0
  17. package/dist/component/_generated/dataModel.js +11 -0
  18. package/dist/component/_generated/dataModel.js.map +1 -0
  19. package/dist/component/_generated/server.d.ts +121 -0
  20. package/dist/component/_generated/server.d.ts.map +1 -0
  21. package/dist/component/_generated/server.js +78 -0
  22. package/dist/component/_generated/server.js.map +1 -0
  23. package/dist/component/convex.config.d.ts +3 -0
  24. package/dist/component/convex.config.d.ts.map +1 -0
  25. package/dist/component/convex.config.js +4 -0
  26. package/dist/component/convex.config.js.map +1 -0
  27. package/dist/component/index.d.ts +15 -0
  28. package/dist/component/index.d.ts.map +1 -0
  29. package/dist/component/index.js +13 -0
  30. package/dist/component/index.js.map +1 -0
  31. package/dist/component/public.d.ts +450 -0
  32. package/dist/component/public.d.ts.map +1 -0
  33. package/dist/component/public.js +528 -0
  34. package/dist/component/public.js.map +1 -0
  35. package/dist/component/schema.d.ts +107 -0
  36. package/dist/component/schema.d.ts.map +1 -0
  37. package/dist/component/schema.js +26 -0
  38. package/dist/component/schema.js.map +1 -0
  39. package/dist/providers/Anonymous.d.ts +50 -0
  40. package/dist/providers/Anonymous.d.ts.map +1 -0
  41. package/dist/providers/Anonymous.js +39 -0
  42. package/dist/providers/Anonymous.js.map +1 -0
  43. package/dist/providers/ConvexCredentials.d.ts +88 -0
  44. package/dist/providers/ConvexCredentials.d.ts.map +1 -0
  45. package/dist/providers/ConvexCredentials.js +37 -0
  46. package/dist/providers/ConvexCredentials.js.map +1 -0
  47. package/dist/providers/Email.d.ts +33 -0
  48. package/dist/providers/Email.d.ts.map +1 -0
  49. package/dist/providers/Email.js +50 -0
  50. package/dist/providers/Email.js.map +1 -0
  51. package/dist/providers/Password.d.ts +95 -0
  52. package/dist/providers/Password.d.ts.map +1 -0
  53. package/dist/providers/Password.js +174 -0
  54. package/dist/providers/Password.js.map +1 -0
  55. package/dist/providers/Phone.d.ts +22 -0
  56. package/dist/providers/Phone.d.ts.map +1 -0
  57. package/dist/providers/Phone.js +37 -0
  58. package/dist/providers/Phone.js.map +1 -0
  59. package/dist/server/convex_types.d.ts +17 -0
  60. package/dist/server/convex_types.d.ts.map +1 -0
  61. package/dist/server/convex_types.js +2 -0
  62. package/dist/server/convex_types.js.map +1 -0
  63. package/dist/server/cookies.d.ts +35 -0
  64. package/dist/server/cookies.d.ts.map +1 -0
  65. package/dist/server/cookies.js +34 -0
  66. package/dist/server/cookies.js.map +1 -0
  67. package/dist/server/implementation/db.d.ts +80 -0
  68. package/dist/server/implementation/db.d.ts.map +1 -0
  69. package/dist/server/implementation/db.js +59 -0
  70. package/dist/server/implementation/db.js.map +1 -0
  71. package/dist/server/implementation/index.d.ts +370 -0
  72. package/dist/server/implementation/index.d.ts.map +1 -0
  73. package/dist/server/implementation/index.js +521 -0
  74. package/dist/server/implementation/index.js.map +1 -0
  75. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts +33 -0
  76. package/dist/server/implementation/mutations/createAccountFromCredentials.d.ts.map +1 -0
  77. package/dist/server/implementation/mutations/createAccountFromCredentials.js +71 -0
  78. package/dist/server/implementation/mutations/createAccountFromCredentials.js.map +1 -0
  79. package/dist/server/implementation/mutations/createVerificationCode.d.ts +25 -0
  80. package/dist/server/implementation/mutations/createVerificationCode.d.ts.map +1 -0
  81. package/dist/server/implementation/mutations/createVerificationCode.js +84 -0
  82. package/dist/server/implementation/mutations/createVerificationCode.js.map +1 -0
  83. package/dist/server/implementation/mutations/index.d.ts +304 -0
  84. package/dist/server/implementation/mutations/index.d.ts.map +1 -0
  85. package/dist/server/implementation/mutations/index.js +108 -0
  86. package/dist/server/implementation/mutations/index.js.map +1 -0
  87. package/dist/server/implementation/mutations/invalidateSessions.d.ts +13 -0
  88. package/dist/server/implementation/mutations/invalidateSessions.d.ts.map +1 -0
  89. package/dist/server/implementation/mutations/invalidateSessions.js +35 -0
  90. package/dist/server/implementation/mutations/invalidateSessions.js.map +1 -0
  91. package/dist/server/implementation/mutations/modifyAccount.d.ts +23 -0
  92. package/dist/server/implementation/mutations/modifyAccount.d.ts.map +1 -0
  93. package/dist/server/implementation/mutations/modifyAccount.js +48 -0
  94. package/dist/server/implementation/mutations/modifyAccount.js.map +1 -0
  95. package/dist/server/implementation/mutations/refreshSession.d.ts +16 -0
  96. package/dist/server/implementation/mutations/refreshSession.d.ts.map +1 -0
  97. package/dist/server/implementation/mutations/refreshSession.js +116 -0
  98. package/dist/server/implementation/mutations/refreshSession.js.map +1 -0
  99. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts +27 -0
  100. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.d.ts.map +1 -0
  101. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js +55 -0
  102. package/dist/server/implementation/mutations/retrieveAccountWithCredentials.js.map +1 -0
  103. package/dist/server/implementation/mutations/signIn.d.ts +17 -0
  104. package/dist/server/implementation/mutations/signIn.d.ts.map +1 -0
  105. package/dist/server/implementation/mutations/signIn.js +26 -0
  106. package/dist/server/implementation/mutations/signIn.js.map +1 -0
  107. package/dist/server/implementation/mutations/signOut.d.ts +11 -0
  108. package/dist/server/implementation/mutations/signOut.d.ts.map +1 -0
  109. package/dist/server/implementation/mutations/signOut.js +24 -0
  110. package/dist/server/implementation/mutations/signOut.js.map +1 -0
  111. package/dist/server/implementation/mutations/userOAuth.d.ts +19 -0
  112. package/dist/server/implementation/mutations/userOAuth.d.ts.map +1 -0
  113. package/dist/server/implementation/mutations/userOAuth.js +84 -0
  114. package/dist/server/implementation/mutations/userOAuth.js.map +1 -0
  115. package/dist/server/implementation/mutations/verifier.d.ts +8 -0
  116. package/dist/server/implementation/mutations/verifier.d.ts.map +1 -0
  117. package/dist/server/implementation/mutations/verifier.js +19 -0
  118. package/dist/server/implementation/mutations/verifier.js.map +1 -0
  119. package/dist/server/implementation/mutations/verifierSignature.d.ts +15 -0
  120. package/dist/server/implementation/mutations/verifierSignature.d.ts.map +1 -0
  121. package/dist/server/implementation/mutations/verifierSignature.js +29 -0
  122. package/dist/server/implementation/mutations/verifierSignature.js.map +1 -0
  123. package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts +21 -0
  124. package/dist/server/implementation/mutations/verifyCodeAndSignIn.d.ts.map +1 -0
  125. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js +127 -0
  126. package/dist/server/implementation/mutations/verifyCodeAndSignIn.js.map +1 -0
  127. package/dist/server/implementation/provider.d.ts +6 -0
  128. package/dist/server/implementation/provider.d.ts.map +1 -0
  129. package/dist/server/implementation/provider.js +21 -0
  130. package/dist/server/implementation/provider.js.map +1 -0
  131. package/dist/server/implementation/rateLimit.d.ts +6 -0
  132. package/dist/server/implementation/rateLimit.d.ts.map +1 -0
  133. package/dist/server/implementation/rateLimit.js +76 -0
  134. package/dist/server/implementation/rateLimit.js.map +1 -0
  135. package/dist/server/implementation/redirects.d.ts +6 -0
  136. package/dist/server/implementation/redirects.d.ts.map +1 -0
  137. package/dist/server/implementation/redirects.js +40 -0
  138. package/dist/server/implementation/redirects.js.map +1 -0
  139. package/dist/server/implementation/refreshTokens.d.ts +40 -0
  140. package/dist/server/implementation/refreshTokens.d.ts.map +1 -0
  141. package/dist/server/implementation/refreshTokens.js +160 -0
  142. package/dist/server/implementation/refreshTokens.js.map +1 -0
  143. package/dist/server/implementation/sessions.d.ts +43 -0
  144. package/dist/server/implementation/sessions.d.ts.map +1 -0
  145. package/dist/server/implementation/sessions.js +94 -0
  146. package/dist/server/implementation/sessions.js.map +1 -0
  147. package/dist/server/implementation/signIn.d.ts +31 -0
  148. package/dist/server/implementation/signIn.d.ts.map +1 -0
  149. package/dist/server/implementation/signIn.js +148 -0
  150. package/dist/server/implementation/signIn.js.map +1 -0
  151. package/dist/server/implementation/tokens.d.ts +7 -0
  152. package/dist/server/implementation/tokens.d.ts.map +1 -0
  153. package/dist/server/implementation/tokens.js +18 -0
  154. package/dist/server/implementation/tokens.js.map +1 -0
  155. package/dist/server/implementation/types.d.ts +288 -0
  156. package/dist/server/implementation/types.d.ts.map +1 -0
  157. package/dist/server/implementation/types.js +182 -0
  158. package/dist/server/implementation/types.js.map +1 -0
  159. package/dist/server/implementation/users.d.ts +27 -0
  160. package/dist/server/implementation/users.d.ts.map +1 -0
  161. package/dist/server/implementation/users.js +181 -0
  162. package/dist/server/implementation/users.js.map +1 -0
  163. package/dist/server/implementation/utils.d.ts +17 -0
  164. package/dist/server/implementation/utils.d.ts.map +1 -0
  165. package/dist/server/implementation/utils.js +72 -0
  166. package/dist/server/implementation/utils.js.map +1 -0
  167. package/dist/server/index.d.ts +17 -0
  168. package/dist/server/index.d.ts.map +1 -0
  169. package/dist/server/index.js +54 -0
  170. package/dist/server/index.js.map +1 -0
  171. package/dist/server/oauth/authorizationUrl.d.ts +13 -0
  172. package/dist/server/oauth/authorizationUrl.d.ts.map +1 -0
  173. package/dist/server/oauth/authorizationUrl.js +91 -0
  174. package/dist/server/oauth/authorizationUrl.js.map +1 -0
  175. package/dist/server/oauth/callback.d.ts +19 -0
  176. package/dist/server/oauth/callback.d.ts.map +1 -0
  177. package/dist/server/oauth/callback.js +173 -0
  178. package/dist/server/oauth/callback.js.map +1 -0
  179. package/dist/server/oauth/checks.d.ts +52 -0
  180. package/dist/server/oauth/checks.d.ts.map +1 -0
  181. package/dist/server/oauth/checks.js +106 -0
  182. package/dist/server/oauth/checks.js.map +1 -0
  183. package/dist/server/oauth/convexAuth.d.ts +12 -0
  184. package/dist/server/oauth/convexAuth.d.ts.map +1 -0
  185. package/dist/server/oauth/convexAuth.js +137 -0
  186. package/dist/server/oauth/convexAuth.js.map +1 -0
  187. package/dist/server/oauth/lib/utils/customFetch.d.ts +9 -0
  188. package/dist/server/oauth/lib/utils/customFetch.d.ts.map +1 -0
  189. package/dist/server/oauth/lib/utils/customFetch.js +11 -0
  190. package/dist/server/oauth/lib/utils/customFetch.js.map +1 -0
  191. package/dist/server/oauth/lib/utils/providers.d.ts +3 -0
  192. package/dist/server/oauth/lib/utils/providers.d.ts.map +1 -0
  193. package/dist/server/oauth/lib/utils/providers.js +7 -0
  194. package/dist/server/oauth/lib/utils/providers.js.map +1 -0
  195. package/dist/server/oauth/providers/oauth.d.ts +43 -0
  196. package/dist/server/oauth/providers/oauth.d.ts.map +1 -0
  197. package/dist/server/oauth/providers/oauth.js +3 -0
  198. package/dist/server/oauth/providers/oauth.js.map +1 -0
  199. package/dist/server/oauth/types.d.ts +24 -0
  200. package/dist/server/oauth/types.d.ts.map +1 -0
  201. package/dist/server/oauth/types.js +5 -0
  202. package/dist/server/oauth/types.js.map +1 -0
  203. package/dist/server/provider_utils.d.ts +76 -0
  204. package/dist/server/provider_utils.d.ts.map +1 -0
  205. package/dist/server/provider_utils.js +177 -0
  206. package/dist/server/provider_utils.js.map +1 -0
  207. package/dist/server/types.d.ts +412 -0
  208. package/dist/server/types.d.ts.map +1 -0
  209. package/dist/server/types.js +2 -0
  210. package/dist/server/types.js.map +1 -0
  211. package/dist/server/utils.d.ts +3 -0
  212. package/dist/server/utils.d.ts.map +1 -0
  213. package/dist/server/utils.js +11 -0
  214. package/dist/server/utils.js.map +1 -0
  215. package/package.json +126 -0
  216. package/providers/Anonymous/package.json +6 -0
  217. package/providers/ConvexCredentials/package.json +6 -0
  218. package/providers/Email/package.json +6 -0
  219. package/providers/Password/package.json +6 -0
  220. package/providers/Phone/package.json +6 -0
  221. package/server/package.json +6 -0
  222. package/src/cli/command.ts +69 -0
  223. package/src/cli/generateKeys.ts +20 -0
  224. package/src/cli/index.ts +840 -0
  225. package/src/client/index.ts +415 -0
  226. package/src/component/_generated/api.ts +52 -0
  227. package/src/component/_generated/component.ts +586 -0
  228. package/src/component/_generated/dataModel.ts +60 -0
  229. package/src/component/_generated/server.ts +156 -0
  230. package/src/component/convex.config.ts +5 -0
  231. package/src/component/index.ts +40 -0
  232. package/src/component/public.ts +607 -0
  233. package/src/component/schema.ts +35 -0
  234. package/src/providers/Anonymous.ts +79 -0
  235. package/src/providers/ConvexCredentials.ts +108 -0
  236. package/src/providers/Email.ts +60 -0
  237. package/src/providers/Password.ts +253 -0
  238. package/src/providers/Phone.ts +46 -0
  239. package/src/server/convex_types.ts +55 -0
  240. package/src/server/cookies.ts +42 -0
  241. package/src/server/implementation/db.ts +125 -0
  242. package/src/server/implementation/index.ts +815 -0
  243. package/src/server/implementation/mutations/createAccountFromCredentials.ts +113 -0
  244. package/src/server/implementation/mutations/createVerificationCode.ts +139 -0
  245. package/src/server/implementation/mutations/index.ts +157 -0
  246. package/src/server/implementation/mutations/invalidateSessions.ts +47 -0
  247. package/src/server/implementation/mutations/modifyAccount.ts +65 -0
  248. package/src/server/implementation/mutations/refreshSession.ts +188 -0
  249. package/src/server/implementation/mutations/retrieveAccountWithCredentials.ts +87 -0
  250. package/src/server/implementation/mutations/signIn.ts +51 -0
  251. package/src/server/implementation/mutations/signOut.ts +38 -0
  252. package/src/server/implementation/mutations/userOAuth.ts +112 -0
  253. package/src/server/implementation/mutations/verifier.ts +29 -0
  254. package/src/server/implementation/mutations/verifierSignature.ts +44 -0
  255. package/src/server/implementation/mutations/verifyCodeAndSignIn.ts +205 -0
  256. package/src/server/implementation/provider.ts +38 -0
  257. package/src/server/implementation/rateLimit.ts +105 -0
  258. package/src/server/implementation/redirects.ts +58 -0
  259. package/src/server/implementation/refreshTokens.ts +221 -0
  260. package/src/server/implementation/sessions.ts +155 -0
  261. package/src/server/implementation/signIn.ts +253 -0
  262. package/src/server/implementation/tokens.ts +29 -0
  263. package/src/server/implementation/types.ts +220 -0
  264. package/src/server/implementation/users.ts +286 -0
  265. package/src/server/implementation/utils.ts +91 -0
  266. package/src/server/index.ts +74 -0
  267. package/src/server/oauth/NOTICE.txt +21 -0
  268. package/src/server/oauth/README.md +7 -0
  269. package/src/server/oauth/authorizationUrl.ts +113 -0
  270. package/src/server/oauth/callback.ts +243 -0
  271. package/src/server/oauth/checks.ts +136 -0
  272. package/src/server/oauth/convexAuth.ts +168 -0
  273. package/src/server/oauth/lib/utils/customFetch.ts +18 -0
  274. package/src/server/oauth/lib/utils/providers.ts +12 -0
  275. package/src/server/oauth/providers/oauth.ts +56 -0
  276. package/src/server/oauth/types.ts +60 -0
  277. package/src/server/provider_utils.ts +222 -0
  278. package/src/server/types.ts +470 -0
  279. package/src/server/utils.ts +12 -0
  280. package/src/test.ts +24 -0
@@ -0,0 +1,286 @@
1
+ import { GenericId } from "convex/values";
2
+ import { Doc, MutationCtx } from "./types.js";
3
+ import { AuthProviderMaterializedConfig, ConvexAuthConfig } from "../types.js";
4
+ import { LOG_LEVELS, logWithLevel } from "./utils.js";
5
+ import { createAuthDb } from "./db.js";
6
+
7
+ type CreateOrUpdateUserArgs = {
8
+ type: "oauth" | "credentials" | "email" | "phone" | "verification";
9
+ provider: AuthProviderMaterializedConfig;
10
+ profile: Record<string, unknown> & {
11
+ email?: string;
12
+ phone?: string;
13
+ emailVerified?: boolean;
14
+ phoneVerified?: boolean;
15
+ };
16
+ shouldLinkViaEmail?: boolean;
17
+ shouldLinkViaPhone?: boolean;
18
+ };
19
+
20
+ export async function upsertUserAndAccount(
21
+ ctx: MutationCtx,
22
+ sessionId: GenericId<"session"> | null,
23
+ account:
24
+ | { existingAccount: Doc<"account"> }
25
+ | {
26
+ providerAccountId: string;
27
+ secret?: string;
28
+ },
29
+ args: CreateOrUpdateUserArgs,
30
+ config: ConvexAuthConfig,
31
+ ): Promise<{
32
+ userId: GenericId<"user">;
33
+ accountId: GenericId<"account">;
34
+ }> {
35
+ const userId = await defaultCreateOrUpdateUser(
36
+ ctx,
37
+ sessionId,
38
+ "existingAccount" in account ? account.existingAccount : null,
39
+ args,
40
+ config,
41
+ );
42
+ const accountId = await createOrUpdateAccount(ctx, userId, account, args, config);
43
+ return { userId, accountId };
44
+ }
45
+
46
+ async function defaultCreateOrUpdateUser(
47
+ ctx: MutationCtx,
48
+ existingSessionId: GenericId<"session"> | null,
49
+ existingAccount: Doc<"account"> | null,
50
+ args: CreateOrUpdateUserArgs,
51
+ config: ConvexAuthConfig,
52
+ ) {
53
+ logWithLevel(LOG_LEVELS.DEBUG, "defaultCreateOrUpdateUser args:", {
54
+ existingAccountId: existingAccount?._id,
55
+ existingSessionId,
56
+ args,
57
+ });
58
+ const existingUserId = existingAccount?.userId ?? null;
59
+ const authDb =
60
+ config.component !== undefined ? createAuthDb(ctx, config.component) : null;
61
+ if (config.callbacks?.createOrUpdateUser !== undefined) {
62
+ logWithLevel(LOG_LEVELS.DEBUG, "Using custom createOrUpdateUser callback");
63
+ return await config.callbacks.createOrUpdateUser(ctx, {
64
+ existingUserId,
65
+ ...args,
66
+ });
67
+ }
68
+
69
+ const {
70
+ provider,
71
+ profile: {
72
+ emailVerified: profileEmailVerified,
73
+ phoneVerified: profilePhoneVerified,
74
+ ...profile
75
+ },
76
+ } = args;
77
+ const emailVerified =
78
+ profileEmailVerified ??
79
+ ((provider.type === "oauth" || provider.type === "oidc") &&
80
+ provider.allowDangerousEmailAccountLinking !== false);
81
+ const phoneVerified = profilePhoneVerified ?? false;
82
+ const shouldLinkViaEmail =
83
+ args.shouldLinkViaEmail || emailVerified || provider.type === "email";
84
+ const shouldLinkViaPhone =
85
+ args.shouldLinkViaPhone || phoneVerified || provider.type === "phone";
86
+
87
+ let userId = existingUserId;
88
+ if (existingUserId === null) {
89
+ const existingUserWithVerifiedEmailId =
90
+ typeof profile.email === "string" && shouldLinkViaEmail
91
+ ? (await uniqueUserWithVerifiedEmail(ctx, profile.email, config))?._id ??
92
+ null
93
+ : null;
94
+
95
+ const existingUserWithVerifiedPhoneId =
96
+ typeof profile.phone === "string" && shouldLinkViaPhone
97
+ ? (await uniqueUserWithVerifiedPhone(ctx, profile.phone, config))?._id ??
98
+ null
99
+ : null;
100
+ // If there is both email and phone verified user
101
+ // already we can't link.
102
+ if (
103
+ existingUserWithVerifiedEmailId !== null &&
104
+ existingUserWithVerifiedPhoneId !== null
105
+ ) {
106
+ logWithLevel(
107
+ LOG_LEVELS.DEBUG,
108
+ `Found existing email and phone verified users, so not linking: email: ${existingUserWithVerifiedEmailId}, phone: ${existingUserWithVerifiedPhoneId}`,
109
+ );
110
+ userId = null;
111
+ } else if (existingUserWithVerifiedEmailId !== null) {
112
+ logWithLevel(
113
+ LOG_LEVELS.DEBUG,
114
+ `Found existing email verified user, linking: ${existingUserWithVerifiedEmailId}`,
115
+ );
116
+ userId = existingUserWithVerifiedEmailId;
117
+ } else if (existingUserWithVerifiedPhoneId !== null) {
118
+ logWithLevel(
119
+ LOG_LEVELS.DEBUG,
120
+ `Found existing phone verified user, linking: ${existingUserWithVerifiedPhoneId}`,
121
+ );
122
+ userId = existingUserWithVerifiedPhoneId;
123
+ } else {
124
+ logWithLevel(
125
+ LOG_LEVELS.DEBUG,
126
+ "No existing verified users found, creating new user",
127
+ );
128
+ userId = null;
129
+ }
130
+ }
131
+ const userData = {
132
+ ...(emailVerified ? { emailVerificationTime: Date.now() } : null),
133
+ ...(phoneVerified ? { phoneVerificationTime: Date.now() } : null),
134
+ ...profile,
135
+ };
136
+ const existingOrLinkedUserId = userId;
137
+ if (userId !== null) {
138
+ try {
139
+ if (authDb !== null) {
140
+ await authDb.users.patch(userId, userData);
141
+ } else {
142
+ await ctx.db.patch(userId, userData);
143
+ }
144
+ } catch (error) {
145
+ throw new Error(
146
+ `Could not update user document with ID \`${userId}\`, ` +
147
+ `either the user has been deleted but their account has not, ` +
148
+ `or the profile data doesn't match the \`users\` table schema: ` +
149
+ `${(error as Error).message}`,
150
+ );
151
+ }
152
+ } else {
153
+ userId =
154
+ authDb !== null
155
+ ? ((await authDb.users.insert(userData)) as GenericId<"user">)
156
+ : await ctx.db.insert("user", userData);
157
+ }
158
+ const afterUserCreatedOrUpdated = config.callbacks?.afterUserCreatedOrUpdated;
159
+ if (afterUserCreatedOrUpdated !== undefined) {
160
+ logWithLevel(
161
+ LOG_LEVELS.DEBUG,
162
+ "Calling custom afterUserCreatedOrUpdated callback",
163
+ );
164
+ await afterUserCreatedOrUpdated(ctx, {
165
+ userId,
166
+ existingUserId: existingOrLinkedUserId,
167
+ ...args,
168
+ });
169
+ } else {
170
+ logWithLevel(
171
+ LOG_LEVELS.DEBUG,
172
+ "No custom afterUserCreatedOrUpdated callback, skipping",
173
+ );
174
+ }
175
+ return userId;
176
+ }
177
+
178
+ async function uniqueUserWithVerifiedEmail(
179
+ ctx: MutationCtx,
180
+ email: string,
181
+ config: ConvexAuthConfig,
182
+ ) {
183
+ if (config.component !== undefined) {
184
+ const authDb = createAuthDb(ctx, config.component);
185
+ return (await authDb.users.findByVerifiedEmail(email)) as Doc<"user"> | null;
186
+ }
187
+ const users = await ctx.db
188
+ .query("user")
189
+ .withIndex("email", (q) => q.eq("email", email))
190
+ .filter((q) => q.neq(q.field("emailVerificationTime"), undefined))
191
+ .take(2);
192
+ return users.length === 1 ? users[0] : null;
193
+ }
194
+
195
+ async function uniqueUserWithVerifiedPhone(
196
+ ctx: MutationCtx,
197
+ phone: string,
198
+ config: ConvexAuthConfig,
199
+ ) {
200
+ if (config.component !== undefined) {
201
+ const authDb = createAuthDb(ctx, config.component);
202
+ return (await authDb.users.findByVerifiedPhone(phone)) as Doc<"user"> | null;
203
+ }
204
+ const users = await ctx.db
205
+ .query("user")
206
+ .withIndex("phone", (q) => q.eq("phone", phone))
207
+ .filter((q) => q.neq(q.field("phoneVerificationTime"), undefined))
208
+ .take(2);
209
+ return users.length === 1 ? users[0] : null;
210
+ }
211
+
212
+ async function createOrUpdateAccount(
213
+ ctx: MutationCtx,
214
+ userId: GenericId<"user">,
215
+ account:
216
+ | { existingAccount: Doc<"account"> }
217
+ | {
218
+ providerAccountId: string;
219
+ secret?: string;
220
+ },
221
+ args: CreateOrUpdateUserArgs,
222
+ config: ConvexAuthConfig,
223
+ ) {
224
+ const authDb =
225
+ config.component !== undefined ? createAuthDb(ctx, config.component) : null;
226
+ const accountId =
227
+ "existingAccount" in account
228
+ ? account.existingAccount._id
229
+ : authDb !== null
230
+ ? ((await authDb.accounts.create({
231
+ userId,
232
+ provider: args.provider.id,
233
+ providerAccountId: account.providerAccountId,
234
+ secret: account.secret,
235
+ })) as GenericId<"account">)
236
+ : await ctx.db.insert("account", {
237
+ userId,
238
+ provider: args.provider.id,
239
+ providerAccountId: account.providerAccountId,
240
+ secret: account.secret,
241
+ });
242
+ // This is never used with the default `createOrUpdateUser` implementation,
243
+ // but it is used for manual linking via custom `createOrUpdateUser`:
244
+ if (
245
+ "existingAccount" in account &&
246
+ account.existingAccount.userId !== userId
247
+ ) {
248
+ if (authDb !== null) {
249
+ await authDb.accounts.patch(accountId, { userId });
250
+ } else {
251
+ await ctx.db.patch(accountId, { userId });
252
+ }
253
+ }
254
+ if (args.profile.emailVerified) {
255
+ if (authDb !== null) {
256
+ await authDb.accounts.patch(accountId, { emailVerified: args.profile.email });
257
+ } else {
258
+ await ctx.db.patch(accountId, { emailVerified: args.profile.email });
259
+ }
260
+ }
261
+ if (args.profile.phoneVerified) {
262
+ if (authDb !== null) {
263
+ await authDb.accounts.patch(accountId, { phoneVerified: args.profile.phone });
264
+ } else {
265
+ await ctx.db.patch(accountId, { phoneVerified: args.profile.phone });
266
+ }
267
+ }
268
+ return accountId;
269
+ }
270
+
271
+ export async function getAccountOrThrow(
272
+ ctx: MutationCtx,
273
+ existingAccountId: GenericId<"account">,
274
+ config: ConvexAuthConfig,
275
+ ) {
276
+ const existingAccount =
277
+ config.component !== undefined
278
+ ? await createAuthDb(ctx, config.component).accounts.getById(existingAccountId)
279
+ : await ctx.db.get(existingAccountId);
280
+ if (existingAccount === null) {
281
+ throw new Error(
282
+ `Expected an account to exist for ID "${existingAccountId}"`,
283
+ );
284
+ }
285
+ return existingAccount;
286
+ }
@@ -0,0 +1,91 @@
1
+ import { sha256 as rawSha256 } from "@oslojs/crypto/sha2";
2
+ import { encodeHexLowerCase } from "@oslojs/encoding";
3
+ import {
4
+ RandomReader,
5
+ generateRandomString as osloGenerateRandomString,
6
+ } from "@oslojs/crypto/random";
7
+
8
+ export const TOKEN_SUB_CLAIM_DIVIDER = "|";
9
+ export const REFRESH_TOKEN_DIVIDER = "|";
10
+
11
+ export function stringToNumber(value: string | undefined) {
12
+ return value !== undefined ? Number(value) : undefined;
13
+ }
14
+
15
+ export async function sha256(input: string) {
16
+ return encodeHexLowerCase(rawSha256(new TextEncoder().encode(input)));
17
+ }
18
+
19
+ export function generateRandomString(length: number, alphabet: string) {
20
+ const random: RandomReader = {
21
+ read(bytes) {
22
+ crypto.getRandomValues(bytes);
23
+ },
24
+ };
25
+
26
+ return osloGenerateRandomString(random, alphabet, length);
27
+ }
28
+
29
+ export function logError(error: unknown) {
30
+ logWithLevel(
31
+ LOG_LEVELS.ERROR,
32
+ error instanceof Error
33
+ ? error.message + "\n" + error.stack?.replace("\\n", "\n")
34
+ : error,
35
+ );
36
+ }
37
+
38
+ export const LOG_LEVELS = {
39
+ ERROR: "ERROR",
40
+ WARN: "WARN",
41
+ INFO: "INFO",
42
+ DEBUG: "DEBUG",
43
+ } as const;
44
+ type LogLevel = keyof typeof LOG_LEVELS;
45
+
46
+ export function logWithLevel(level: LogLevel, ...args: unknown[]) {
47
+ const configuredLogLevel =
48
+ LOG_LEVELS[
49
+ (process.env.AUTH_LOG_LEVEL as LogLevel | undefined) ?? "INFO"
50
+ ] ?? "INFO";
51
+ switch (level) {
52
+ case "ERROR":
53
+ console.error(...args);
54
+ break;
55
+ case "WARN":
56
+ if (configuredLogLevel !== "ERROR") {
57
+ console.warn(...args);
58
+ }
59
+ break;
60
+ case "INFO":
61
+ if (configuredLogLevel === "INFO" || configuredLogLevel === "DEBUG") {
62
+ console.info(...args);
63
+ }
64
+ break;
65
+ case "DEBUG":
66
+ if (configuredLogLevel === "DEBUG") {
67
+ console.debug(...args);
68
+ }
69
+ break;
70
+ }
71
+ }
72
+
73
+ const UNREDACTED_LENGTH = 5;
74
+ export function maybeRedact(value: string) {
75
+ if (value === "") {
76
+ return "";
77
+ }
78
+ const shouldRedact = process.env.AUTH_LOG_SECRETS !== "true";
79
+ if (shouldRedact) {
80
+ if (value.length < UNREDACTED_LENGTH * 2) {
81
+ return "<redacted>";
82
+ }
83
+ return (
84
+ value.substring(0, UNREDACTED_LENGTH) +
85
+ "<redacted>" +
86
+ value.substring(value.length - UNREDACTED_LENGTH)
87
+ );
88
+ } else {
89
+ return value;
90
+ }
91
+ }
@@ -0,0 +1,74 @@
1
+ import { parse, serialize } from "cookie";
2
+ import { isLocalHost } from "./utils.js";
3
+
4
+ export type AuthCookieConfig = {
5
+ maxAge: number | null;
6
+ };
7
+
8
+ export type AuthCookies = {
9
+ token: string | null;
10
+ refreshToken: string | null;
11
+ verifier: string | null;
12
+ };
13
+
14
+ export function authCookieNames(host?: string) {
15
+ const prefix = isLocalHost(host) ? "" : "__Host-";
16
+ return {
17
+ token: `${prefix}__convexAuthJWT`,
18
+ refreshToken: `${prefix}__convexAuthRefreshToken`,
19
+ verifier: `${prefix}__convexAuthOAuthVerifier`,
20
+ };
21
+ }
22
+
23
+ export function parseAuthCookies(
24
+ cookieHeader: string | null | undefined,
25
+ host?: string,
26
+ ): AuthCookies {
27
+ const names = authCookieNames(host);
28
+ const parsed = parse(cookieHeader ?? "");
29
+ return {
30
+ token: parsed[names.token] ?? null,
31
+ refreshToken: parsed[names.refreshToken] ?? null,
32
+ verifier: parsed[names.verifier] ?? null,
33
+ };
34
+ }
35
+
36
+ export function serializeAuthCookies(
37
+ cookies: AuthCookies,
38
+ host?: string,
39
+ config: AuthCookieConfig = { maxAge: null },
40
+ ) {
41
+ const names = authCookieNames(host);
42
+ const secure = !isLocalHost(host);
43
+ const base = {
44
+ path: "/",
45
+ httpOnly: true,
46
+ sameSite: "lax" as const,
47
+ secure,
48
+ };
49
+ const maxAge = config.maxAge ?? undefined;
50
+ return [
51
+ serialize(names.token, cookies.token ?? "", {
52
+ ...base,
53
+ maxAge: cookies.token === null ? 0 : maxAge,
54
+ expires: cookies.token === null ? new Date(0) : undefined,
55
+ }),
56
+ serialize(names.refreshToken, cookies.refreshToken ?? "", {
57
+ ...base,
58
+ maxAge: cookies.refreshToken === null ? 0 : maxAge,
59
+ expires: cookies.refreshToken === null ? new Date(0) : undefined,
60
+ }),
61
+ serialize(names.verifier, cookies.verifier ?? "", {
62
+ ...base,
63
+ maxAge: cookies.verifier === null ? 0 : maxAge,
64
+ expires: cookies.verifier === null ? new Date(0) : undefined,
65
+ }),
66
+ ];
67
+ }
68
+
69
+ export function shouldProxyAuthAction(pathname: string, apiRoute: string) {
70
+ if (apiRoute.endsWith("/")) {
71
+ return pathname === apiRoute || pathname === apiRoute.slice(0, -1);
72
+ }
73
+ return pathname === apiRoute || pathname === `${apiRoute}/`;
74
+ }
@@ -0,0 +1,21 @@
1
+ Some code in this directory is derived from the Auth.js project
2
+ (https://github.com/nextauthjs/next-auth), and is used according to the terms
3
+ of that project's license.
4
+
5
+ ---------
6
+
7
+ ISC License
8
+
9
+ Copyright (c) 2022-2024, Balázs Orbán
10
+
11
+ Permission to use, copy, modify, and/or distribute this software for any
12
+ purpose with or without fee is hereby granted, provided that the above
13
+ copyright notice and this permission notice appear in all copies.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,7 @@
1
+ The code in this directory is meant to handle OAuth2 and OIDC flows.
2
+
3
+ The code is adapted from the @auth/core package, but since some of their types
4
+ and functions used are internal, they're copied over but with similar structure.
5
+
6
+ Everything that deviates from the original code is marked with a "ConvexAuth:"
7
+ comment.
@@ -0,0 +1,113 @@
1
+ // This file corresponds to packages/core/src/lib/actions/signin/authorization-url.ts in the @auth/core package (commit 5af1f30a32e64591abc50ae4d2dba4682e525431).
2
+ import * as checks from "./checks.js";
3
+ import { InternalOptions } from "./types.js";
4
+ import {
5
+ callbackUrl,
6
+ getAuthorizationSignature,
7
+ } from "./convexAuth.js";
8
+ import { Cookie } from "@auth/core/lib/utils/cookie.js";
9
+ import { logWithLevel } from "../implementation/utils.js";
10
+
11
+ /**
12
+ * Generates an authorization/request token URL.
13
+ *
14
+ * [OAuth 2](https://www.oauth.com/oauth2-servers/authorization/the-authorization-request/)
15
+ */
16
+ export async function getAuthorizationUrl(
17
+ // ConvexAuth: we don't accept a query argument
18
+ options: InternalOptions<"oauth" | "oidc">,
19
+ ) {
20
+ const { provider } = options;
21
+
22
+ let url = provider.authorization?.url;
23
+
24
+ // ConvexAuth: ConvexAuth does slightly different logic to determine the authorization endpoint
25
+ const { as, authorization: authorizationEndpoint, configSource } = provider;
26
+
27
+ if (!authorizationEndpoint) {
28
+ throw new TypeError("Could not determine the authorization endpoint.");
29
+ }
30
+ if (!url) {
31
+ url = new URL(authorizationEndpoint.url);
32
+ }
33
+
34
+ const authParams = url.searchParams;
35
+
36
+ // ConvexAuth: The logic for the callback URL is different from Auth.js
37
+ const redirect_uri = callbackUrl(provider.id);
38
+ // TODO(ConvexAuth): Support redirect proxy URLs.
39
+ // If we do so, update state.create to take the data value as an origin parameter (see the Auth.js code for ref).
40
+ // let data: string | undefined;
41
+ // if (!options.isOnRedirectProxy && provider.redirectProxyUrl) {
42
+ // redirect_uri = provider.redirectProxyUrl;
43
+ // data = provider.callbackUrl;
44
+ // logger.debug("using redirect proxy", { redirect_uri, data });
45
+ // }
46
+
47
+ const params = Object.assign(
48
+ {
49
+ response_type: "code",
50
+ // clientId can technically be undefined, should we check this in assert.ts or rely on the Authorization Server to do it?
51
+ client_id: provider.clientId,
52
+ redirect_uri,
53
+ // @ts-expect-error TODO:
54
+ ...provider.authorization?.params,
55
+ },
56
+ Object.fromEntries(url.searchParams.entries() ?? []),
57
+ // ConvexAuth: no query arguments are combined in the params
58
+ );
59
+
60
+ for (const k in params) authParams.set(k, params[k]);
61
+
62
+ const cookies: Cookie[] = [];
63
+
64
+ // ConvexAuth: no value passed for `origin` (Auth.js uses `data` from above)
65
+ const state = await checks.state.create(options);
66
+ if (state) {
67
+ authParams.set("state", state.value);
68
+ cookies.push(state.cookie);
69
+ }
70
+
71
+ // ConvexAuth: We need to save the value of the codeVerifier.
72
+ let codeVerifier: string | undefined;
73
+ if (provider.checks?.includes("pkce")) {
74
+ // ConvexAuth: we check where the config came from to help decide which branch to take here
75
+ if (configSource === "discovered" && !as.code_challenge_methods_supported?.includes("S256")) {
76
+ // We assume S256 PKCE support, if the server does not advertise that,
77
+ // a random `nonce` must be used for CSRF protection.
78
+ if (provider.type === "oidc") provider.checks = ["nonce"];
79
+ } else {
80
+ const pkce = await checks.pkce.create(options);
81
+ authParams.set("code_challenge", pkce.codeChallenge);
82
+ authParams.set("code_challenge_method", "S256");
83
+ cookies.push(pkce.cookie);
84
+ codeVerifier = pkce.codeVerifier;
85
+ }
86
+ }
87
+
88
+ const nonce = await checks.nonce.create(options);
89
+ if (nonce) {
90
+ authParams.set("nonce", nonce.value);
91
+ cookies.push(nonce.cookie);
92
+ }
93
+
94
+ // TODO: This does not work in normalizeOAuth because authorization endpoint can come from discovery
95
+ // Need to make normalizeOAuth async
96
+ if (provider.type === "oidc" && !url.searchParams.has("scope")) {
97
+ url.searchParams.set("scope", "openid profile email");
98
+ }
99
+
100
+ logWithLevel("DEBUG", "authorization url is ready", {
101
+ url,
102
+ cookies,
103
+ provider,
104
+ });
105
+
106
+ const convexAuthSignature = getAuthorizationSignature({
107
+ codeVerifier,
108
+ state: authParams.get("state") ?? undefined,
109
+ nonce: authParams.get("nonce") ?? undefined,
110
+ });
111
+
112
+ return { redirect: url.toString(), cookies, signature: convexAuthSignature };
113
+ }