@robelest/convex-auth 0.0.4-preview.13 → 0.0.4-preview.16

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 (328) hide show
  1. package/README.md +140 -9
  2. package/dist/bin.cjs +5957 -5478
  3. package/dist/client/index.d.ts +3 -7
  4. package/dist/client/index.d.ts.map +1 -1
  5. package/dist/client/index.js +27 -26
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/component/_generated/api.d.ts +14 -0
  8. package/dist/component/_generated/api.d.ts.map +1 -1
  9. package/dist/component/_generated/api.js.map +1 -1
  10. package/dist/component/_generated/component.d.ts +1672 -24
  11. package/dist/component/_generated/component.d.ts.map +1 -1
  12. package/dist/component/convex.config.d.ts +2 -2
  13. package/dist/component/convex.config.d.ts.map +1 -1
  14. package/dist/component/index.d.ts +1 -1
  15. package/dist/component/index.js +2 -2
  16. package/dist/component/model.d.ts +153 -0
  17. package/dist/component/model.d.ts.map +1 -0
  18. package/dist/component/model.js +343 -0
  19. package/dist/component/model.js.map +1 -0
  20. package/dist/component/providers/sso.d.ts +1 -1
  21. package/dist/component/public/enterprise.d.ts +54 -0
  22. package/dist/component/public/enterprise.d.ts.map +1 -0
  23. package/dist/component/public/enterprise.js +515 -0
  24. package/dist/component/public/enterprise.js.map +1 -0
  25. package/dist/component/public/factors.d.ts +52 -0
  26. package/dist/component/public/factors.d.ts.map +1 -0
  27. package/dist/component/public/factors.js +285 -0
  28. package/dist/component/public/factors.js.map +1 -0
  29. package/dist/component/public/groups.d.ts +116 -0
  30. package/dist/component/public/groups.d.ts.map +1 -0
  31. package/dist/component/public/groups.js +596 -0
  32. package/dist/component/public/groups.js.map +1 -0
  33. package/dist/component/public/identity.d.ts +93 -0
  34. package/dist/component/public/identity.d.ts.map +1 -0
  35. package/dist/component/public/identity.js +426 -0
  36. package/dist/component/public/identity.js.map +1 -0
  37. package/dist/component/public/keys.d.ts +41 -0
  38. package/dist/component/public/keys.d.ts.map +1 -0
  39. package/dist/component/public/keys.js +157 -0
  40. package/dist/component/public/keys.js.map +1 -0
  41. package/dist/component/public/shared.d.ts +26 -0
  42. package/dist/component/public/shared.d.ts.map +1 -0
  43. package/dist/component/public/shared.js +32 -0
  44. package/dist/component/public/shared.js.map +1 -0
  45. package/dist/component/public.d.ts +9 -321
  46. package/dist/component/public.d.ts.map +1 -1
  47. package/dist/component/public.js +6 -2145
  48. package/dist/component/schema.d.ts +406 -260
  49. package/dist/component/schema.js +37 -32
  50. package/dist/component/schema.js.map +1 -1
  51. package/dist/component/server/auth.d.ts +161 -15
  52. package/dist/component/server/auth.d.ts.map +1 -1
  53. package/dist/component/server/auth.js +100 -7
  54. package/dist/component/server/auth.js.map +1 -1
  55. package/dist/component/server/cookies.js +3 -0
  56. package/dist/component/server/cookies.js.map +1 -1
  57. package/dist/component/server/db.js +1 -0
  58. package/dist/component/server/db.js.map +1 -1
  59. package/dist/component/server/device.js +3 -1
  60. package/dist/component/server/device.js.map +1 -1
  61. package/dist/component/server/domains/core.js +629 -0
  62. package/dist/component/server/domains/core.js.map +1 -0
  63. package/dist/component/server/domains/sso.js +884 -0
  64. package/dist/component/server/domains/sso.js.map +1 -0
  65. package/dist/component/server/factory.d.ts +136 -0
  66. package/dist/component/server/factory.d.ts.map +1 -0
  67. package/dist/component/server/factory.js +1134 -0
  68. package/dist/component/server/factory.js.map +1 -0
  69. package/dist/component/server/fx.js +2 -1
  70. package/dist/component/server/fx.js.map +1 -1
  71. package/dist/component/server/http.js +287 -0
  72. package/dist/component/server/http.js.map +1 -0
  73. package/dist/component/server/identity.js +13 -0
  74. package/dist/component/server/identity.js.map +1 -0
  75. package/dist/component/server/keys.js +4 -0
  76. package/dist/component/server/keys.js.map +1 -1
  77. package/dist/component/server/mutations/account.js +1 -1
  78. package/dist/component/server/mutations/index.js +2 -2
  79. package/dist/component/server/mutations/index.js.map +1 -1
  80. package/dist/component/server/mutations/invalidate.js +1 -1
  81. package/dist/component/server/mutations/oauth.js +10 -7
  82. package/dist/component/server/mutations/oauth.js.map +1 -1
  83. package/dist/component/server/mutations/refresh.js +1 -1
  84. package/dist/component/server/mutations/register.js +1 -1
  85. package/dist/component/server/mutations/retrieve.js +1 -1
  86. package/dist/component/server/mutations/signature.js +1 -1
  87. package/dist/component/server/mutations/store.js +6 -3
  88. package/dist/component/server/mutations/store.js.map +1 -1
  89. package/dist/component/server/mutations/verify.js +1 -1
  90. package/dist/component/server/oauth.js +3 -0
  91. package/dist/component/server/oauth.js.map +1 -1
  92. package/dist/component/server/passkey.js +3 -2
  93. package/dist/component/server/passkey.js.map +1 -1
  94. package/dist/component/server/provider.js +2 -0
  95. package/dist/component/server/provider.js.map +1 -1
  96. package/dist/component/server/providers.js +10 -0
  97. package/dist/component/server/providers.js.map +1 -1
  98. package/dist/component/server/ratelimit.js +3 -0
  99. package/dist/component/server/ratelimit.js.map +1 -1
  100. package/dist/component/server/redirects.js +2 -0
  101. package/dist/component/server/redirects.js.map +1 -1
  102. package/dist/component/server/refresh.js +5 -0
  103. package/dist/component/server/refresh.js.map +1 -1
  104. package/dist/component/server/sessions.js +5 -0
  105. package/dist/component/server/sessions.js.map +1 -1
  106. package/dist/component/server/signin.js +2 -1
  107. package/dist/component/server/signin.js.map +1 -1
  108. package/dist/component/server/sso.js +166 -19
  109. package/dist/component/server/sso.js.map +1 -1
  110. package/dist/component/server/tokens.js +1 -0
  111. package/dist/component/server/tokens.js.map +1 -1
  112. package/dist/component/server/totp.js +4 -2
  113. package/dist/component/server/totp.js.map +1 -1
  114. package/dist/component/server/types.d.ts +106 -38
  115. package/dist/component/server/types.d.ts.map +1 -1
  116. package/dist/component/server/types.js.map +1 -1
  117. package/dist/component/server/users.js +1 -0
  118. package/dist/component/server/users.js.map +1 -1
  119. package/dist/component/server/utils.js +44 -2
  120. package/dist/component/server/utils.js.map +1 -1
  121. package/dist/providers/anonymous.d.ts +1 -1
  122. package/dist/providers/credentials.d.ts +1 -1
  123. package/dist/providers/password.d.ts +1 -1
  124. package/dist/providers/sso.d.ts +1 -1
  125. package/dist/providers/sso.js.map +1 -1
  126. package/dist/server/auth.d.ts +163 -17
  127. package/dist/server/auth.d.ts.map +1 -1
  128. package/dist/server/auth.js +100 -7
  129. package/dist/server/auth.js.map +1 -1
  130. package/dist/server/cookies.d.ts +1 -38
  131. package/dist/server/cookies.js +3 -0
  132. package/dist/server/cookies.js.map +1 -1
  133. package/dist/server/db.d.ts +1 -125
  134. package/dist/server/db.js +1 -0
  135. package/dist/server/db.js.map +1 -1
  136. package/dist/server/device.d.ts +1 -24
  137. package/dist/server/device.js +3 -1
  138. package/dist/server/device.js.map +1 -1
  139. package/dist/server/domains/core.d.ts +434 -0
  140. package/dist/server/domains/core.d.ts.map +1 -0
  141. package/dist/server/domains/core.js +629 -0
  142. package/dist/server/domains/core.js.map +1 -0
  143. package/dist/server/domains/sso.d.ts +409 -0
  144. package/dist/server/domains/sso.d.ts.map +1 -0
  145. package/dist/server/domains/sso.js +884 -0
  146. package/dist/server/domains/sso.js.map +1 -0
  147. package/dist/server/enterpriseValidators.d.ts +1 -0
  148. package/dist/server/enterpriseValidators.js +60 -0
  149. package/dist/server/enterpriseValidators.js.map +1 -0
  150. package/dist/server/factory.d.ts +136 -0
  151. package/dist/server/factory.d.ts.map +1 -0
  152. package/dist/server/factory.js +1134 -0
  153. package/dist/server/factory.js.map +1 -0
  154. package/dist/server/fx.d.ts +1 -16
  155. package/dist/server/fx.d.ts.map +1 -1
  156. package/dist/server/fx.js +1 -0
  157. package/dist/server/fx.js.map +1 -1
  158. package/dist/server/http.d.ts +59 -0
  159. package/dist/server/http.d.ts.map +1 -0
  160. package/dist/server/http.js +287 -0
  161. package/dist/server/http.js.map +1 -0
  162. package/dist/server/identity.d.ts +1 -0
  163. package/dist/server/identity.js +13 -0
  164. package/dist/server/identity.js.map +1 -0
  165. package/dist/server/index.d.ts +468 -1
  166. package/dist/server/index.d.ts.map +1 -1
  167. package/dist/server/index.js +530 -36
  168. package/dist/server/index.js.map +1 -1
  169. package/dist/server/keys.d.ts +1 -57
  170. package/dist/server/keys.js +4 -0
  171. package/dist/server/keys.js.map +1 -1
  172. package/dist/server/mutations/account.d.ts +7 -7
  173. package/dist/server/mutations/account.d.ts.map +1 -1
  174. package/dist/server/mutations/code.d.ts +13 -13
  175. package/dist/server/mutations/code.d.ts.map +1 -1
  176. package/dist/server/mutations/index.d.ts +107 -107
  177. package/dist/server/mutations/index.d.ts.map +1 -1
  178. package/dist/server/mutations/index.js +1 -1
  179. package/dist/server/mutations/index.js.map +1 -1
  180. package/dist/server/mutations/invalidate.d.ts +5 -5
  181. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  182. package/dist/server/mutations/oauth.d.ts +10 -10
  183. package/dist/server/mutations/oauth.d.ts.map +1 -1
  184. package/dist/server/mutations/oauth.js +9 -6
  185. package/dist/server/mutations/oauth.js.map +1 -1
  186. package/dist/server/mutations/refresh.d.ts +4 -4
  187. package/dist/server/mutations/register.d.ts +12 -12
  188. package/dist/server/mutations/register.d.ts.map +1 -1
  189. package/dist/server/mutations/retrieve.d.ts +7 -7
  190. package/dist/server/mutations/signature.d.ts +5 -5
  191. package/dist/server/mutations/signin.d.ts +6 -6
  192. package/dist/server/mutations/signin.d.ts.map +1 -1
  193. package/dist/server/mutations/signout.d.ts +1 -1
  194. package/dist/server/mutations/store.d.ts +3 -2
  195. package/dist/server/mutations/store.d.ts.map +1 -1
  196. package/dist/server/mutations/store.js +6 -3
  197. package/dist/server/mutations/store.js.map +1 -1
  198. package/dist/server/mutations/verifier.d.ts +1 -1
  199. package/dist/server/mutations/verify.d.ts +11 -11
  200. package/dist/server/mutations/verify.d.ts.map +1 -1
  201. package/dist/server/oauth.d.ts +1 -59
  202. package/dist/server/oauth.js +3 -0
  203. package/dist/server/oauth.js.map +1 -1
  204. package/dist/server/passkey.d.ts.map +1 -1
  205. package/dist/server/passkey.js +3 -2
  206. package/dist/server/passkey.js.map +1 -1
  207. package/dist/server/provider.d.ts +1 -14
  208. package/dist/server/provider.d.ts.map +1 -1
  209. package/dist/server/provider.js +2 -0
  210. package/dist/server/provider.js.map +1 -1
  211. package/dist/server/providers.js +10 -0
  212. package/dist/server/providers.js.map +1 -1
  213. package/dist/server/ratelimit.d.ts +1 -22
  214. package/dist/server/ratelimit.js +3 -0
  215. package/dist/server/ratelimit.js.map +1 -1
  216. package/dist/server/redirects.d.ts +1 -10
  217. package/dist/server/redirects.js +2 -0
  218. package/dist/server/redirects.js.map +1 -1
  219. package/dist/server/refresh.d.ts +1 -37
  220. package/dist/server/refresh.js +5 -0
  221. package/dist/server/refresh.js.map +1 -1
  222. package/dist/server/sessions.d.ts +1 -28
  223. package/dist/server/sessions.js +5 -0
  224. package/dist/server/sessions.js.map +1 -1
  225. package/dist/server/signin.d.ts +1 -55
  226. package/dist/server/signin.js +2 -1
  227. package/dist/server/signin.js.map +1 -1
  228. package/dist/server/sso.d.ts +1 -348
  229. package/dist/server/sso.js +165 -18
  230. package/dist/server/sso.js.map +1 -1
  231. package/dist/server/templates.d.ts +1 -21
  232. package/dist/server/templates.js +1 -0
  233. package/dist/server/templates.js.map +1 -1
  234. package/dist/server/tokens.d.ts +1 -11
  235. package/dist/server/tokens.js +1 -0
  236. package/dist/server/tokens.js.map +1 -1
  237. package/dist/server/totp.d.ts +1 -23
  238. package/dist/server/totp.js +4 -2
  239. package/dist/server/totp.js.map +1 -1
  240. package/dist/server/types.d.ts +114 -77
  241. package/dist/server/types.d.ts.map +1 -1
  242. package/dist/server/types.js.map +1 -1
  243. package/dist/server/users.d.ts +1 -31
  244. package/dist/server/users.js +1 -0
  245. package/dist/server/users.js.map +1 -1
  246. package/dist/server/utils.d.ts +1 -27
  247. package/dist/server/utils.js +44 -2
  248. package/dist/server/utils.js.map +1 -1
  249. package/dist/server/version.d.ts +1 -1
  250. package/dist/server/version.js +1 -1
  251. package/dist/server/version.js.map +1 -1
  252. package/package.json +4 -5
  253. package/src/cli/bin.ts +5 -0
  254. package/src/cli/index.ts +22 -9
  255. package/src/cli/keys.ts +3 -0
  256. package/src/client/index.ts +36 -37
  257. package/src/component/_generated/api.ts +14 -0
  258. package/src/component/_generated/component.ts +2106 -9
  259. package/src/component/index.ts +3 -1
  260. package/src/component/model.ts +441 -0
  261. package/src/component/public/enterprise.ts +753 -0
  262. package/src/component/public/factors.ts +332 -0
  263. package/src/component/public/groups.ts +932 -0
  264. package/src/component/public/identity.ts +566 -0
  265. package/src/component/public/keys.ts +209 -0
  266. package/src/component/public/shared.ts +119 -0
  267. package/src/component/public.ts +5 -2965
  268. package/src/component/schema.ts +68 -63
  269. package/src/providers/sso.ts +1 -1
  270. package/src/server/auth.ts +413 -18
  271. package/src/server/cookies.ts +3 -0
  272. package/src/server/db.ts +3 -0
  273. package/src/server/device.ts +3 -1
  274. package/src/server/domains/core.ts +1071 -0
  275. package/src/server/domains/sso.ts +1749 -0
  276. package/src/server/enterpriseValidators.ts +93 -0
  277. package/src/server/factory.ts +2181 -0
  278. package/src/server/fx.ts +1 -0
  279. package/src/server/http.ts +529 -0
  280. package/src/server/identity.ts +18 -0
  281. package/src/server/index.ts +806 -40
  282. package/src/server/keys.ts +4 -0
  283. package/src/server/mutations/index.ts +1 -1
  284. package/src/server/mutations/oauth.ts +36 -8
  285. package/src/server/mutations/store.ts +6 -3
  286. package/src/server/oauth.ts +6 -0
  287. package/src/server/passkey.ts +3 -2
  288. package/src/server/provider.ts +2 -0
  289. package/src/server/providers.ts +20 -0
  290. package/src/server/ratelimit.ts +3 -0
  291. package/src/server/redirects.ts +2 -0
  292. package/src/server/refresh.ts +5 -0
  293. package/src/server/sessions.ts +5 -0
  294. package/src/server/signin.ts +1 -0
  295. package/src/server/sso.ts +259 -17
  296. package/src/server/templates.ts +1 -0
  297. package/src/server/tokens.ts +1 -0
  298. package/src/server/totp.ts +4 -2
  299. package/src/server/types.ts +178 -83
  300. package/src/server/users.ts +1 -0
  301. package/src/server/utils.ts +71 -1
  302. package/src/server/version.ts +1 -1
  303. package/dist/component/public.js.map +0 -1
  304. package/dist/component/server/implementation.d.ts +0 -1264
  305. package/dist/component/server/implementation.d.ts.map +0 -1
  306. package/dist/component/server/implementation.js +0 -2365
  307. package/dist/component/server/implementation.js.map +0 -1
  308. package/dist/server/cookies.d.ts.map +0 -1
  309. package/dist/server/db.d.ts.map +0 -1
  310. package/dist/server/device.d.ts.map +0 -1
  311. package/dist/server/implementation.d.ts +0 -1264
  312. package/dist/server/implementation.d.ts.map +0 -1
  313. package/dist/server/implementation.js +0 -2365
  314. package/dist/server/implementation.js.map +0 -1
  315. package/dist/server/keys.d.ts.map +0 -1
  316. package/dist/server/oauth.d.ts.map +0 -1
  317. package/dist/server/ratelimit.d.ts.map +0 -1
  318. package/dist/server/redirects.d.ts.map +0 -1
  319. package/dist/server/refresh.d.ts.map +0 -1
  320. package/dist/server/sessions.d.ts.map +0 -1
  321. package/dist/server/signin.d.ts.map +0 -1
  322. package/dist/server/sso.d.ts.map +0 -1
  323. package/dist/server/templates.d.ts.map +0 -1
  324. package/dist/server/tokens.d.ts.map +0 -1
  325. package/dist/server/totp.d.ts.map +0 -1
  326. package/dist/server/users.d.ts.map +0 -1
  327. package/dist/server/utils.d.ts.map +0 -1
  328. package/src/server/implementation.ts +0 -5336
package/src/server/fx.ts CHANGED
@@ -100,6 +100,7 @@ function match<T>(
100
100
  );
101
101
  }
102
102
 
103
+ /** @internal */
103
104
  export const Fx = {
104
105
  ...BaseFx,
105
106
  match,
@@ -0,0 +1,529 @@
1
+ import {
2
+ GenericActionCtx,
3
+ GenericDataModel,
4
+ HttpRouter,
5
+ httpActionGeneric,
6
+ } from "convex/server";
7
+ import { ConvexError } from "convex/values";
8
+ import { parse as parseCookies } from "cookie";
9
+
10
+ import { isAuthError } from "./errors";
11
+ import { AuthError, Fx } from "./fx";
12
+ import type { CorsConfig, HttpKeyContext } from "./types";
13
+ import { logError } from "./utils";
14
+
15
+ export function createHttpAction(auth: {
16
+ key: { verify: (ctx: GenericActionCtx<any>, rawKey: string) => Promise<any> };
17
+ }) {
18
+ return (
19
+ handler: (
20
+ ctx: GenericActionCtx<GenericDataModel> & HttpKeyContext,
21
+ request: Request,
22
+ ) => Promise<Response | Record<string, unknown>>,
23
+ options?: {
24
+ scope?: { resource: string; action: string };
25
+ cors?: CorsConfig;
26
+ },
27
+ ) => {
28
+ const corsConfig = options?.cors ?? {};
29
+ const corsHeaders: Record<string, string> = {
30
+ "Access-Control-Allow-Origin": corsConfig.origin ?? "*",
31
+ "Access-Control-Allow-Methods":
32
+ corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
33
+ "Access-Control-Allow-Headers":
34
+ corsConfig.headers ?? "Content-Type,Authorization",
35
+ };
36
+
37
+ return httpActionGeneric(async (genericCtx, request) => {
38
+ return Fx.run(
39
+ Fx.from({
40
+ ok: async () => {
41
+ const authHeader = request.headers.get("Authorization");
42
+ if (!authHeader?.startsWith("Bearer ")) {
43
+ return new Response(
44
+ JSON.stringify({
45
+ error: "Missing or malformed Authorization: Bearer header.",
46
+ code: "MISSING_BEARER_TOKEN",
47
+ }),
48
+ {
49
+ status: 401,
50
+ headers: {
51
+ ...corsHeaders,
52
+ "Content-Type": "application/json",
53
+ },
54
+ },
55
+ );
56
+ }
57
+ const rawKey = authHeader.slice(7);
58
+
59
+ const keyResult = await Fx.run(
60
+ Fx.from({
61
+ ok: () => auth.key.verify(genericCtx, rawKey),
62
+ err: (error) => error,
63
+ }).pipe(
64
+ Fx.fold({
65
+ ok: (result) => ({ ok: true, value: result }) as const,
66
+ err: (error) => ({ ok: false, error }) as const,
67
+ }),
68
+ ),
69
+ );
70
+
71
+ if (!keyResult.ok) {
72
+ if (isAuthError(keyResult.error)) {
73
+ const { code, message } = keyResult.error.data as {
74
+ code: string;
75
+ message: string;
76
+ };
77
+ return new Response(JSON.stringify({ error: message, code }), {
78
+ status: 403,
79
+ headers: {
80
+ ...corsHeaders,
81
+ "Content-Type": "application/json",
82
+ },
83
+ });
84
+ }
85
+ throw keyResult.error;
86
+ }
87
+
88
+ if (
89
+ options?.scope &&
90
+ !keyResult.value.scopes.can(
91
+ options.scope.resource,
92
+ options.scope.action,
93
+ )
94
+ ) {
95
+ return new Response(
96
+ JSON.stringify({
97
+ error: "This API key does not have the required permissions.",
98
+ code: "SCOPE_CHECK_FAILED",
99
+ }),
100
+ {
101
+ status: 403,
102
+ headers: {
103
+ ...corsHeaders,
104
+ "Content-Type": "application/json",
105
+ },
106
+ },
107
+ );
108
+ }
109
+
110
+ const enrichedCtx = Object.assign(genericCtx, {
111
+ key: {
112
+ userId: keyResult.value.userId,
113
+ keyId: keyResult.value.keyId,
114
+ scopes: keyResult.value.scopes,
115
+ },
116
+ });
117
+ const result = await handler(enrichedCtx, request);
118
+
119
+ if (result instanceof Response) {
120
+ const headers = new Headers(result.headers);
121
+ for (const [k, val] of Object.entries(corsHeaders)) {
122
+ if (!headers.has(k)) headers.set(k, val);
123
+ }
124
+ return new Response(result.body, {
125
+ status: result.status,
126
+ statusText: result.statusText,
127
+ headers,
128
+ });
129
+ }
130
+
131
+ return new Response(JSON.stringify(result), {
132
+ status: 200,
133
+ headers: {
134
+ ...corsHeaders,
135
+ "Content-Type": "application/json",
136
+ },
137
+ });
138
+ },
139
+ err: (error) => error,
140
+ }).pipe(
141
+ Fx.recover((error) => {
142
+ logError(error);
143
+ return Fx.succeed(
144
+ new Response(
145
+ JSON.stringify({
146
+ error: "An unexpected error occurred.",
147
+ code: "INTERNAL_ERROR",
148
+ }),
149
+ {
150
+ status: 500,
151
+ headers: {
152
+ ...corsHeaders,
153
+ "Content-Type": "application/json",
154
+ },
155
+ },
156
+ ),
157
+ );
158
+ }),
159
+ ),
160
+ );
161
+ });
162
+ };
163
+ }
164
+
165
+ export function createHttpRoute(
166
+ wrapAction: ReturnType<typeof createHttpAction>,
167
+ ) {
168
+ return (
169
+ http: { route: (config: any) => void },
170
+ routeConfig: {
171
+ path: string;
172
+ method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
173
+ handler: (
174
+ ctx: GenericActionCtx<GenericDataModel> & HttpKeyContext,
175
+ request: Request,
176
+ ) => Promise<Response | Record<string, unknown>>;
177
+ scope?: { resource: string; action: string };
178
+ cors?: CorsConfig;
179
+ },
180
+ ) => {
181
+ const corsConfig = routeConfig.cors ?? {};
182
+ const corsHeaders: Record<string, string> = {
183
+ "Access-Control-Allow-Origin": corsConfig.origin ?? "*",
184
+ "Access-Control-Allow-Methods":
185
+ corsConfig.methods ?? "GET,POST,PUT,PATCH,DELETE,OPTIONS",
186
+ "Access-Control-Allow-Headers":
187
+ corsConfig.headers ?? "Content-Type,Authorization",
188
+ };
189
+
190
+ http.route({
191
+ path: routeConfig.path,
192
+ method: "OPTIONS",
193
+ handler: httpActionGeneric(async () => {
194
+ return new Response(null, { status: 204, headers: corsHeaders });
195
+ }),
196
+ });
197
+
198
+ http.route({
199
+ path: routeConfig.path,
200
+ method: routeConfig.method,
201
+ handler: wrapAction(routeConfig.handler, {
202
+ scope: routeConfig.scope,
203
+ cors: routeConfig.cors,
204
+ }),
205
+ });
206
+ };
207
+ }
208
+
209
+ export function convertErrorsToResponse(
210
+ errorStatusCode: number,
211
+ action: (ctx: GenericActionCtx<any>, request: Request) => Promise<Response>,
212
+ ) {
213
+ return async (ctx: GenericActionCtx<any>, request: Request) => {
214
+ return Fx.run(
215
+ Fx.from({
216
+ ok: () => action(ctx, request),
217
+ err: (error) => error,
218
+ }).pipe(
219
+ Fx.recover((error) => {
220
+ if (isAuthError(error)) {
221
+ return Fx.succeed(
222
+ new Response(
223
+ JSON.stringify({
224
+ code: error.data.code,
225
+ message: error.data.message,
226
+ }),
227
+ {
228
+ status: errorStatusCode,
229
+ headers: { "Content-Type": "application/json" },
230
+ },
231
+ ),
232
+ );
233
+ } else if (error instanceof ConvexError) {
234
+ return Fx.succeed(
235
+ new Response(null, {
236
+ status: errorStatusCode,
237
+ statusText:
238
+ typeof error.data === "string" ? error.data : "Error",
239
+ }),
240
+ );
241
+ } else {
242
+ logError(error);
243
+ return Fx.succeed(
244
+ new Response(null, {
245
+ status: 500,
246
+ statusText: "Internal Server Error",
247
+ }),
248
+ );
249
+ }
250
+ }),
251
+ ),
252
+ );
253
+ };
254
+ }
255
+
256
+ export function getCookies(
257
+ request: Request,
258
+ ): Record<string, string | undefined> {
259
+ return parseCookies(request.headers.get("Cookie") ?? "");
260
+ }
261
+
262
+ export type SSORuntimeRoute = {
263
+ pathname?: string;
264
+ enterpriseId: string;
265
+ protocol: "oidc" | "saml" | "scim";
266
+ rest: string[];
267
+ };
268
+
269
+ function parseEnterpriseRuntimeRoute(
270
+ pathname: string,
271
+ routeBase: string,
272
+ ): SSORuntimeRoute | null {
273
+ const runtimePrefix = `${routeBase}/`;
274
+ const runtimeParts = pathname.startsWith(runtimePrefix)
275
+ ? pathname.slice(runtimePrefix.length).split("/").filter(Boolean)
276
+ : [];
277
+ const [runtimeEnterpriseId, protocol, ...rest] = runtimeParts;
278
+ if (
279
+ runtimeEnterpriseId === undefined ||
280
+ (protocol !== "oidc" && protocol !== "saml" && protocol !== "scim") ||
281
+ rest.length === 0
282
+ ) {
283
+ return null;
284
+ }
285
+ return {
286
+ pathname,
287
+ enterpriseId: runtimeEnterpriseId,
288
+ protocol,
289
+ rest,
290
+ };
291
+ }
292
+
293
+ export function addOpenIdRoutes(
294
+ http: HttpRouter,
295
+ deps: {
296
+ getIssuer: () => string;
297
+ getJwks: () => string;
298
+ },
299
+ ) {
300
+ const cacheControl =
301
+ "public, max-age=15, stale-while-revalidate=15, stale-if-error=86400";
302
+
303
+ http.route({
304
+ path: "/.well-known/openid-configuration",
305
+ method: "GET",
306
+ handler: httpActionGeneric(async () => {
307
+ const issuer = deps.getIssuer();
308
+ return new Response(
309
+ JSON.stringify({
310
+ issuer,
311
+ jwks_uri: `${issuer}/.well-known/jwks.json`,
312
+ }),
313
+ {
314
+ status: 200,
315
+ headers: {
316
+ "Content-Type": "application/json",
317
+ "Cache-Control": cacheControl,
318
+ },
319
+ },
320
+ );
321
+ }),
322
+ });
323
+
324
+ http.route({
325
+ path: "/.well-known/jwks.json",
326
+ method: "GET",
327
+ handler: httpActionGeneric(async () => {
328
+ return new Response(deps.getJwks(), {
329
+ status: 200,
330
+ headers: {
331
+ "Content-Type": "application/json",
332
+ "Cache-Control": cacheControl,
333
+ },
334
+ });
335
+ }),
336
+ });
337
+ }
338
+
339
+ export function addAuthRoutes(
340
+ http: HttpRouter,
341
+ deps: {
342
+ handleSignIn: (
343
+ ctx: GenericActionCtx<any>,
344
+ request: Request,
345
+ ) => Promise<Response>;
346
+ handleCallback: (
347
+ ctx: GenericActionCtx<any>,
348
+ request: Request,
349
+ ) => Promise<Response>;
350
+ },
351
+ ) {
352
+ http.route({
353
+ pathPrefix: "/api/auth/signin/",
354
+ method: "GET",
355
+ handler: httpActionGeneric(deps.handleSignIn),
356
+ });
357
+
358
+ const callbackHandler = httpActionGeneric(deps.handleCallback);
359
+
360
+ http.route({
361
+ pathPrefix: "/api/auth/callback/",
362
+ method: "GET",
363
+ handler: callbackHandler,
364
+ });
365
+
366
+ http.route({
367
+ pathPrefix: "/api/auth/callback/",
368
+ method: "POST",
369
+ handler: callbackHandler,
370
+ });
371
+ }
372
+
373
+ export function addSSORoutes(
374
+ http: HttpRouter,
375
+ deps: {
376
+ routeBase: string;
377
+ convertErrorsToResponse: typeof convertErrorsToResponse;
378
+ handleSamlMetadata: (
379
+ ctx: GenericActionCtx<any>,
380
+ request: Request,
381
+ route: SSORuntimeRoute,
382
+ ) => Promise<Response>;
383
+ handleSamlSignIn: (
384
+ ctx: GenericActionCtx<any>,
385
+ request: Request,
386
+ route: SSORuntimeRoute,
387
+ ) => Promise<Response>;
388
+ handleOidcSignIn: (
389
+ ctx: GenericActionCtx<any>,
390
+ request: Request,
391
+ route: SSORuntimeRoute,
392
+ ) => Promise<Response>;
393
+ handleOidcCallback: (
394
+ ctx: GenericActionCtx<any>,
395
+ request: Request,
396
+ route: SSORuntimeRoute,
397
+ ) => Promise<Response>;
398
+ handleSamlAcs: (
399
+ ctx: GenericActionCtx<any>,
400
+ request: Request,
401
+ route: SSORuntimeRoute,
402
+ ) => Promise<Response>;
403
+ handleSamlSlo: (
404
+ ctx: GenericActionCtx<any>,
405
+ request: Request,
406
+ route: SSORuntimeRoute,
407
+ ) => Promise<Response>;
408
+ handleScimRequest: (
409
+ ctx: GenericActionCtx<any>,
410
+ request: Request,
411
+ ) => Promise<Response>;
412
+ scimError: (status: number, scimType: string, detail: string) => Response;
413
+ },
414
+ ) {
415
+ const routePrefix = `${deps.routeBase}/`;
416
+
417
+ http.route({
418
+ pathPrefix: routePrefix,
419
+ method: "GET",
420
+ handler: httpActionGeneric(
421
+ deps.convertErrorsToResponse(400, async (ctx, request) => {
422
+ const route = parseEnterpriseRuntimeRoute(
423
+ new URL(request.url).pathname,
424
+ deps.routeBase,
425
+ );
426
+ if (!route) {
427
+ throw new AuthError(
428
+ "INVALID_PARAMETERS",
429
+ "Invalid enterprise runtime path.",
430
+ ).toConvexError();
431
+ }
432
+ if (route.protocol === "saml" && route.rest.length === 1) {
433
+ if (route.rest[0] === "metadata") {
434
+ return await deps.handleSamlMetadata(ctx, request, route);
435
+ }
436
+ if (route.rest[0] === "signin") {
437
+ return await deps.handleSamlSignIn(ctx, request, route);
438
+ }
439
+ if (route.rest[0] === "acs") {
440
+ return await deps.handleSamlAcs(ctx, request, route);
441
+ }
442
+ if (route.rest[0] === "slo") {
443
+ return await deps.handleSamlSlo(ctx, request, route);
444
+ }
445
+ }
446
+ if (route.protocol === "oidc" && route.rest.length === 1) {
447
+ if (route.rest[0] === "signin") {
448
+ return await deps.handleOidcSignIn(ctx, request, route);
449
+ }
450
+ if (route.rest[0] === "callback") {
451
+ return await deps.handleOidcCallback(ctx, request, route);
452
+ }
453
+ }
454
+ if (route.protocol === "scim" && route.rest[0] === "v2") {
455
+ return await deps.handleScimRequest(ctx, request);
456
+ }
457
+ throw new AuthError(
458
+ "INVALID_PARAMETERS",
459
+ "Invalid enterprise runtime path.",
460
+ ).toConvexError();
461
+ }),
462
+ ),
463
+ });
464
+
465
+ http.route({
466
+ pathPrefix: routePrefix,
467
+ method: "POST",
468
+ handler: httpActionGeneric(
469
+ deps.convertErrorsToResponse(400, async (ctx, request) => {
470
+ const route = parseEnterpriseRuntimeRoute(
471
+ new URL(request.url).pathname,
472
+ deps.routeBase,
473
+ );
474
+ if (route?.protocol === "saml" && route.rest.length === 1) {
475
+ if (route.rest[0] === "acs") {
476
+ return await deps.handleSamlAcs(ctx, request, route);
477
+ }
478
+ if (route.rest[0] === "slo") {
479
+ return await deps.handleSamlSlo(ctx, request, route);
480
+ }
481
+ }
482
+ if (route?.protocol === "scim" && route.rest[0] === "v2") {
483
+ return await deps.handleScimRequest(ctx, request);
484
+ }
485
+ throw new AuthError(
486
+ "INVALID_PARAMETERS",
487
+ "Invalid enterprise runtime path.",
488
+ ).toConvexError();
489
+ }),
490
+ ),
491
+ });
492
+
493
+ http.route({
494
+ pathPrefix: routePrefix,
495
+ method: "PUT",
496
+ handler: httpActionGeneric(
497
+ deps.convertErrorsToResponse(400, async (ctx, request) => {
498
+ const route = parseEnterpriseRuntimeRoute(
499
+ new URL(request.url).pathname,
500
+ deps.routeBase,
501
+ );
502
+ if (route?.protocol === "scim" && route.rest[0] === "v2") {
503
+ return await deps.handleScimRequest(ctx, request);
504
+ }
505
+ throw new AuthError(
506
+ "INVALID_PARAMETERS",
507
+ "Invalid enterprise runtime path.",
508
+ ).toConvexError();
509
+ }),
510
+ ),
511
+ });
512
+
513
+ for (const method of ["PATCH", "DELETE"] as const) {
514
+ http.route({
515
+ pathPrefix: routePrefix,
516
+ method,
517
+ handler: httpActionGeneric(async (ctx, request) => {
518
+ const route = parseEnterpriseRuntimeRoute(
519
+ new URL(request.url).pathname,
520
+ deps.routeBase,
521
+ );
522
+ if (!route || route.protocol !== "scim" || route.rest[0] !== "v2") {
523
+ return deps.scimError(404, "notFound", "SCIM resource not found.");
524
+ }
525
+ return await deps.handleScimRequest(ctx, request);
526
+ }),
527
+ });
528
+ }
529
+ }
@@ -0,0 +1,18 @@
1
+ import { AuthError } from "./fx";
2
+
3
+ /** @internal */
4
+ export function userIdFromIdentitySubject(subject: string): string {
5
+ const [userId, ...rest] = subject.split("|");
6
+ if (
7
+ typeof userId !== "string" ||
8
+ userId.length === 0 ||
9
+ rest.length === 0 ||
10
+ rest.some((segment) => segment.length === 0)
11
+ ) {
12
+ throw new AuthError(
13
+ "INTERNAL_ERROR",
14
+ "Authenticated identity subject is malformed.",
15
+ );
16
+ }
17
+ return userId;
18
+ }