@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,105 @@
1
+ import { ConvexAuthConfig } from "../types.js";
2
+ import { Doc, MutationCtx } from "./types.js";
3
+ import { createAuthDb } from "./db.js";
4
+
5
+ const DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR = 10;
6
+
7
+ export async function isSignInRateLimited(
8
+ ctx: MutationCtx,
9
+ identifier: string,
10
+ config: ConvexAuthConfig,
11
+ ) {
12
+ const state = await getRateLimitState(ctx, identifier, config);
13
+ if (state === null) {
14
+ return false;
15
+ }
16
+ return state.attempsLeft < 1;
17
+ }
18
+
19
+ export async function recordFailedSignIn(
20
+ ctx: MutationCtx,
21
+ identifier: string,
22
+ config: ConvexAuthConfig,
23
+ ) {
24
+ const state = await getRateLimitState(ctx, identifier, config);
25
+ if (state !== null) {
26
+ if (config.component !== undefined) {
27
+ await createAuthDb(ctx, config.component).rateLimits.patch(state.limit._id, {
28
+ attemptsLeft: state.attempsLeft - 1,
29
+ lastAttemptTime: Date.now(),
30
+ });
31
+ } else {
32
+ await ctx.db.patch(state.limit._id, {
33
+ attemptsLeft: state.attempsLeft - 1,
34
+ lastAttemptTime: Date.now(),
35
+ });
36
+ }
37
+ } else {
38
+ const maxAttempsPerHour = configuredMaxAttempsPerHour(config);
39
+ if (config.component !== undefined) {
40
+ await createAuthDb(ctx, config.component).rateLimits.create({
41
+ identifier,
42
+ attemptsLeft: maxAttempsPerHour - 1,
43
+ lastAttemptTime: Date.now(),
44
+ });
45
+ } else {
46
+ await ctx.db.insert("limit", {
47
+ identifier,
48
+ attemptsLeft: maxAttempsPerHour - 1,
49
+ lastAttemptTime: Date.now(),
50
+ });
51
+ }
52
+ }
53
+ }
54
+
55
+ export async function resetSignInRateLimit(
56
+ ctx: MutationCtx,
57
+ identifier: string,
58
+ config: ConvexAuthConfig,
59
+ ) {
60
+ const existingState = await getRateLimitState(ctx, identifier, config);
61
+ if (existingState !== null) {
62
+ if (config.component !== undefined) {
63
+ await createAuthDb(ctx, config.component).rateLimits.delete(
64
+ existingState.limit._id,
65
+ );
66
+ } else {
67
+ await ctx.db.delete(existingState.limit._id);
68
+ }
69
+ }
70
+ }
71
+
72
+ async function getRateLimitState(
73
+ ctx: MutationCtx,
74
+ identifier: string,
75
+ config: ConvexAuthConfig,
76
+ ) {
77
+ const now = Date.now();
78
+ const maxAttempsPerHour = configuredMaxAttempsPerHour(config);
79
+ const limit =
80
+ config.component !== undefined
81
+ ? ((await createAuthDb(ctx, config.component).rateLimits.get(identifier)) as
82
+ | Doc<"limit">
83
+ | null)
84
+ : await ctx.db
85
+ .query("limit")
86
+ .withIndex("identifier", (q) => q.eq("identifier", identifier))
87
+ .unique();
88
+ if (limit === null) {
89
+ return null;
90
+ }
91
+ const elapsed = now - limit.lastAttemptTime;
92
+ const maxAttempsPerMs = maxAttempsPerHour / (60 * 60 * 1000);
93
+ const attempsLeft = Math.min(
94
+ maxAttempsPerHour,
95
+ limit.attemptsLeft + elapsed * maxAttempsPerMs,
96
+ );
97
+ return { limit, attempsLeft };
98
+ }
99
+
100
+ function configuredMaxAttempsPerHour(config: ConvexAuthConfig) {
101
+ return (
102
+ config.signIn?.maxFailedAttempsPerHour ??
103
+ DEFAULT_MAX_SIGN_IN_ATTEMPTS_PER_HOUR
104
+ );
105
+ }
@@ -0,0 +1,58 @@
1
+ import { ConvexAuthMaterializedConfig } from "../types.js";
2
+ import { requireEnv } from "../utils.js";
3
+
4
+ export async function redirectAbsoluteUrl(
5
+ config: ConvexAuthMaterializedConfig,
6
+ params: { redirectTo: unknown },
7
+ ) {
8
+ if (params.redirectTo !== undefined) {
9
+ if (typeof params.redirectTo !== "string") {
10
+ throw new Error(
11
+ `Expected \`redirectTo\` to be a string, got ${params.redirectTo as any}`,
12
+ );
13
+ }
14
+ const redirectCallback =
15
+ config.callbacks?.redirect ?? defaultRedirectCallback;
16
+ return await redirectCallback(params as { redirectTo: string });
17
+ }
18
+ return siteUrl();
19
+ }
20
+
21
+ async function defaultRedirectCallback({ redirectTo }: { redirectTo: string }) {
22
+ const baseUrl = siteUrl();
23
+ if (redirectTo.startsWith("?") || redirectTo.startsWith("/")) {
24
+ return `${baseUrl}${redirectTo}`;
25
+ }
26
+ if (redirectTo.startsWith(baseUrl)) {
27
+ const after = redirectTo[baseUrl.length];
28
+ if (after === undefined || after === "?" || after === "/") {
29
+ return redirectTo;
30
+ }
31
+ }
32
+ throw new Error(
33
+ `Invalid \`redirectTo\` ${redirectTo} for configured SITE_URL: ${baseUrl.toString()}`,
34
+ );
35
+ }
36
+
37
+ // Temporary work-around because Convex doesn't support
38
+ // schemes other than http and https.
39
+ export function setURLSearchParam(
40
+ absoluteUrl: string,
41
+ param: string,
42
+ value: string,
43
+ ) {
44
+ const pattern = /([^:]+):(.*)/;
45
+ const [, scheme, rest] = absoluteUrl.match(pattern)!;
46
+ const hasNoDomain = /^\/\/(?:\/|$|\?)/.test(rest);
47
+ const startsWithPath = hasNoDomain && rest.startsWith("///");
48
+ const url = new URL(
49
+ `http:${hasNoDomain ? "//googblibok" + rest.slice(2) : rest}`,
50
+ );
51
+ url.searchParams.set(param, value);
52
+ const [, , withParam] = url.toString().match(pattern)!;
53
+ return `${scheme}:${hasNoDomain ? (startsWithPath ? "/" : "") + "//" + withParam.slice(13) : withParam}`;
54
+ }
55
+
56
+ function siteUrl() {
57
+ return requireEnv("SITE_URL").replace(/\/$/, "");
58
+ }
@@ -0,0 +1,221 @@
1
+ import { GenericId } from "convex/values";
2
+ import { ConvexAuthConfig } from "../types.js";
3
+ import { Doc, MutationCtx } from "./types.js";
4
+ import {
5
+ LOG_LEVELS,
6
+ REFRESH_TOKEN_DIVIDER,
7
+ logWithLevel,
8
+ maybeRedact,
9
+ stringToNumber,
10
+ } from "./utils.js";
11
+ import { createAuthDb } from "./db.js";
12
+
13
+ const DEFAULT_SESSION_INACTIVE_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
14
+ export const REFRESH_TOKEN_REUSE_WINDOW_MS = 10 * 1000; // 10 seconds
15
+ export async function createRefreshToken(
16
+ ctx: MutationCtx,
17
+ config: ConvexAuthConfig,
18
+ sessionId: GenericId<"session">,
19
+ parentRefreshTokenId: GenericId<"token"> | null,
20
+ ) {
21
+ const expirationTime =
22
+ Date.now() +
23
+ (config.session?.inactiveDurationMs ??
24
+ stringToNumber(process.env.AUTH_SESSION_INACTIVE_DURATION_MS) ??
25
+ DEFAULT_SESSION_INACTIVE_DURATION_MS);
26
+ if (config.component !== undefined) {
27
+ return (await createAuthDb(ctx, config.component).refreshTokens.create({
28
+ sessionId,
29
+ expirationTime,
30
+ parentRefreshTokenId: parentRefreshTokenId ?? undefined,
31
+ })) as GenericId<"token">;
32
+ }
33
+ const newRefreshTokenId = await ctx.db.insert("token", {
34
+ sessionId,
35
+ expirationTime,
36
+ parentRefreshTokenId: parentRefreshTokenId ?? undefined,
37
+ });
38
+ return newRefreshTokenId;
39
+ }
40
+
41
+ export const formatRefreshToken = (
42
+ refreshTokenId: GenericId<"token">,
43
+ sessionId: GenericId<"session">,
44
+ ) => {
45
+ return `${refreshTokenId}${REFRESH_TOKEN_DIVIDER}${sessionId}`;
46
+ };
47
+
48
+ export const parseRefreshToken = (
49
+ refreshToken: string,
50
+ ): {
51
+ refreshTokenId: GenericId<"token">;
52
+ sessionId: GenericId<"session">;
53
+ } => {
54
+ const [refreshTokenId, sessionId] = refreshToken.split(REFRESH_TOKEN_DIVIDER);
55
+ if (!refreshTokenId || !sessionId) {
56
+ throw new Error(`Can't parse refresh token: ${maybeRedact(refreshToken)}`);
57
+ }
58
+ return {
59
+ refreshTokenId: refreshTokenId as GenericId<"token">,
60
+ sessionId: sessionId as GenericId<"session">,
61
+ };
62
+ };
63
+
64
+ /**
65
+ * Mark all refresh tokens descending from the given refresh token as invalid immediately.
66
+ * This is used when we detect an invalid use of a refresh token, and want to revoke
67
+ * the entire tree.
68
+ *
69
+ * @param ctx
70
+ * @param refreshToken
71
+ */
72
+ export async function invalidateRefreshTokensInSubtree(
73
+ ctx: MutationCtx,
74
+ refreshToken: Doc<"token">,
75
+ config: ConvexAuthConfig,
76
+ ) {
77
+ const authDb =
78
+ config.component !== undefined ? createAuthDb(ctx, config.component) : null;
79
+ const tokensToInvalidate = [refreshToken];
80
+ let frontier = [refreshToken._id];
81
+ while (frontier.length > 0) {
82
+ const nextFrontier = [];
83
+ for (const currentTokenId of frontier) {
84
+ const children =
85
+ authDb !== null
86
+ ? ((await authDb.refreshTokens.getChildren(
87
+ refreshToken.sessionId,
88
+ currentTokenId,
89
+ )) as Doc<"token">[])
90
+ : await ctx.db
91
+ .query("token")
92
+ .withIndex("sessionIdAndParentRefreshTokenId", (q) =>
93
+ q
94
+ .eq("sessionId", refreshToken.sessionId)
95
+ .eq("parentRefreshTokenId", currentTokenId),
96
+ )
97
+ .collect();
98
+ tokensToInvalidate.push(...children);
99
+ nextFrontier.push(...children.map((child) => child._id));
100
+ }
101
+ frontier = nextFrontier;
102
+ }
103
+ for (const token of tokensToInvalidate) {
104
+ // Mark these as used so they can't be used again (even within the reuse window)
105
+ if (
106
+ token.firstUsedTime === undefined ||
107
+ token.firstUsedTime > Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS
108
+ ) {
109
+ if (authDb !== null) {
110
+ await authDb.refreshTokens.patch(token._id, {
111
+ firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,
112
+ });
113
+ } else {
114
+ await ctx.db.patch(token._id, {
115
+ firstUsedTime: Date.now() - REFRESH_TOKEN_REUSE_WINDOW_MS,
116
+ });
117
+ }
118
+ }
119
+ }
120
+ return tokensToInvalidate;
121
+ }
122
+
123
+ export async function deleteAllRefreshTokens(
124
+ ctx: MutationCtx,
125
+ sessionId: GenericId<"session">,
126
+ config: ConvexAuthConfig,
127
+ ) {
128
+ if (config.component !== undefined) {
129
+ await createAuthDb(ctx, config.component).refreshTokens.deleteAll(sessionId);
130
+ return;
131
+ }
132
+ const existingRefreshTokens = await ctx.db
133
+ .query("token")
134
+ .withIndex("sessionIdAndParentRefreshTokenId", (q) =>
135
+ q.eq("sessionId", sessionId),
136
+ )
137
+ .collect();
138
+ for (const refreshTokenDoc of existingRefreshTokens) {
139
+ await ctx.db.delete(refreshTokenDoc._id);
140
+ }
141
+ }
142
+
143
+ export async function refreshTokenIfValid(
144
+ ctx: MutationCtx,
145
+ refreshTokenId: string,
146
+ tokenSessionId: string,
147
+ config: ConvexAuthConfig,
148
+ ) {
149
+ const authDb =
150
+ config.component !== undefined ? createAuthDb(ctx, config.component) : null;
151
+ let refreshTokenDoc: Doc<"token"> | null;
152
+ try {
153
+ refreshTokenDoc =
154
+ authDb !== null
155
+ ? ((await authDb.refreshTokens.getById(
156
+ refreshTokenId as GenericId<"token">,
157
+ )) as Doc<"token"> | null)
158
+ : await ctx.db.get(refreshTokenId as GenericId<"token">);
159
+ } catch {
160
+ logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token format");
161
+ return null;
162
+ }
163
+
164
+ if (refreshTokenDoc === null) {
165
+ logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token");
166
+ return null;
167
+ }
168
+ if (refreshTokenDoc.expirationTime < Date.now()) {
169
+ logWithLevel(LOG_LEVELS.ERROR, "Expired refresh token");
170
+ return null;
171
+ }
172
+ if (refreshTokenDoc.sessionId !== tokenSessionId) {
173
+ logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token session ID");
174
+ return null;
175
+ }
176
+ let session: Doc<"session"> | null;
177
+ try {
178
+ session =
179
+ authDb !== null
180
+ ? ((await authDb.sessions.getById(refreshTokenDoc.sessionId)) as
181
+ | Doc<"session">
182
+ | null)
183
+ : await ctx.db.get(refreshTokenDoc.sessionId);
184
+ } catch {
185
+ logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token session format");
186
+ return null;
187
+ }
188
+ if (session === null) {
189
+ logWithLevel(LOG_LEVELS.ERROR, "Invalid refresh token session");
190
+ return null;
191
+ }
192
+ if (session.expirationTime < Date.now()) {
193
+ logWithLevel(LOG_LEVELS.ERROR, "Expired refresh token session");
194
+ return null;
195
+ }
196
+ return { session, refreshTokenDoc };
197
+ }
198
+ /**
199
+ * The active refresh token is the most recently created refresh token that has
200
+ * never been used.
201
+ *
202
+ * @param ctx
203
+ * @param sessionId
204
+ */
205
+ export async function loadActiveRefreshToken(
206
+ ctx: MutationCtx,
207
+ sessionId: GenericId<"session">,
208
+ config: ConvexAuthConfig,
209
+ ) {
210
+ if (config.component !== undefined) {
211
+ return (await createAuthDb(ctx, config.component).refreshTokens.getActive(
212
+ sessionId,
213
+ )) as Doc<"token"> | null;
214
+ }
215
+ return ctx.db
216
+ .query("token")
217
+ .withIndex("sessionId", (q) => q.eq("sessionId", sessionId))
218
+ .filter((q) => q.eq(q.field("firstUsedTime"), undefined))
219
+ .order("desc")
220
+ .first();
221
+ }
@@ -0,0 +1,155 @@
1
+ import { GenericId } from "convex/values";
2
+ import { ConvexAuthConfig } from "../types.js";
3
+ import { Doc, MutationCtx, SessionInfo } from "./types.js";
4
+ import { Auth } from "convex/server";
5
+ import {
6
+ LOG_LEVELS,
7
+ TOKEN_SUB_CLAIM_DIVIDER,
8
+ logWithLevel,
9
+ maybeRedact,
10
+ stringToNumber,
11
+ } from "./utils.js";
12
+ import { generateToken } from "./tokens.js";
13
+ import {
14
+ createRefreshToken,
15
+ formatRefreshToken,
16
+ deleteAllRefreshTokens,
17
+ } from "./refreshTokens.js";
18
+ import { createAuthDb } from "./db.js";
19
+
20
+ const DEFAULT_SESSION_TOTAL_DURATION_MS = 1000 * 60 * 60 * 24 * 30; // 30 days
21
+
22
+ export async function maybeGenerateTokensForSession(
23
+ ctx: MutationCtx,
24
+ config: ConvexAuthConfig,
25
+ userId: GenericId<"user">,
26
+ sessionId: GenericId<"session">,
27
+ generateTokens: boolean,
28
+ ): Promise<SessionInfo> {
29
+ return {
30
+ userId,
31
+ sessionId,
32
+ tokens: generateTokens
33
+ ? await generateTokensForSession(ctx, config, {
34
+ userId,
35
+ sessionId,
36
+ issuedRefreshTokenId: null,
37
+ parentRefreshTokenId: null,
38
+ })
39
+ : null,
40
+ };
41
+ }
42
+
43
+ export async function createNewAndDeleteExistingSession(
44
+ ctx: MutationCtx,
45
+ config: ConvexAuthConfig,
46
+ userId: GenericId<"user">,
47
+ ) {
48
+ const authDb =
49
+ config.component !== undefined ? createAuthDb(ctx, config.component) : null;
50
+ const existingSessionId = await getAuthSessionId(ctx);
51
+ if (existingSessionId !== null) {
52
+ const existingSession =
53
+ authDb !== null
54
+ ? await authDb.sessions.getById(existingSessionId)
55
+ : await ctx.db.get(existingSessionId);
56
+ if (existingSession !== null) {
57
+ await deleteSession(ctx, existingSession, config);
58
+ }
59
+ }
60
+ return await createSession(ctx, userId, config);
61
+ }
62
+
63
+ export async function generateTokensForSession(
64
+ ctx: MutationCtx,
65
+ config: ConvexAuthConfig,
66
+ args: {
67
+ userId: GenericId<"user">;
68
+ sessionId: GenericId<"session">;
69
+ issuedRefreshTokenId: GenericId<"token"> | null;
70
+ parentRefreshTokenId: GenericId<"token"> | null;
71
+ },
72
+ ) {
73
+ const ids = { userId: args.userId, sessionId: args.sessionId };
74
+ const refreshTokenId =
75
+ args.issuedRefreshTokenId ??
76
+ (await createRefreshToken(
77
+ ctx,
78
+ config,
79
+ args.sessionId,
80
+ args.parentRefreshTokenId,
81
+ ));
82
+ const result = {
83
+ token: await generateToken(ids, config),
84
+ refreshToken: formatRefreshToken(refreshTokenId, args.sessionId),
85
+ };
86
+ logWithLevel(
87
+ LOG_LEVELS.DEBUG,
88
+ `Generated token ${maybeRedact(result.token)} and refresh token ${maybeRedact(refreshTokenId)} for session ${maybeRedact(args.sessionId)}`,
89
+ );
90
+ return result;
91
+ }
92
+
93
+ async function createSession(
94
+ ctx: MutationCtx,
95
+ userId: GenericId<"user">,
96
+ config: ConvexAuthConfig,
97
+ ) {
98
+ const expirationTime =
99
+ Date.now() +
100
+ (config.session?.totalDurationMs ??
101
+ stringToNumber(process.env.AUTH_SESSION_TOTAL_DURATION_MS) ??
102
+ DEFAULT_SESSION_TOTAL_DURATION_MS);
103
+ if (config.component !== undefined) {
104
+ return (await createAuthDb(ctx, config.component).sessions.create(
105
+ userId,
106
+ expirationTime,
107
+ )) as GenericId<"session">;
108
+ }
109
+ return await ctx.db.insert("session", { expirationTime, userId });
110
+ }
111
+
112
+ export async function deleteSession(
113
+ ctx: MutationCtx,
114
+ session: Doc<"session">,
115
+ config: ConvexAuthConfig,
116
+ ) {
117
+ if (config.component !== undefined) {
118
+ await createAuthDb(ctx, config.component).sessions.delete(session._id);
119
+ } else {
120
+ await ctx.db.delete(session._id);
121
+ }
122
+ await deleteAllRefreshTokens(ctx, session._id, config);
123
+ }
124
+
125
+ /**
126
+ * Return the current session ID.
127
+ *
128
+ * ```ts filename="convex/myFunctions.tsx"
129
+ * import { mutation } from "./_generated/server";
130
+ * import { getAuthSessionId } from "@robelest/convex-auth/component";
131
+ *
132
+ * export const doSomething = mutation({
133
+ * args: {/* ... *\/},
134
+ * handler: async (ctx, args) => {
135
+ * const sessionId = await getAuthSessionId(ctx);
136
+ * if (sessionId === null) {
137
+ * throw new Error("Client is not authenticated!")
138
+ * }
139
+ * const session = await ctx.db.get(sessionId);
140
+ * // ...
141
+ * },
142
+ * });
143
+ * ```
144
+ *
145
+ * @param ctx query, mutation or action `ctx`
146
+ * @returns the session ID or `null` if the client isn't authenticated
147
+ */
148
+ export async function getAuthSessionId(ctx: { auth: Auth }) {
149
+ const identity = await ctx.auth.getUserIdentity();
150
+ if (identity === null) {
151
+ return null;
152
+ }
153
+ const [, sessionId] = identity.subject.split(TOKEN_SUB_CLAIM_DIVIDER);
154
+ return sessionId as GenericId<"session">;
155
+ }