@robelest/convex-auth 0.0.4-preview.18 → 0.0.4-preview.19

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 (541) hide show
  1. package/README.md +6 -3
  2. package/dist/authorization/index.d.ts +46 -0
  3. package/dist/authorization/index.d.ts.map +1 -1
  4. package/dist/authorization/index.js +46 -0
  5. package/dist/authorization/index.js.map +1 -1
  6. package/dist/bin.cjs +21 -13
  7. package/dist/client/core/types.d.ts +20 -0
  8. package/dist/client/core/types.d.ts.map +1 -0
  9. package/dist/client/index.d.ts +2 -16
  10. package/dist/client/index.d.ts.map +1 -1
  11. package/dist/client/index.js +67 -565
  12. package/dist/client/index.js.map +1 -1
  13. package/dist/component/_generated/api.d.ts +40 -12
  14. package/dist/component/_generated/api.d.ts.map +1 -1
  15. package/dist/component/_generated/api.js.map +1 -1
  16. package/dist/component/_generated/component.d.ts +1306 -1344
  17. package/dist/component/_generated/component.d.ts.map +1 -1
  18. package/dist/component/client/core/types.d.ts +2 -0
  19. package/dist/component/client/index.d.ts +2 -2
  20. package/dist/component/model.d.ts +80 -80
  21. package/dist/component/model.d.ts.map +1 -1
  22. package/dist/component/model.js +5 -1
  23. package/dist/component/model.js.map +1 -1
  24. package/dist/component/providers/credentials.d.ts +3 -2
  25. package/dist/component/providers/credentials.d.ts.map +1 -1
  26. package/dist/component/providers/email.d.ts +30 -0
  27. package/dist/component/providers/email.d.ts.map +1 -1
  28. package/dist/component/providers/phone.d.ts +28 -0
  29. package/dist/component/providers/phone.d.ts.map +1 -1
  30. package/dist/component/public/enterprise/audit.d.ts +73 -0
  31. package/dist/component/public/enterprise/audit.d.ts.map +1 -0
  32. package/dist/component/public/enterprise/audit.js +108 -0
  33. package/dist/component/public/enterprise/audit.js.map +1 -0
  34. package/dist/component/public/enterprise/core.d.ts +176 -0
  35. package/dist/component/public/enterprise/core.d.ts.map +1 -0
  36. package/dist/component/public/enterprise/core.js +292 -0
  37. package/dist/component/public/enterprise/core.js.map +1 -0
  38. package/dist/component/public/enterprise/domains.d.ts +174 -0
  39. package/dist/component/public/enterprise/domains.d.ts.map +1 -0
  40. package/dist/component/public/enterprise/domains.js +271 -0
  41. package/dist/component/public/enterprise/domains.js.map +1 -0
  42. package/dist/component/public/enterprise/scim.d.ts +245 -0
  43. package/dist/component/public/enterprise/scim.d.ts.map +1 -0
  44. package/dist/component/public/enterprise/scim.js +344 -0
  45. package/dist/component/public/enterprise/scim.js.map +1 -0
  46. package/dist/component/public/enterprise/secrets.d.ts +78 -0
  47. package/dist/component/public/enterprise/secrets.d.ts.map +1 -0
  48. package/dist/component/public/enterprise/secrets.js +118 -0
  49. package/dist/component/public/enterprise/secrets.js.map +1 -0
  50. package/dist/component/public/enterprise/webhooks.d.ts +211 -0
  51. package/dist/component/public/enterprise/webhooks.d.ts.map +1 -0
  52. package/dist/component/public/enterprise/webhooks.js +300 -0
  53. package/dist/component/public/enterprise/webhooks.js.map +1 -0
  54. package/dist/component/public/factors/devices.d.ts +157 -0
  55. package/dist/component/public/factors/devices.d.ts.map +1 -0
  56. package/dist/component/public/factors/devices.js +216 -0
  57. package/dist/component/public/factors/devices.js.map +1 -0
  58. package/dist/component/public/factors/passkeys.d.ts +175 -0
  59. package/dist/component/public/factors/passkeys.d.ts.map +1 -0
  60. package/dist/component/public/factors/passkeys.js +238 -0
  61. package/dist/component/public/factors/passkeys.js.map +1 -0
  62. package/dist/component/public/factors/totp.d.ts +189 -0
  63. package/dist/component/public/factors/totp.d.ts.map +1 -0
  64. package/dist/component/public/factors/totp.js +254 -0
  65. package/dist/component/public/factors/totp.js.map +1 -0
  66. package/dist/component/public/groups/core.d.ts +137 -0
  67. package/dist/component/public/groups/core.d.ts.map +1 -0
  68. package/dist/component/public/groups/core.js +321 -0
  69. package/dist/component/public/groups/core.js.map +1 -0
  70. package/dist/component/public/groups/invites.d.ts +217 -0
  71. package/dist/component/public/groups/invites.d.ts.map +1 -0
  72. package/dist/component/public/groups/invites.js +457 -0
  73. package/dist/component/public/groups/invites.js.map +1 -0
  74. package/dist/component/public/groups/members.d.ts +204 -0
  75. package/dist/component/public/groups/members.d.ts.map +1 -0
  76. package/dist/component/public/groups/members.js +355 -0
  77. package/dist/component/public/groups/members.js.map +1 -0
  78. package/dist/component/public/identity/accounts.d.ts +147 -0
  79. package/dist/component/public/identity/accounts.d.ts.map +1 -0
  80. package/dist/component/public/identity/accounts.js +200 -0
  81. package/dist/component/public/identity/accounts.js.map +1 -0
  82. package/dist/component/public/identity/codes.d.ts +104 -0
  83. package/dist/component/public/identity/codes.d.ts.map +1 -0
  84. package/dist/component/public/identity/codes.js +140 -0
  85. package/dist/component/public/identity/codes.js.map +1 -0
  86. package/dist/component/public/identity/sessions.d.ts +128 -0
  87. package/dist/component/public/identity/sessions.d.ts.map +1 -0
  88. package/dist/component/public/identity/sessions.js +192 -0
  89. package/dist/component/public/identity/sessions.js.map +1 -0
  90. package/dist/component/public/identity/tokens.d.ts +169 -0
  91. package/dist/component/public/identity/tokens.d.ts.map +1 -0
  92. package/dist/component/public/identity/tokens.js +227 -0
  93. package/dist/component/public/identity/tokens.js.map +1 -0
  94. package/dist/component/public/identity/users.d.ts +212 -0
  95. package/dist/component/public/identity/users.d.ts.map +1 -0
  96. package/dist/component/public/identity/users.js +311 -0
  97. package/dist/component/public/identity/users.js.map +1 -0
  98. package/dist/component/public/identity/verifiers.d.ts +116 -0
  99. package/dist/component/public/identity/verifiers.d.ts.map +1 -0
  100. package/dist/component/public/identity/verifiers.js +154 -0
  101. package/dist/component/public/identity/verifiers.js.map +1 -0
  102. package/dist/component/public/security/keys.d.ts +209 -0
  103. package/dist/component/public/security/keys.d.ts.map +1 -0
  104. package/dist/component/public/security/keys.js +319 -0
  105. package/dist/component/public/security/keys.js.map +1 -0
  106. package/dist/component/public/security/limits.d.ts +114 -0
  107. package/dist/component/public/security/limits.d.ts.map +1 -0
  108. package/dist/component/public/security/limits.js +169 -0
  109. package/dist/component/public/security/limits.js.map +1 -0
  110. package/dist/component/public.d.ts +22 -7
  111. package/dist/component/public.js +21 -6
  112. package/dist/component/schema.d.ts +288 -288
  113. package/dist/component/server/auth.d.ts +212 -25
  114. package/dist/component/server/auth.d.ts.map +1 -1
  115. package/dist/component/server/auth.js +68 -4
  116. package/dist/component/server/auth.js.map +1 -1
  117. package/dist/component/server/authError.js +34 -0
  118. package/dist/component/server/authError.js.map +1 -0
  119. package/dist/component/server/{providers.js → config.js} +2 -2
  120. package/dist/component/server/config.js.map +1 -0
  121. package/dist/component/server/{domains/core.js → core.js} +107 -88
  122. package/dist/component/server/core.js.map +1 -0
  123. package/dist/component/server/{provider.js → crypto.js} +3 -3
  124. package/dist/component/server/crypto.js.map +1 -0
  125. package/dist/component/server/device.js +1 -1
  126. package/dist/component/server/device.js.map +1 -1
  127. package/dist/component/server/enterprise/config.js +46 -0
  128. package/dist/component/server/enterprise/config.js.map +1 -0
  129. package/dist/component/server/{domains/sso.js → enterprise/domain.js} +6 -5
  130. package/dist/component/server/enterprise/domain.js.map +1 -0
  131. package/dist/component/server/enterprise/http.js +766 -0
  132. package/dist/component/server/enterprise/http.js.map +1 -0
  133. package/dist/component/server/enterprise/oidc.js +248 -0
  134. package/dist/component/server/enterprise/oidc.js.map +1 -0
  135. package/dist/component/server/enterprise/policy.js +85 -0
  136. package/dist/component/server/enterprise/policy.js.map +1 -0
  137. package/dist/component/server/enterprise/saml.js +338 -0
  138. package/dist/component/server/enterprise/saml.js.map +1 -0
  139. package/dist/component/server/enterprise/scim.js +97 -0
  140. package/dist/component/server/enterprise/scim.js.map +1 -0
  141. package/dist/component/server/enterprise/shared.js +51 -0
  142. package/dist/component/server/enterprise/shared.js.map +1 -0
  143. package/dist/component/server/errors.js.map +1 -1
  144. package/dist/component/server/http.js +2 -1
  145. package/dist/component/server/http.js.map +1 -1
  146. package/dist/component/server/identity.js +1 -1
  147. package/dist/component/server/identity.js.map +1 -1
  148. package/dist/component/server/{ratelimit.js → limits.js} +3 -3
  149. package/dist/component/server/limits.js.map +1 -0
  150. package/dist/component/server/mutations/account.js +3 -3
  151. package/dist/component/server/mutations/account.js.map +1 -1
  152. package/dist/component/server/mutations/code.js +2 -2
  153. package/dist/component/server/mutations/code.js.map +1 -1
  154. package/dist/component/server/mutations/invalidate.js +1 -1
  155. package/dist/component/server/mutations/invalidate.js.map +1 -1
  156. package/dist/component/server/mutations/oauth.js +5 -3
  157. package/dist/component/server/mutations/oauth.js.map +1 -1
  158. package/dist/component/server/mutations/refresh.js +1 -1
  159. package/dist/component/server/mutations/refresh.js.map +1 -1
  160. package/dist/component/server/mutations/register.js +3 -3
  161. package/dist/component/server/mutations/register.js.map +1 -1
  162. package/dist/component/server/mutations/retrieve.js +4 -4
  163. package/dist/component/server/mutations/retrieve.js.map +1 -1
  164. package/dist/component/server/mutations/signature.js +2 -2
  165. package/dist/component/server/mutations/signature.js.map +1 -1
  166. package/dist/component/server/mutations/signin.js +1 -1
  167. package/dist/component/server/mutations/signin.js.map +1 -1
  168. package/dist/component/server/mutations/signout.js +1 -1
  169. package/dist/component/server/mutations/signout.js.map +1 -1
  170. package/dist/component/server/mutations/store/refs.js +15 -0
  171. package/dist/component/server/mutations/store/refs.js.map +1 -0
  172. package/dist/component/server/mutations/store.js +80 -10
  173. package/dist/component/server/mutations/store.js.map +1 -1
  174. package/dist/component/server/mutations/verifier.js +1 -1
  175. package/dist/component/server/mutations/verifier.js.map +1 -1
  176. package/dist/component/server/mutations/verify.js +4 -3
  177. package/dist/component/server/mutations/verify.js.map +1 -1
  178. package/dist/component/server/oauth.js +1 -1
  179. package/dist/component/server/oauth.js.map +1 -1
  180. package/dist/component/server/passkey.js +2 -1
  181. package/dist/component/server/passkey.js.map +1 -1
  182. package/dist/component/server/redirects.js +1 -1
  183. package/dist/component/server/redirects.js.map +1 -1
  184. package/dist/component/server/refresh.js +1 -1
  185. package/dist/component/server/refresh.js.map +1 -1
  186. package/dist/component/server/{factory.d.ts → runtime.d.ts} +2 -2
  187. package/dist/component/server/runtime.d.ts.map +1 -0
  188. package/dist/component/server/runtime.js +413 -0
  189. package/dist/component/server/runtime.js.map +1 -0
  190. package/dist/component/server/signin.js +2 -1
  191. package/dist/component/server/signin.js.map +1 -1
  192. package/dist/component/server/totp.js +2 -1
  193. package/dist/component/server/totp.js.map +1 -1
  194. package/dist/component/server/types.d.ts +225 -41
  195. package/dist/component/server/types.d.ts.map +1 -1
  196. package/dist/component/server/types.js.map +1 -1
  197. package/dist/component/server/users.js +1 -1
  198. package/dist/component/server/users.js.map +1 -1
  199. package/dist/component/server/utils.js +1 -1
  200. package/dist/component/server/utils.js.map +1 -1
  201. package/dist/core/types.d.ts +369 -0
  202. package/dist/core/types.d.ts.map +1 -0
  203. package/dist/factors/device.js +105 -0
  204. package/dist/factors/device.js.map +1 -0
  205. package/dist/factors/passkey.js +181 -0
  206. package/dist/factors/passkey.js.map +1 -0
  207. package/dist/factors/totp.js +122 -0
  208. package/dist/factors/totp.js.map +1 -0
  209. package/dist/providers/credentials.d.ts +5 -2
  210. package/dist/providers/credentials.d.ts.map +1 -1
  211. package/dist/providers/credentials.js +2 -0
  212. package/dist/providers/credentials.js.map +1 -1
  213. package/dist/providers/email.d.ts +30 -0
  214. package/dist/providers/email.d.ts.map +1 -1
  215. package/dist/providers/email.js +24 -0
  216. package/dist/providers/email.js.map +1 -1
  217. package/dist/providers/phone.d.ts +28 -0
  218. package/dist/providers/phone.d.ts.map +1 -1
  219. package/dist/providers/phone.js +22 -0
  220. package/dist/providers/phone.js.map +1 -1
  221. package/dist/runtime/browser.js +68 -0
  222. package/dist/runtime/browser.js.map +1 -0
  223. package/dist/runtime/invite.js +51 -0
  224. package/dist/runtime/invite.js.map +1 -0
  225. package/dist/runtime/proxy.js +70 -0
  226. package/dist/runtime/proxy.js.map +1 -0
  227. package/dist/runtime/storage.js +37 -0
  228. package/dist/runtime/storage.js.map +1 -0
  229. package/dist/server/auth.d.ts +218 -28
  230. package/dist/server/auth.d.ts.map +1 -1
  231. package/dist/server/auth.js +68 -4
  232. package/dist/server/auth.js.map +1 -1
  233. package/dist/server/{fx.d.ts → authError.d.ts} +25 -15
  234. package/dist/server/authError.d.ts.map +1 -0
  235. package/dist/server/authError.js +34 -0
  236. package/dist/server/authError.js.map +1 -0
  237. package/dist/server/{providers.js → config.js} +2 -2
  238. package/dist/server/config.js.map +1 -0
  239. package/dist/server/core.d.ts +1436 -0
  240. package/dist/server/core.d.ts.map +1 -0
  241. package/dist/server/{domains/core.js → core.js} +107 -88
  242. package/dist/server/core.js.map +1 -0
  243. package/dist/server/{provider.d.ts → crypto.d.ts} +2 -2
  244. package/dist/server/crypto.d.ts.map +1 -0
  245. package/dist/server/{provider.js → crypto.js} +3 -3
  246. package/dist/{component/server/provider.js.map → server/crypto.js.map} +1 -1
  247. package/dist/server/device.js +1 -1
  248. package/dist/server/device.js.map +1 -1
  249. package/dist/server/enterprise/config.js +46 -0
  250. package/dist/server/enterprise/config.js.map +1 -0
  251. package/dist/server/{domains/sso.d.ts → enterprise/domain.d.ts} +4 -4
  252. package/dist/server/enterprise/domain.d.ts.map +1 -0
  253. package/dist/server/{domains/sso.js → enterprise/domain.js} +6 -5
  254. package/dist/server/enterprise/domain.js.map +1 -0
  255. package/dist/server/enterprise/http.d.ts +26 -0
  256. package/dist/server/enterprise/http.d.ts.map +1 -0
  257. package/dist/server/enterprise/http.js +766 -0
  258. package/dist/server/enterprise/http.js.map +1 -0
  259. package/dist/server/enterprise/oidc.js +248 -0
  260. package/dist/server/enterprise/oidc.js.map +1 -0
  261. package/dist/server/enterprise/policy.js +85 -0
  262. package/dist/server/enterprise/policy.js.map +1 -0
  263. package/dist/server/enterprise/saml.d.ts +1 -0
  264. package/dist/server/enterprise/saml.js +338 -0
  265. package/dist/server/enterprise/saml.js.map +1 -0
  266. package/dist/server/enterprise/scim.d.ts +1 -0
  267. package/dist/server/enterprise/scim.js +97 -0
  268. package/dist/server/enterprise/scim.js.map +1 -0
  269. package/dist/server/enterprise/shared.d.ts +5 -0
  270. package/dist/server/enterprise/shared.d.ts.map +1 -0
  271. package/dist/server/enterprise/shared.js +51 -0
  272. package/dist/server/enterprise/shared.js.map +1 -0
  273. package/dist/server/enterprise/validators.d.ts +1 -0
  274. package/dist/server/{enterpriseValidators.js → enterprise/validators.js} +2 -2
  275. package/dist/server/enterprise/validators.js.map +1 -0
  276. package/dist/server/errors.d.ts +2 -0
  277. package/dist/server/errors.d.ts.map +1 -1
  278. package/dist/server/errors.js +2 -0
  279. package/dist/server/errors.js.map +1 -1
  280. package/dist/server/http.d.ts +2 -2
  281. package/dist/server/http.d.ts.map +1 -1
  282. package/dist/server/http.js +2 -1
  283. package/dist/server/http.js.map +1 -1
  284. package/dist/server/identity.js +1 -1
  285. package/dist/server/identity.js.map +1 -1
  286. package/dist/server/index.d.ts +4 -705
  287. package/dist/server/index.js +4 -1367
  288. package/dist/server/limits.d.ts +1 -0
  289. package/dist/server/{ratelimit.js → limits.js} +3 -3
  290. package/dist/server/limits.js.map +1 -0
  291. package/dist/server/mounts.d.ts +647 -0
  292. package/dist/server/mounts.d.ts.map +1 -0
  293. package/dist/server/mounts.js +643 -0
  294. package/dist/server/mounts.js.map +1 -0
  295. package/dist/server/mutations/account.d.ts +8 -8
  296. package/dist/server/mutations/account.js +3 -3
  297. package/dist/server/mutations/account.js.map +1 -1
  298. package/dist/server/mutations/code.d.ts +13 -13
  299. package/dist/server/mutations/code.js +2 -2
  300. package/dist/server/mutations/code.js.map +1 -1
  301. package/dist/server/mutations/index.d.ts +8 -312
  302. package/dist/server/mutations/index.js +14 -84
  303. package/dist/server/mutations/invalidate.d.ts +5 -5
  304. package/dist/server/mutations/invalidate.d.ts.map +1 -1
  305. package/dist/server/mutations/invalidate.js +1 -1
  306. package/dist/server/mutations/invalidate.js.map +1 -1
  307. package/dist/server/mutations/oauth.d.ts +9 -9
  308. package/dist/server/mutations/oauth.d.ts.map +1 -1
  309. package/dist/server/mutations/oauth.js +5 -3
  310. package/dist/server/mutations/oauth.js.map +1 -1
  311. package/dist/server/mutations/refresh.d.ts +4 -4
  312. package/dist/server/mutations/refresh.js +1 -1
  313. package/dist/server/mutations/refresh.js.map +1 -1
  314. package/dist/server/mutations/register.d.ts +10 -10
  315. package/dist/server/mutations/register.d.ts.map +1 -1
  316. package/dist/server/mutations/register.js +3 -3
  317. package/dist/server/mutations/register.js.map +1 -1
  318. package/dist/server/mutations/retrieve.d.ts +8 -10
  319. package/dist/server/mutations/retrieve.d.ts.map +1 -1
  320. package/dist/server/mutations/retrieve.js +5 -7
  321. package/dist/server/mutations/retrieve.js.map +1 -1
  322. package/dist/server/mutations/signature.d.ts +6 -6
  323. package/dist/server/mutations/signature.d.ts.map +1 -1
  324. package/dist/server/mutations/signature.js +2 -2
  325. package/dist/server/mutations/signature.js.map +1 -1
  326. package/dist/server/mutations/signin.d.ts +1 -1
  327. package/dist/server/mutations/signin.js +1 -1
  328. package/dist/server/mutations/signin.js.map +1 -1
  329. package/dist/server/mutations/signout.d.ts +1 -1
  330. package/dist/server/mutations/signout.js +1 -1
  331. package/dist/server/mutations/signout.js.map +1 -1
  332. package/dist/server/mutations/store/refs.d.ts +12 -0
  333. package/dist/server/mutations/store/refs.d.ts.map +1 -0
  334. package/dist/server/mutations/store/refs.js +15 -0
  335. package/dist/server/mutations/store/refs.js.map +1 -0
  336. package/dist/server/mutations/store.d.ts +303 -9
  337. package/dist/server/mutations/store.d.ts.map +1 -1
  338. package/dist/server/mutations/store.js +80 -10
  339. package/dist/server/mutations/store.js.map +1 -1
  340. package/dist/server/mutations/verifier.d.ts +1 -1
  341. package/dist/server/mutations/verifier.js +1 -1
  342. package/dist/server/mutations/verifier.js.map +1 -1
  343. package/dist/server/mutations/verify.d.ts +8 -8
  344. package/dist/server/mutations/verify.d.ts.map +1 -1
  345. package/dist/server/mutations/verify.js +4 -3
  346. package/dist/server/mutations/verify.js.map +1 -1
  347. package/dist/server/oauth.js +1 -1
  348. package/dist/server/oauth.js.map +1 -1
  349. package/dist/server/passkey.d.ts +2 -2
  350. package/dist/server/passkey.d.ts.map +1 -1
  351. package/dist/server/passkey.js +3 -2
  352. package/dist/server/passkey.js.map +1 -1
  353. package/dist/server/redirects.js +1 -1
  354. package/dist/server/redirects.js.map +1 -1
  355. package/dist/server/refresh.js +1 -1
  356. package/dist/server/refresh.js.map +1 -1
  357. package/dist/server/{factory.d.ts → runtime.d.ts} +16 -16
  358. package/dist/server/runtime.d.ts.map +1 -0
  359. package/dist/server/runtime.js +413 -0
  360. package/dist/server/runtime.js.map +1 -0
  361. package/dist/server/signin.js +3 -2
  362. package/dist/server/signin.js.map +1 -1
  363. package/dist/server/ssr.d.ts +226 -0
  364. package/dist/server/ssr.d.ts.map +1 -0
  365. package/dist/server/ssr.js +786 -0
  366. package/dist/server/ssr.js.map +1 -0
  367. package/dist/server/totp.js +2 -1
  368. package/dist/server/totp.js.map +1 -1
  369. package/dist/server/types.d.ts +242 -42
  370. package/dist/server/types.d.ts.map +1 -1
  371. package/dist/server/types.js +12 -1
  372. package/dist/server/types.js.map +1 -1
  373. package/dist/server/users.js +1 -1
  374. package/dist/server/users.js.map +1 -1
  375. package/dist/server/utils.js +1 -1
  376. package/dist/server/utils.js.map +1 -1
  377. package/package.json +3 -4
  378. package/src/authorization/index.ts +46 -0
  379. package/src/cli/index.ts +38 -20
  380. package/src/client/core/types.ts +437 -0
  381. package/src/client/factors/device.ts +160 -0
  382. package/src/client/factors/passkey.ts +282 -0
  383. package/src/client/factors/totp.ts +150 -0
  384. package/src/client/index.ts +133 -1267
  385. package/src/client/runtime/browser.ts +112 -0
  386. package/src/client/runtime/invite.ts +65 -0
  387. package/src/client/runtime/proxy.ts +111 -0
  388. package/src/client/runtime/storage.ts +79 -0
  389. package/src/component/_generated/api.ts +40 -12
  390. package/src/component/_generated/component.ts +1772 -1798
  391. package/src/component/model.ts +6 -0
  392. package/src/component/public/enterprise/audit.ts +120 -0
  393. package/src/component/public/enterprise/core.ts +354 -0
  394. package/src/component/public/enterprise/domains.ts +323 -0
  395. package/src/component/public/enterprise/scim.ts +396 -0
  396. package/src/component/public/enterprise/secrets.ts +132 -0
  397. package/src/component/public/enterprise/webhooks.ts +306 -0
  398. package/src/component/public/factors/devices.ts +223 -0
  399. package/src/component/public/factors/passkeys.ts +242 -0
  400. package/src/component/public/factors/totp.ts +258 -0
  401. package/src/component/public/groups/core.ts +481 -0
  402. package/src/component/public/groups/invites.ts +602 -0
  403. package/src/component/public/groups/members.ts +409 -0
  404. package/src/component/public/identity/accounts.ts +206 -0
  405. package/src/component/public/identity/codes.ts +148 -0
  406. package/src/component/public/identity/sessions.ts +209 -0
  407. package/src/component/public/identity/tokens.ts +250 -0
  408. package/src/component/public/identity/users.ts +354 -0
  409. package/src/component/public/identity/verifiers.ts +157 -0
  410. package/src/component/public/security/keys.ts +365 -0
  411. package/src/component/public/security/limits.ts +173 -0
  412. package/src/component/public.ts +27 -5
  413. package/src/providers/credentials.ts +4 -5
  414. package/src/providers/email.ts +30 -0
  415. package/src/providers/phone.ts +28 -0
  416. package/src/server/auth.ts +282 -28
  417. package/src/server/authError.ts +44 -0
  418. package/src/server/core.ts +2095 -0
  419. package/src/server/{provider.ts → crypto.ts} +1 -1
  420. package/src/server/device.ts +1 -1
  421. package/src/server/enterprise/config.ts +51 -0
  422. package/src/server/{domains/sso.ts → enterprise/domain.ts} +4 -2
  423. package/src/server/enterprise/http.ts +1324 -0
  424. package/src/server/enterprise/oidc.ts +500 -0
  425. package/src/server/enterprise/policy.ts +128 -0
  426. package/src/server/enterprise/saml.ts +578 -0
  427. package/src/server/enterprise/scim.ts +135 -0
  428. package/src/server/enterprise/shared.ts +134 -0
  429. package/src/server/errors.ts +2 -0
  430. package/src/server/http.ts +3 -1
  431. package/src/server/identity.ts +1 -1
  432. package/src/server/index.ts +32 -2605
  433. package/src/server/{ratelimit.ts → limits.ts} +1 -1
  434. package/src/server/mounts.ts +948 -0
  435. package/src/server/mutations/account.ts +4 -4
  436. package/src/server/mutations/code.ts +3 -3
  437. package/src/server/mutations/index.ts +8 -148
  438. package/src/server/mutations/invalidate.ts +2 -2
  439. package/src/server/mutations/oauth.ts +8 -6
  440. package/src/server/mutations/refresh.ts +3 -3
  441. package/src/server/mutations/register.ts +3 -3
  442. package/src/server/mutations/retrieve.ts +4 -8
  443. package/src/server/mutations/signature.ts +3 -3
  444. package/src/server/mutations/signin.ts +2 -2
  445. package/src/server/mutations/signout.ts +2 -2
  446. package/src/server/mutations/store/refs.ts +10 -0
  447. package/src/server/mutations/store.ts +137 -9
  448. package/src/server/mutations/verifier.ts +2 -2
  449. package/src/server/mutations/verify.ts +5 -5
  450. package/src/server/oauth.ts +1 -1
  451. package/src/server/passkey.ts +3 -4
  452. package/src/server/redirects.ts +1 -1
  453. package/src/server/refresh.ts +1 -1
  454. package/src/server/runtime.ts +880 -0
  455. package/src/server/signin.ts +3 -1
  456. package/src/server/ssr.ts +1764 -0
  457. package/src/server/totp.ts +3 -1
  458. package/src/server/types.ts +254 -43
  459. package/src/server/users.ts +1 -1
  460. package/src/server/utils.ts +1 -1
  461. package/src/test.ts +25 -0
  462. package/dist/component/public/enterprise.d.ts +0 -54
  463. package/dist/component/public/enterprise.d.ts.map +0 -1
  464. package/dist/component/public/enterprise.js +0 -515
  465. package/dist/component/public/enterprise.js.map +0 -1
  466. package/dist/component/public/factors.d.ts +0 -52
  467. package/dist/component/public/factors.d.ts.map +0 -1
  468. package/dist/component/public/factors.js +0 -285
  469. package/dist/component/public/factors.js.map +0 -1
  470. package/dist/component/public/groups.d.ts +0 -124
  471. package/dist/component/public/groups.d.ts.map +0 -1
  472. package/dist/component/public/groups.js +0 -680
  473. package/dist/component/public/groups.js.map +0 -1
  474. package/dist/component/public/identity.d.ts +0 -93
  475. package/dist/component/public/identity.d.ts.map +0 -1
  476. package/dist/component/public/identity.js +0 -426
  477. package/dist/component/public/identity.js.map +0 -1
  478. package/dist/component/public/keys.d.ts +0 -41
  479. package/dist/component/public/keys.d.ts.map +0 -1
  480. package/dist/component/public/keys.js +0 -157
  481. package/dist/component/public/keys.js.map +0 -1
  482. package/dist/component/public/shared.d.ts +0 -26
  483. package/dist/component/public/shared.d.ts.map +0 -1
  484. package/dist/component/public/shared.js +0 -32
  485. package/dist/component/public/shared.js.map +0 -1
  486. package/dist/component/server/domains/core.js.map +0 -1
  487. package/dist/component/server/domains/sso.js.map +0 -1
  488. package/dist/component/server/factory.d.ts.map +0 -1
  489. package/dist/component/server/factory.js +0 -1134
  490. package/dist/component/server/factory.js.map +0 -1
  491. package/dist/component/server/fx.js +0 -66
  492. package/dist/component/server/fx.js.map +0 -1
  493. package/dist/component/server/mutations/index.js +0 -85
  494. package/dist/component/server/mutations/index.js.map +0 -1
  495. package/dist/component/server/providers.js.map +0 -1
  496. package/dist/component/server/ratelimit.js.map +0 -1
  497. package/dist/component/server/sso.js +0 -830
  498. package/dist/component/server/sso.js.map +0 -1
  499. package/dist/server/domains/core.d.ts +0 -449
  500. package/dist/server/domains/core.d.ts.map +0 -1
  501. package/dist/server/domains/core.js.map +0 -1
  502. package/dist/server/domains/sso.d.ts.map +0 -1
  503. package/dist/server/domains/sso.js.map +0 -1
  504. package/dist/server/enterpriseValidators.js.map +0 -1
  505. package/dist/server/factory.d.ts.map +0 -1
  506. package/dist/server/factory.js +0 -1134
  507. package/dist/server/factory.js.map +0 -1
  508. package/dist/server/fx.d.ts.map +0 -1
  509. package/dist/server/fx.js +0 -66
  510. package/dist/server/fx.js.map +0 -1
  511. package/dist/server/index.d.ts.map +0 -1
  512. package/dist/server/index.js.map +0 -1
  513. package/dist/server/mutations/index.d.ts.map +0 -1
  514. package/dist/server/mutations/index.js.map +0 -1
  515. package/dist/server/provider.d.ts.map +0 -1
  516. package/dist/server/provider.js.map +0 -1
  517. package/dist/server/providers.js.map +0 -1
  518. package/dist/server/ratelimit.js.map +0 -1
  519. package/dist/server/sso.js +0 -830
  520. package/dist/server/sso.js.map +0 -1
  521. package/dist/server/version.d.ts +0 -5
  522. package/dist/server/version.d.ts.map +0 -1
  523. package/dist/server/version.js +0 -6
  524. package/dist/server/version.js.map +0 -1
  525. package/src/component/public/enterprise.ts +0 -753
  526. package/src/component/public/factors.ts +0 -333
  527. package/src/component/public/groups.ts +0 -1066
  528. package/src/component/public/identity.ts +0 -569
  529. package/src/component/public/keys.ts +0 -209
  530. package/src/component/public/shared.ts +0 -119
  531. package/src/server/domains/core.ts +0 -1096
  532. package/src/server/factory.ts +0 -2181
  533. package/src/server/fx.ts +0 -152
  534. package/src/server/sso.ts +0 -1516
  535. package/src/server/version.ts +0 -2
  536. /package/dist/server/{enterpriseValidators.d.ts → config.d.ts} +0 -0
  537. /package/dist/server/{providers.d.ts → enterprise/config.d.ts} +0 -0
  538. /package/dist/server/{ratelimit.d.ts → enterprise/oidc.d.ts} +0 -0
  539. /package/dist/server/{sso.d.ts → enterprise/policy.d.ts} +0 -0
  540. /package/src/server/{providers.ts → config.ts} +0 -0
  541. /package/src/server/{enterpriseValidators.ts → enterprise/validators.ts} +0 -0
package/src/server/sso.ts DELETED
@@ -1,1516 +0,0 @@
1
- import { sha256 } from "@oslojs/crypto/sha2";
2
- import {
3
- decodeBase64urlIgnorePadding,
4
- encodeBase64urlNoPadding,
5
- } from "@oslojs/encoding";
6
- import { Fx } from "@robelest/fx";
7
- import {
8
- Constants,
9
- IdentityProvider,
10
- ServiceProvider,
11
- setSchemaValidator,
12
- } from "@robelest/samlify";
13
-
14
- // Samlify requires a schema validator to be registered before parsing any SAML
15
- // response. We use a permissive validator that always resolves because Convex's
16
- // edge runtime has no file-system access for XML schema files, and structural
17
- // correctness is already ensured by the XML parser. This is called directly
18
- // before each parse operation since Convex can restart the V8 isolate between
19
- // requests, resetting module-level state.
20
- const _samlifyPermissiveValidator = {
21
- validate: (_xml: string) => Promise.resolve("OK"),
22
- };
23
- function ensureSamlifyValidator() {
24
- setSchemaValidator(_samlifyPermissiveValidator);
25
- }
26
- import { decodeIdToken } from "arctic";
27
- import {
28
- createRemoteJWKSet,
29
- customFetch,
30
- decodeProtectedHeader,
31
- jwtVerify,
32
- } from "jose";
33
-
34
- import type {
35
- EnterprisePolicy,
36
- EnterprisePolicyPatch,
37
- OAuthMaterializedConfig,
38
- OAuthProfile,
39
- SAMLAttributeMapping,
40
- } from "./types";
41
-
42
- /** @internal */
43
- export type ParsedSamlMetadata = {
44
- issuer: string;
45
- sso: {
46
- redirect?: string;
47
- post?: string;
48
- };
49
- slo: {
50
- redirect?: string;
51
- post?: string;
52
- };
53
- signingCert: string | string[] | null;
54
- encryptionCert: string | string[] | null;
55
- nameIdFormats: string[];
56
- wantsSignedAuthnRequests: boolean;
57
- };
58
-
59
- /** @internal */
60
- export type EnterpriseSamlSource = { kind: "enterprise"; id: string };
61
-
62
- /** @internal */
63
- export type EnterpriseSamlRelayState = {
64
- source: EnterpriseSamlSource;
65
- signature: string;
66
- requestId: string;
67
- state: string;
68
- redirectTo?: string;
69
- };
70
-
71
- /** @internal */
72
- export type EnterpriseSamlUrls = {
73
- metadataUrl: string;
74
- acsUrl: string;
75
- sloUrl?: string;
76
- };
77
-
78
- /** @internal */
79
- export type EnterpriseSamlLoadedSource = {
80
- source: EnterpriseSamlSource;
81
- config: unknown;
82
- status?: string;
83
- };
84
-
85
- /** @internal */
86
- export type EnterpriseSamlHttpRequest = {
87
- url: URL;
88
- body: Record<string, string>;
89
- query: Record<string, string>;
90
- binding: "redirect" | "post";
91
- relayState?: string;
92
- hasSamlRequest: boolean;
93
- hasSamlResponse: boolean;
94
- };
95
-
96
- /** @internal */
97
- export type ScimListRequest = {
98
- startIndex: number;
99
- count: number;
100
- filter?: { attribute: string; value: string };
101
- };
102
-
103
- /** @internal */
104
- export const SCIM_USER_SCHEMA_ID = "urn:ietf:params:scim:schemas:core:2.0:User";
105
- /** @internal */
106
- export const SCIM_GROUP_SCHEMA_ID =
107
- "urn:ietf:params:scim:schemas:core:2.0:Group";
108
-
109
- /** @internal */
110
- export const ENTERPRISE_OIDC_PROVIDER_PREFIX = "enterprise:oidc:";
111
- /** @internal */
112
- export const ENTERPRISE_SAML_PROVIDER_PREFIX = "enterprise:saml:";
113
- const OIDC_JWKS_CACHE = new Map<
114
- string,
115
- ReturnType<typeof createRemoteJWKSet>
116
- >();
117
-
118
- /** @internal */
119
- export function normalizeDomain(domain: string): string {
120
- return domain.trim().toLowerCase().replace(/^@+/, "");
121
- }
122
-
123
- /** @internal */
124
- export function enterpriseOidcProviderId(enterpriseId: string): string {
125
- return `${ENTERPRISE_OIDC_PROVIDER_PREFIX}${enterpriseId}`;
126
- }
127
-
128
- /** @internal */
129
- export function enterpriseSamlProviderId(enterpriseId: string): string {
130
- return `${ENTERPRISE_SAML_PROVIDER_PREFIX}${enterpriseId}`;
131
- }
132
-
133
- /** @internal */
134
- export function getEnterpriseSamlUrls(opts: {
135
- rootUrl: string;
136
- source: EnterpriseSamlSource;
137
- }): EnterpriseSamlUrls {
138
- const root = opts.rootUrl.replace(/\/$/, "");
139
- const metadataBase = `${root}/api/auth/sso/${opts.source.id}/saml/metadata`;
140
- const acsBase = `${root}/api/auth/sso/${opts.source.id}/saml/acs`;
141
- const sloBase = `${root}/api/auth/sso/${opts.source.id}/saml/slo`;
142
- return {
143
- metadataUrl: metadataBase,
144
- acsUrl: acsBase,
145
- sloUrl: sloBase,
146
- };
147
- }
148
-
149
- /** @internal */
150
- export function getEnterpriseOidcUrls(opts: {
151
- rootUrl: string;
152
- enterpriseId: string;
153
- }) {
154
- const root = opts.rootUrl.replace(/\/$/, "");
155
- return {
156
- signInUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/signin`,
157
- callbackUrl: `${root}/api/auth/sso/${opts.enterpriseId}/oidc/callback`,
158
- };
159
- }
160
-
161
- /** @internal */
162
- export function isEnterpriseSamlSourceActive(
163
- source: EnterpriseSamlLoadedSource,
164
- ) {
165
- return source.status === "active";
166
- }
167
-
168
- /** @internal */
169
- export function isEnterpriseProviderId(providerId: string): boolean {
170
- return (
171
- providerId.startsWith(ENTERPRISE_OIDC_PROVIDER_PREFIX) ||
172
- providerId.startsWith(ENTERPRISE_SAML_PROVIDER_PREFIX)
173
- );
174
- }
175
-
176
- const asRecord = (value: unknown) =>
177
- typeof value === "object" && value !== null
178
- ? (value as Record<string, any>)
179
- : null;
180
-
181
- /** @internal */
182
- export const DEFAULT_ENTERPRISE_POLICY: EnterprisePolicy = {
183
- version: 1,
184
- identity: {
185
- accountLinking: {
186
- oidc: "verifiedEmail",
187
- saml: "verifiedEmail",
188
- },
189
- },
190
- provisioning: {
191
- scimReuse: {
192
- user: "externalId",
193
- },
194
- jit: {
195
- mode: "createUserAndMembership",
196
- defaultRoleIds: [],
197
- },
198
- deprovision: {
199
- mode: "soft",
200
- },
201
- },
202
- };
203
-
204
- /** @internal */
205
- export function normalizeEnterprisePolicy(policy: unknown): EnterprisePolicy {
206
- const input = asRecord(policy) ?? {};
207
- const identity = asRecord(input.identity) ?? {};
208
- const accountLinking = asRecord(identity.accountLinking) ?? {};
209
- const provisioning = asRecord(input.provisioning) ?? {};
210
- const scimReuse = asRecord(provisioning.scimReuse) ?? {};
211
- const jit = asRecord(provisioning.jit) ?? {};
212
- const deprovision = asRecord(provisioning.deprovision) ?? {};
213
- const extend = asRecord(input.extend) ?? undefined;
214
-
215
- return {
216
- version: 1,
217
- identity: {
218
- accountLinking: {
219
- oidc:
220
- accountLinking.oidc === "none"
221
- ? "none"
222
- : DEFAULT_ENTERPRISE_POLICY.identity.accountLinking.oidc,
223
- saml:
224
- accountLinking.saml === "none"
225
- ? "none"
226
- : DEFAULT_ENTERPRISE_POLICY.identity.accountLinking.saml,
227
- },
228
- },
229
- provisioning: {
230
- scimReuse: {
231
- user:
232
- scimReuse.user === "none"
233
- ? "none"
234
- : DEFAULT_ENTERPRISE_POLICY.provisioning.scimReuse.user,
235
- },
236
- jit: {
237
- mode:
238
- jit.mode === "off" ||
239
- jit.mode === "createUser" ||
240
- jit.mode === "createUserAndMembership"
241
- ? jit.mode
242
- : DEFAULT_ENTERPRISE_POLICY.provisioning.jit.mode,
243
- defaultRoleIds: Array.isArray(jit.defaultRoleIds)
244
- ? Array.from(
245
- new Set(
246
- jit.defaultRoleIds.filter(
247
- (value): value is string =>
248
- typeof value === "string" && value.length > 0,
249
- ),
250
- ),
251
- )
252
- : typeof jit.defaultRole === "string" && jit.defaultRole.length > 0
253
- ? [jit.defaultRole]
254
- : DEFAULT_ENTERPRISE_POLICY.provisioning.jit.defaultRoleIds,
255
- },
256
- deprovision: {
257
- mode:
258
- deprovision.mode === "hard"
259
- ? "hard"
260
- : DEFAULT_ENTERPRISE_POLICY.provisioning.deprovision.mode,
261
- },
262
- },
263
- ...(extend ? { extend } : {}),
264
- };
265
- }
266
-
267
- /** @internal */
268
- export function patchEnterprisePolicy(
269
- current: unknown,
270
- patch: EnterprisePolicyPatch,
271
- ): EnterprisePolicy {
272
- const base = normalizeEnterprisePolicy(current);
273
- return normalizeEnterprisePolicy({
274
- ...base,
275
- ...patch,
276
- identity: {
277
- ...base.identity,
278
- ...patch.identity,
279
- accountLinking: {
280
- ...base.identity.accountLinking,
281
- ...patch.identity?.accountLinking,
282
- },
283
- },
284
- provisioning: {
285
- ...base.provisioning,
286
- ...patch.provisioning,
287
- scimReuse: {
288
- ...base.provisioning.scimReuse,
289
- ...patch.provisioning?.scimReuse,
290
- },
291
- jit: {
292
- ...base.provisioning.jit,
293
- ...patch.provisioning?.jit,
294
- },
295
- deprovision: {
296
- ...base.provisioning.deprovision,
297
- ...patch.provisioning?.deprovision,
298
- },
299
- },
300
- extend:
301
- patch.extend === undefined
302
- ? base.extend
303
- : { ...base.extend, ...patch.extend },
304
- });
305
- }
306
-
307
- const getProtocolConfig = (config: unknown, protocol: "oidc" | "saml") => {
308
- const base = asRecord(config);
309
- const direct = base?.[protocol];
310
- const viaProtocols = asRecord(base?.protocols)?.[protocol];
311
- return asRecord(direct) ?? asRecord(viaProtocols) ?? {};
312
- };
313
-
314
- /** @internal */
315
- export function getOidcConfig(config: unknown): Record<string, any> {
316
- return getProtocolConfig(config, "oidc");
317
- }
318
-
319
- /** @internal */
320
- export function getPublicOidcConfig(config: unknown): Record<string, any> {
321
- const oidc = getOidcConfig(config);
322
- const { clientSecret: _clientSecret, ...publicOidc } = oidc;
323
- return publicOidc;
324
- }
325
-
326
- /** @internal */
327
- export function withOidcSecretState(
328
- config: Record<string, any>,
329
- hasClientSecret: boolean,
330
- ) {
331
- return {
332
- ...config,
333
- hasClientSecret,
334
- };
335
- }
336
-
337
- /** @internal */
338
- export function getSamlConfig(config: unknown): Record<string, any> {
339
- return getProtocolConfig(config, "saml");
340
- }
341
-
342
- /** @internal */
343
- export function upsertProtocolConfig(
344
- config: unknown,
345
- protocol: "oidc" | "saml",
346
- protocolConfig: Record<string, unknown>,
347
- ) {
348
- const base = asRecord(config) ?? {};
349
- const protocols = asRecord(base.protocols) ?? {};
350
- protocols[protocol] = {
351
- ...asRecord(protocols[protocol]),
352
- ...protocolConfig,
353
- };
354
- return { ...base, protocols };
355
- }
356
-
357
- /** @internal */
358
- export function createSamlPostBindingResponse(opts: {
359
- endpoint: string;
360
- parameter: "SAMLRequest" | "SAMLResponse";
361
- value: string;
362
- relayState?: string;
363
- }) {
364
- const fields = [
365
- `<input type="hidden" name="${opts.parameter}" value="${opts.value.replace(/"/g, "&quot;")}" />`,
366
- opts.relayState
367
- ? `<input type="hidden" name="RelayState" value="${opts.relayState.replace(/"/g, "&quot;")}" />`
368
- : "",
369
- ].join("");
370
- return new Response(
371
- `<!doctype html><html><body><form method="POST" action="${opts.endpoint}">${fields}</form><script>document.forms[0].submit();</script></body></html>`,
372
- { status: 200, headers: { "Content-Type": "text/html; charset=utf-8" } },
373
- );
374
- }
375
-
376
- /** @internal */
377
- export function decodeRelayState(
378
- value: string | null,
379
- ): Record<string, unknown> {
380
- if (!value) {
381
- return {};
382
- }
383
- try {
384
- return JSON.parse(
385
- new TextDecoder().decode(decodeBase64urlIgnorePadding(value)),
386
- );
387
- } catch {
388
- return {};
389
- }
390
- }
391
-
392
- /** @internal */
393
- export function encodeEnterpriseSamlRelayState(
394
- value: EnterpriseSamlRelayState,
395
- ) {
396
- return encodeBase64urlNoPadding(
397
- new TextEncoder().encode(
398
- JSON.stringify({
399
- source: `${value.source.kind}:${value.source.id}`,
400
- signature: value.signature,
401
- requestId: value.requestId,
402
- state: value.state,
403
- redirectTo: value.redirectTo,
404
- }),
405
- ),
406
- );
407
- }
408
-
409
- /** @internal */
410
- export function decodeEnterpriseSamlRelayStateOrThrow(
411
- value: string | null,
412
- ): EnterpriseSamlRelayState {
413
- if (!value) {
414
- throw new Error("Missing SAML RelayState.");
415
- }
416
- const decoded = decodeRelayState(value);
417
- if (
418
- typeof decoded.source !== "string" ||
419
- typeof decoded.signature !== "string" ||
420
- typeof decoded.requestId !== "string" ||
421
- typeof decoded.state !== "string"
422
- ) {
423
- throw new Error("Invalid SAML RelayState.");
424
- }
425
- const [kind, ...rest] = decoded.source.split(":");
426
- const id = rest.join(":");
427
- if (kind !== "enterprise" || id.length === 0) {
428
- throw new Error("Invalid enterprise SAML source.");
429
- }
430
- return {
431
- source: { kind, id } as EnterpriseSamlSource,
432
- signature: decoded.signature,
433
- requestId: decoded.requestId,
434
- state: decoded.state,
435
- redirectTo:
436
- typeof decoded.redirectTo === "string" ? decoded.redirectTo : undefined,
437
- };
438
- }
439
-
440
- /** @internal */
441
- export async function readRequestBody(
442
- request: Request,
443
- ): Promise<Record<string, string>> {
444
- const contentType = request.headers.get("Content-Type") ?? "";
445
- if (
446
- contentType.includes("application/x-www-form-urlencoded") ||
447
- contentType.includes("multipart/form-data")
448
- ) {
449
- const form = await request.formData();
450
- const body: Record<string, string> = {};
451
- form.forEach((value, key) => {
452
- body[key] = typeof value === "string" ? value : value.name;
453
- });
454
- return body;
455
- }
456
- return {};
457
- }
458
-
459
- /** @internal */
460
- export async function readEnterpriseSamlHttpRequest(
461
- request: Request,
462
- ): Promise<EnterpriseSamlHttpRequest> {
463
- const url = new URL(request.url);
464
- const body = await readRequestBody(request);
465
- const query = Object.fromEntries(url.searchParams);
466
- const binding =
467
- request.method === "GET"
468
- ? "redirect"
469
- : body.SAMLResponse || body.SAMLRequest
470
- ? "post"
471
- : "redirect";
472
- return {
473
- url,
474
- body,
475
- query,
476
- binding,
477
- relayState:
478
- body.RelayState ?? url.searchParams.get("RelayState") ?? undefined,
479
- hasSamlRequest: Boolean(
480
- body.SAMLRequest ?? url.searchParams.get("SAMLRequest"),
481
- ),
482
- hasSamlResponse: Boolean(
483
- body.SAMLResponse ?? url.searchParams.get("SAMLResponse"),
484
- ),
485
- };
486
- }
487
-
488
- async function discoverOidcConfiguration(config: Record<string, any>) {
489
- const discoveryUrl =
490
- typeof config.discoveryUrl === "string"
491
- ? config.discoveryUrl
492
- : typeof config.issuer === "string"
493
- ? `${config.issuer.replace(/\/$/, "")}/.well-known/openid-configuration`
494
- : null;
495
-
496
- if (!discoveryUrl) {
497
- throw new Error("Enterprise OIDC requires an issuer or discoveryUrl.");
498
- }
499
-
500
- const oidcFetch = createEnterpriseOidcFetch(config, config.issuer);
501
-
502
- return await Fx.run(
503
- Fx.defer(() =>
504
- Fx.from({
505
- ok: async () => {
506
- const response = await oidcFetch(discoveryUrl);
507
- if (!response.ok) {
508
- throw new Error(
509
- `Failed to discover OIDC configuration: ${response.status}`,
510
- );
511
- }
512
- const discovery = (await response.json()) as Record<string, any>;
513
- if (
514
- typeof discovery.issuer !== "string" ||
515
- typeof discovery.authorization_endpoint !== "string" ||
516
- typeof discovery.token_endpoint !== "string" ||
517
- typeof discovery.jwks_uri !== "string"
518
- ) {
519
- throw new Error(
520
- "OIDC discovery document is missing required fields.",
521
- );
522
- }
523
- return discovery;
524
- },
525
- err: (error) =>
526
- error instanceof Error ? error : new Error(String(error)),
527
- }),
528
- ).pipe(
529
- Fx.timeout(10_000),
530
- Fx.retry(
531
- Fx.retry.compose(
532
- Fx.retry.jittered(Fx.retry.exponential(200)),
533
- Fx.retry.recurs(2),
534
- ),
535
- ),
536
- Fx.recover((error) =>
537
- Fx.fail(error instanceof Error ? error : new Error(String(error))),
538
- ),
539
- ),
540
- );
541
- }
542
-
543
- function createEnterpriseOidcFetch(
544
- config: Record<string, any>,
545
- discoveredIssuer?: string,
546
- ) {
547
- const runtimeOrigin =
548
- typeof config.discoveryUrl === "string"
549
- ? new URL(config.discoveryUrl).origin
550
- : undefined;
551
- const externalHost =
552
- typeof config.issuer === "string"
553
- ? new URL(config.issuer).host
554
- : typeof discoveredIssuer === "string"
555
- ? new URL(discoveredIssuer).host
556
- : undefined;
557
-
558
- return async (input: string | URL, init?: RequestInit) => {
559
- const url = new URL(typeof input === "string" ? input : input.toString());
560
- const rewrittenUrl =
561
- runtimeOrigin !== undefined && url.origin !== runtimeOrigin
562
- ? new URL(`${runtimeOrigin}${url.pathname}${url.search}`)
563
- : url;
564
- const headers = new Headers(init?.headers);
565
- if (runtimeOrigin !== undefined && externalHost !== undefined) {
566
- headers.set("host", externalHost);
567
- }
568
- return await fetch(rewrittenUrl, { ...init, headers });
569
- };
570
- }
571
-
572
- function getOidcJwks(
573
- url: string,
574
- fetchImpl?: ReturnType<typeof createEnterpriseOidcFetch>,
575
- ) {
576
- const cacheKey = fetchImpl ? `${url}::custom` : url;
577
- let jwks = OIDC_JWKS_CACHE.get(cacheKey);
578
- if (!jwks) {
579
- jwks = fetchImpl
580
- ? createRemoteJWKSet(new URL(url), { [customFetch]: fetchImpl })
581
- : createRemoteJWKSet(new URL(url));
582
- OIDC_JWKS_CACHE.set(cacheKey, jwks);
583
- }
584
- return jwks;
585
- }
586
-
587
- type UserInfoFetchFailure =
588
- | { kind: "transport"; error: unknown }
589
- | { kind: "subject-mismatch" };
590
-
591
- function userInfoProfileFx(opts: {
592
- endpoint: string;
593
- accessToken: string;
594
- verifiedClaims: Record<string, unknown>;
595
- verifiedProfile: OAuthProfile & { emailVerified?: boolean };
596
- fetchImpl?: ReturnType<typeof createEnterpriseOidcFetch>;
597
- }) {
598
- return Fx.from({
599
- ok: async () => {
600
- const response = await (opts.fetchImpl ?? fetch)(opts.endpoint, {
601
- headers: { Authorization: `Bearer ${opts.accessToken}` },
602
- });
603
- if (!response.ok) {
604
- throw new Error(`OIDC userinfo request failed: ${response.status}`);
605
- }
606
- return (await response.json()) as Record<string, unknown>;
607
- },
608
- err: (error): UserInfoFetchFailure => ({ kind: "transport", error }),
609
- }).pipe(
610
- Fx.chain((userInfo) => {
611
- const userInfoSubject =
612
- typeof userInfo.sub === "string" ? userInfo.sub : undefined;
613
- const tokenSubject =
614
- typeof opts.verifiedClaims.sub === "string"
615
- ? opts.verifiedClaims.sub
616
- : undefined;
617
- return userInfoSubject !== undefined &&
618
- tokenSubject !== undefined &&
619
- userInfoSubject !== tokenSubject
620
- ? Fx.fail({ kind: "subject-mismatch" } as const)
621
- : Fx.succeed({
622
- id:
623
- userInfoSubject ??
624
- (typeof opts.verifiedClaims.sub === "string"
625
- ? opts.verifiedClaims.sub
626
- : undefined) ??
627
- crypto.randomUUID(),
628
- email:
629
- typeof userInfo.email === "string"
630
- ? userInfo.email
631
- : opts.verifiedProfile.email,
632
- emailVerified:
633
- typeof userInfo.email_verified === "boolean"
634
- ? userInfo.email_verified
635
- : opts.verifiedProfile.emailVerified,
636
- name:
637
- typeof userInfo.name === "string"
638
- ? userInfo.name
639
- : opts.verifiedProfile.name,
640
- image:
641
- typeof userInfo.picture === "string"
642
- ? userInfo.picture
643
- : opts.verifiedProfile.image,
644
- } as OAuthProfile & { emailVerified?: boolean });
645
- }),
646
- Fx.recover((failure) => {
647
- if (failure.kind === "transport") {
648
- return Fx.succeed(null);
649
- }
650
- return Fx.fail(
651
- new Error("OIDC userinfo subject does not match ID token subject."),
652
- );
653
- }),
654
- );
655
- }
656
-
657
- /** @internal */
658
- export async function createEnterpriseOidcProvider(
659
- config: Record<string, any>,
660
- redirectUri: string,
661
- ) {
662
- const discovery = await discoverOidcConfiguration(config);
663
- const expectedIssuer = String(config.issuer ?? discovery.issuer).replace(
664
- /\/$/,
665
- "",
666
- );
667
- const discoveredIssuer = String(discovery.issuer).replace(/\/$/, "");
668
- const strictIssuer = config.strictIssuer === true;
669
- if (
670
- typeof config.issuer === "string" &&
671
- expectedIssuer !== discoveredIssuer
672
- ) {
673
- if (strictIssuer) {
674
- throw new Error(
675
- `Configured OIDC issuer mismatch. configured=${expectedIssuer} discovery=${discoveredIssuer}`,
676
- );
677
- }
678
- console.warn(
679
- "Configured OIDC issuer differs from discovery issuer; accepting both for token verification.",
680
- {
681
- configuredIssuer: expectedIssuer,
682
- discoveryIssuer: discoveredIssuer,
683
- },
684
- );
685
- }
686
- const authorizationEndpoint = discovery.authorization_endpoint as string;
687
- const tokenEndpoint = discovery.token_endpoint as string;
688
- const jwksUri = String(config.jwksUri ?? discovery.jwks_uri);
689
- const supportedIdTokenSigningAlgs = Array.isArray(
690
- discovery.id_token_signing_alg_values_supported,
691
- )
692
- ? discovery.id_token_signing_alg_values_supported.filter(
693
- (value: unknown): value is string => typeof value === "string",
694
- )
695
- : [];
696
- const userinfoEndpoint =
697
- (discovery.userinfo_endpoint as string | undefined) ?? undefined;
698
- const oidcFetch = createEnterpriseOidcFetch(
699
- config,
700
- discovery.issuer as string,
701
- );
702
- const scopes = Array.isArray(config.scopes)
703
- ? config.scopes.filter(
704
- (value: unknown): value is string => typeof value === "string",
705
- )
706
- : ["openid", "profile", "email"];
707
- const expectedAudience = config.audience ?? String(config.clientId);
708
- const getIssuerCandidates = (issuer: string) => {
709
- const candidates = [issuer];
710
- if (issuer.startsWith("https://")) {
711
- candidates.push(`http://${issuer.slice("https://".length)}`);
712
- } else if (issuer.startsWith("http://")) {
713
- candidates.push(`https://${issuer.slice("http://".length)}`);
714
- }
715
- return candidates;
716
- };
717
- const expectedIssuers = strictIssuer
718
- ? [expectedIssuer]
719
- : Array.from(
720
- new Set([
721
- ...getIssuerCandidates(expectedIssuer),
722
- ...getIssuerCandidates(discoveredIssuer),
723
- ]),
724
- );
725
- const jwks = getOidcJwks(jwksUri, oidcFetch);
726
- let verifiedClaims: Record<string, unknown> | null = null;
727
- let verifiedProfile: (OAuthProfile & { emailVerified?: boolean }) | null =
728
- null;
729
- const normalizeProfile = (claims: Record<string, unknown>) => ({
730
- id: typeof claims.sub === "string" ? claims.sub : crypto.randomUUID(),
731
- email: typeof claims.email === "string" ? claims.email : undefined,
732
- emailVerified:
733
- typeof claims.email_verified === "boolean"
734
- ? claims.email_verified
735
- : undefined,
736
- name: typeof claims.name === "string" ? claims.name : undefined,
737
- image: typeof claims.picture === "string" ? claims.picture : undefined,
738
- });
739
-
740
- const provider = {
741
- createAuthorizationURL(
742
- state: string,
743
- codeVerifier: string,
744
- requestedScopes: string[],
745
- ) {
746
- const url = new URL(authorizationEndpoint);
747
- url.searchParams.set("response_type", "code");
748
- url.searchParams.set("client_id", String(config.clientId));
749
- url.searchParams.set("redirect_uri", redirectUri);
750
- url.searchParams.set(
751
- "scope",
752
- (requestedScopes.length > 0 ? requestedScopes : scopes).join(" "),
753
- );
754
- url.searchParams.set("state", state);
755
- url.searchParams.set("code_challenge_method", "S256");
756
- url.searchParams.set(
757
- "code_challenge",
758
- encodeBase64urlNoPadding(
759
- sha256(new TextEncoder().encode(codeVerifier)),
760
- ),
761
- );
762
- const authorizationParams =
763
- typeof config.authorizationParams === "object" &&
764
- config.authorizationParams !== null
765
- ? (config.authorizationParams as Record<string, unknown>)
766
- : {};
767
- for (const [key, value] of Object.entries(authorizationParams)) {
768
- if (typeof value === "string") {
769
- url.searchParams.set(key, value);
770
- }
771
- }
772
- return url;
773
- },
774
- async validateAuthorizationCode(code: string, codeVerifier?: string) {
775
- const body = new URLSearchParams({
776
- grant_type: "authorization_code",
777
- code,
778
- redirect_uri: redirectUri,
779
- client_id: String(config.clientId),
780
- });
781
- if (typeof config.clientSecret === "string") {
782
- body.set("client_secret", config.clientSecret);
783
- }
784
- if (codeVerifier) {
785
- body.set("code_verifier", codeVerifier);
786
- }
787
- const response = await oidcFetch(tokenEndpoint, {
788
- method: "POST",
789
- headers: { "Content-Type": "application/x-www-form-urlencoded" },
790
- body,
791
- });
792
- if (!response.ok) {
793
- throw new Error(`OIDC token exchange failed: ${response.status}`);
794
- }
795
- const data = (await response.json()) as Record<string, any>;
796
- return {
797
- data,
798
- idToken() {
799
- if (typeof data.id_token !== "string") {
800
- throw new Error("OIDC response is missing id_token.");
801
- }
802
- return data.id_token;
803
- },
804
- accessToken() {
805
- if (typeof data.access_token !== "string") {
806
- throw new Error("OIDC response is missing access_token.");
807
- }
808
- return data.access_token;
809
- },
810
- };
811
- },
812
- };
813
-
814
- const oauthConfig = {
815
- scopes,
816
- nonce: true,
817
- validateTokens: async (tokens: any, ctx: { nonce?: string }) => {
818
- const verified = await Fx.run(
819
- Fx.gen(function* () {
820
- yield* Fx.guard(
821
- ctx.nonce === undefined,
822
- Fx.fail(new Error("OIDC nonce is required.")),
823
- );
824
-
825
- const idToken = tokens.idToken();
826
- const protectedHeader = decodeProtectedHeader(idToken);
827
- const tokenAlg = protectedHeader.alg;
828
- const useSymmetricValidation =
829
- typeof tokenAlg === "string" &&
830
- (tokenAlg === "HS256" ||
831
- tokenAlg === "HS384" ||
832
- tokenAlg === "HS512") &&
833
- supportedIdTokenSigningAlgs.includes(tokenAlg);
834
-
835
- const verificationOptions = {
836
- audience: expectedAudience,
837
- requiredClaims: ["iss", "sub", "aud", "exp", "iat"],
838
- clockTolerance: config.clockToleranceSeconds ?? 10,
839
- } as const;
840
-
841
- const verification = yield* Fx.from({
842
- ok: () =>
843
- useSymmetricValidation
844
- ? jwtVerify(
845
- idToken,
846
- (() => {
847
- if (typeof config.clientSecret !== "string") {
848
- throw new Error(
849
- "OIDC provider uses symmetric ID token signatures but clientSecret is missing.",
850
- );
851
- }
852
- return new TextEncoder().encode(config.clientSecret);
853
- })(),
854
- verificationOptions as any,
855
- )
856
- : jwtVerify(idToken, jwks as any, verificationOptions as any),
857
- err: (error) =>
858
- error instanceof Error ? error : new Error(String(error)),
859
- });
860
-
861
- const payload = verification.payload as Record<string, unknown>;
862
- const tokenIssuerRaw =
863
- typeof payload.iss === "string" ? payload.iss : undefined;
864
- const tokenIssuer =
865
- typeof tokenIssuerRaw === "string"
866
- ? tokenIssuerRaw.replace(/\/$/, "")
867
- : undefined;
868
-
869
- yield* Fx.guard(
870
- !tokenIssuer || !expectedIssuers.includes(tokenIssuer),
871
- Fx.fail(
872
- new Error(
873
- `OIDC token issuer mismatch. Received: ${tokenIssuer ?? "<missing>"}. Expected one of: ${expectedIssuers.join(", ")}`,
874
- ),
875
- ),
876
- );
877
-
878
- yield* Fx.guard(
879
- payload.nonce !== ctx.nonce,
880
- Fx.fail(new Error("OIDC nonce mismatch.")),
881
- );
882
-
883
- yield* Fx.guard(
884
- Array.isArray(payload.aud) &&
885
- payload.aud.length > 1 &&
886
- payload.azp !== String(config.clientId),
887
- Fx.fail(
888
- new Error("OIDC authorized party does not match client ID."),
889
- ),
890
- );
891
-
892
- return payload;
893
- }),
894
- );
895
-
896
- verifiedClaims = verified;
897
- verifiedProfile = normalizeProfile(verified);
898
- },
899
- accountLinking: config.accountLinking,
900
- profile: async (tokens: any): Promise<OAuthProfile> => {
901
- if (verifiedProfile === null || verifiedClaims === null) {
902
- const claims = decodeIdToken(tokens.idToken()) as Record<
903
- string,
904
- unknown
905
- >;
906
- verifiedClaims = claims;
907
- verifiedProfile = normalizeProfile(claims);
908
- }
909
- if (userinfoEndpoint && typeof tokens.accessToken === "function") {
910
- const userInfoProfile = await Fx.run(
911
- userInfoProfileFx({
912
- endpoint: userinfoEndpoint,
913
- accessToken: tokens.accessToken(),
914
- verifiedClaims,
915
- verifiedProfile,
916
- fetchImpl: oidcFetch,
917
- }),
918
- );
919
- if (userInfoProfile !== null) {
920
- return userInfoProfile;
921
- }
922
- }
923
- return verifiedProfile;
924
- },
925
- } as const;
926
-
927
- return { provider, oauthConfig };
928
- }
929
-
930
- /** @internal */
931
- export function createSyntheticOAuthMaterializedConfig(
932
- providerId: string,
933
- options?: {
934
- accountLinking?: OAuthMaterializedConfig["accountLinking"];
935
- },
936
- ): OAuthMaterializedConfig {
937
- return {
938
- id: providerId,
939
- type: "oauth",
940
- provider: null,
941
- scopes: [],
942
- accountLinking: options?.accountLinking ?? "verifiedEmail",
943
- };
944
- }
945
-
946
- /** @internal */
947
- export function parseSamlIdpMetadata(metadata: string): ParsedSamlMetadata {
948
- const idp = IdentityProvider({ metadata });
949
- const entityMeta = idp.entityMeta;
950
-
951
- const normalizeService = (value: unknown): string | undefined => {
952
- return typeof value === "string" && value.length > 0 ? value : undefined;
953
- };
954
-
955
- return {
956
- issuer: entityMeta.getEntityID(),
957
- sso: {
958
- redirect: normalizeService(entityMeta.getSingleSignOnService("redirect")),
959
- post: normalizeService(entityMeta.getSingleSignOnService("post")),
960
- },
961
- slo: {
962
- redirect: normalizeService(entityMeta.getSingleLogoutService("redirect")),
963
- post: normalizeService(entityMeta.getSingleLogoutService("post")),
964
- },
965
- signingCert: entityMeta.getX509Certificate("signing"),
966
- encryptionCert: entityMeta.getX509Certificate("encrypt"),
967
- nameIdFormats: (() => {
968
- const nameIdFormat = entityMeta.getNameIDFormat();
969
- return Array.isArray(nameIdFormat) ? nameIdFormat : [];
970
- })(),
971
- wantsSignedAuthnRequests: entityMeta.isWantAuthnRequestsSigned(),
972
- };
973
- }
974
-
975
- /** @internal */
976
- export function createServiceProviderMetadata(opts: {
977
- entityId: string;
978
- acsUrl: string;
979
- sloUrl?: string;
980
- authnRequestsSigned?: boolean;
981
- signingCert?: string | string[];
982
- encryptCert?: string | string[];
983
- privateKey?: string;
984
- privateKeyPass?: string;
985
- encPrivateKey?: string;
986
- encPrivateKeyPass?: string;
987
- }) {
988
- const binding = Constants.namespace.binding;
989
- const sp = ServiceProvider({
990
- entityID: opts.entityId,
991
- authnRequestsSigned: opts.authnRequestsSigned ?? false,
992
- privateKey: opts.privateKey,
993
- privateKeyPass: opts.privateKeyPass,
994
- signingCert: opts.signingCert,
995
- encryptCert: opts.encryptCert,
996
- encPrivateKey: opts.encPrivateKey,
997
- encPrivateKeyPass: opts.encPrivateKeyPass,
998
- assertionConsumerService: [
999
- {
1000
- Binding: binding.post,
1001
- Location: opts.acsUrl,
1002
- },
1003
- ],
1004
- singleLogoutService: opts.sloUrl
1005
- ? [
1006
- {
1007
- Binding: binding.redirect,
1008
- Location: opts.sloUrl,
1009
- },
1010
- {
1011
- Binding: binding.post,
1012
- Location: opts.sloUrl,
1013
- },
1014
- ]
1015
- : undefined,
1016
- });
1017
- return sp.getMetadata();
1018
- }
1019
-
1020
- /** @internal */
1021
- export function createEnterpriseSamlMetadataXml(opts: {
1022
- rootUrl: string;
1023
- source: EnterpriseSamlSource;
1024
- config: unknown;
1025
- }) {
1026
- return createServiceProviderMetadata(
1027
- getSamlServiceProviderOptions({
1028
- rootUrl: opts.rootUrl,
1029
- source: opts.source,
1030
- config: opts.config,
1031
- }),
1032
- );
1033
- }
1034
-
1035
- /** @internal */
1036
- export function getSamlServiceProviderOptions(opts: {
1037
- rootUrl: string;
1038
- source: EnterpriseSamlSource;
1039
- config: unknown;
1040
- overrides?: {
1041
- entityId?: string;
1042
- acsUrl?: string;
1043
- sloUrl?: string;
1044
- };
1045
- relayState?: string;
1046
- }) {
1047
- const saml = getSamlConfig(opts.config);
1048
- const sp = asRecord(saml.sp) ?? {};
1049
- const urls = getEnterpriseSamlUrls({
1050
- rootUrl: opts.rootUrl,
1051
- source: opts.source,
1052
- });
1053
- return {
1054
- entityId: opts.overrides?.entityId ?? sp.entityId ?? urls.metadataUrl,
1055
- acsUrl: opts.overrides?.acsUrl ?? sp.acsUrl ?? urls.acsUrl,
1056
- sloUrl: opts.overrides?.sloUrl ?? sp.sloUrl ?? urls.sloUrl,
1057
- relayState: opts.relayState,
1058
- authnRequestsSigned: saml.signAuthnRequests,
1059
- signingCert: sp.signingCert,
1060
- encryptCert: sp.encryptCert,
1061
- privateKey: sp.privateKey,
1062
- privateKeyPass: sp.privateKeyPass,
1063
- encPrivateKey: sp.encPrivateKey,
1064
- encPrivateKeyPass: sp.encPrivateKeyPass,
1065
- };
1066
- }
1067
-
1068
- /** @internal */
1069
- export function createSamlServiceProvider(opts: {
1070
- entityId: string;
1071
- acsUrl: string;
1072
- sloUrl?: string;
1073
- relayState?: string;
1074
- authnRequestsSigned?: boolean;
1075
- signingCert?: string | string[];
1076
- encryptCert?: string | string[];
1077
- privateKey?: string;
1078
- privateKeyPass?: string;
1079
- encPrivateKey?: string;
1080
- encPrivateKeyPass?: string;
1081
- }) {
1082
- const binding = Constants.namespace.binding;
1083
- return ServiceProvider({
1084
- entityID: opts.entityId,
1085
- relayState: opts.relayState ?? "",
1086
- authnRequestsSigned: opts.authnRequestsSigned ?? false,
1087
- privateKey: opts.privateKey,
1088
- privateKeyPass: opts.privateKeyPass,
1089
- signingCert: opts.signingCert,
1090
- encryptCert: opts.encryptCert,
1091
- encPrivateKey: opts.encPrivateKey,
1092
- encPrivateKeyPass: opts.encPrivateKeyPass,
1093
- assertionConsumerService: [
1094
- {
1095
- Binding: binding.post,
1096
- Location: opts.acsUrl,
1097
- },
1098
- ],
1099
- singleLogoutService: opts.sloUrl
1100
- ? [
1101
- { Binding: binding.redirect, Location: opts.sloUrl },
1102
- { Binding: binding.post, Location: opts.sloUrl },
1103
- ]
1104
- : undefined,
1105
- });
1106
- }
1107
-
1108
- /** @internal */
1109
- export function createEnterpriseSamlRuntime(opts: {
1110
- rootUrl: string;
1111
- source: EnterpriseSamlSource;
1112
- config: unknown;
1113
- relayState?: string;
1114
- overrides?: {
1115
- entityId?: string;
1116
- acsUrl?: string;
1117
- sloUrl?: string;
1118
- };
1119
- }) {
1120
- const saml = getSamlConfig(opts.config);
1121
- const spOptions = getSamlServiceProviderOptions({
1122
- rootUrl: opts.rootUrl,
1123
- source: opts.source,
1124
- config: opts.config,
1125
- relayState: opts.relayState,
1126
- overrides: opts.overrides,
1127
- });
1128
- if (typeof saml.idp?.metadataXml !== "string") {
1129
- throw new Error("SAML IdP metadata is missing.");
1130
- }
1131
- return {
1132
- saml,
1133
- sp: createSamlServiceProvider(spOptions),
1134
- idp: IdentityProvider({ metadata: saml.idp.metadataXml }),
1135
- urls: getEnterpriseSamlUrls({ rootUrl: opts.rootUrl, source: opts.source }),
1136
- };
1137
- }
1138
-
1139
- /** @internal */
1140
- export function createEnterpriseSamlSignInRequest(opts: {
1141
- rootUrl: string;
1142
- source: EnterpriseSamlSource;
1143
- config: unknown;
1144
- state: string;
1145
- signature: string;
1146
- redirectTo?: string;
1147
- }) {
1148
- const runtime = createEnterpriseSamlRuntime({
1149
- rootUrl: opts.rootUrl,
1150
- source: opts.source,
1151
- config: opts.config,
1152
- });
1153
- const binding = runtime.saml.idp.sso?.redirect ? "redirect" : "post";
1154
- const loginRequest = runtime.sp.createLoginRequest(
1155
- runtime.idp,
1156
- binding as any,
1157
- ) as any;
1158
- const relayState = encodeEnterpriseSamlRelayState({
1159
- source: opts.source,
1160
- signature: opts.signature,
1161
- requestId: loginRequest.id,
1162
- state: opts.state,
1163
- redirectTo: opts.redirectTo,
1164
- });
1165
- return {
1166
- requestId: loginRequest.id as string,
1167
- binding,
1168
- relayState,
1169
- redirectUrl:
1170
- binding === "redirect"
1171
- ? (() => {
1172
- const redirectUrl = new URL(loginRequest.context);
1173
- redirectUrl.searchParams.set("RelayState", relayState);
1174
- return redirectUrl.toString();
1175
- })()
1176
- : undefined,
1177
- post:
1178
- binding === "post"
1179
- ? {
1180
- endpoint: loginRequest.entityEndpoint as string,
1181
- value: loginRequest.context as string,
1182
- }
1183
- : undefined,
1184
- };
1185
- }
1186
-
1187
- /** @internal */
1188
- export async function parseEnterpriseSamlLoginResponse(opts: {
1189
- request: Request;
1190
- rootUrl: string;
1191
- source: EnterpriseSamlSource;
1192
- config: unknown;
1193
- }) {
1194
- ensureSamlifyValidator();
1195
- const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);
1196
- const runtime = createEnterpriseSamlRuntime({
1197
- rootUrl: opts.rootUrl,
1198
- source: opts.source,
1199
- config: opts.config,
1200
- });
1201
- const parsed = (await runtime.sp.parseLoginResponse(
1202
- runtime.idp as any,
1203
- httpRequest.binding as any,
1204
- {
1205
- query: httpRequest.query,
1206
- body: httpRequest.body,
1207
- },
1208
- )) as any;
1209
- // Check for deprecated SAML algorithms and warn
1210
- warnDeprecatedSamlAlgorithms(parsed);
1211
-
1212
- return {
1213
- ...httpRequest,
1214
- runtime,
1215
- parsed,
1216
- relayState: decodeEnterpriseSamlRelayStateOrThrow(
1217
- httpRequest.relayState ?? null,
1218
- ),
1219
- };
1220
- }
1221
-
1222
- const DEPRECATED_SAML_ALGORITHMS = new Set([
1223
- // Signature algorithms
1224
- "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
1225
- "http://www.w3.org/2000/09/xmldsig#dsa-sha1",
1226
- // Digest algorithms
1227
- "http://www.w3.org/2000/09/xmldsig#sha1",
1228
- // Key encryption
1229
- "http://www.w3.org/2001/04/xmlenc#rsa-1_5",
1230
- // Data encryption
1231
- "http://www.w3.org/2001/04/xmlenc#tripledes-cbc",
1232
- ]);
1233
-
1234
- /**
1235
- * Warn when the SAML response uses deprecated cryptographic algorithms
1236
- * (SHA-1, RSA 1.5, 3DES). These are still accepted for compatibility
1237
- * but should be flagged.
1238
- */
1239
- function warnDeprecatedSamlAlgorithms(parsed: any) {
1240
- try {
1241
- const sigAlg =
1242
- parsed?.extract?.signature?.signatureAlgorithm ??
1243
- parsed?.extract?.response?.signatureAlgorithm;
1244
- const digestAlg = parsed?.extract?.signature?.digestAlgorithm;
1245
-
1246
- if (sigAlg && DEPRECATED_SAML_ALGORITHMS.has(sigAlg)) {
1247
- console.warn(
1248
- `[convex-auth] SAML response uses deprecated signature algorithm: ${sigAlg}. ` +
1249
- `Consider upgrading your IdP to use RSA-SHA256 or stronger.`,
1250
- );
1251
- }
1252
- if (digestAlg && DEPRECATED_SAML_ALGORITHMS.has(digestAlg)) {
1253
- console.warn(
1254
- `[convex-auth] SAML response uses deprecated digest algorithm: ${digestAlg}. ` +
1255
- `Consider upgrading your IdP to use SHA-256 or stronger.`,
1256
- );
1257
- }
1258
- } catch {
1259
- // Non-critical — don't break auth flow for algorithm check failures
1260
- }
1261
- }
1262
-
1263
- /** @internal */
1264
- export function validateEnterpriseSamlLoginRelayState(opts: {
1265
- relayState: EnterpriseSamlRelayState;
1266
- source: EnterpriseSamlSource;
1267
- inResponseTo?: string;
1268
- }) {
1269
- if (
1270
- opts.relayState.source.kind !== opts.source.kind ||
1271
- opts.relayState.source.id !== opts.source.id ||
1272
- opts.relayState.requestId !== opts.inResponseTo
1273
- ) {
1274
- throw new Error("SAML RelayState did not match the pending login request.");
1275
- }
1276
- }
1277
-
1278
- /** @internal */
1279
- export async function parseEnterpriseSamlLogoutMessage(opts: {
1280
- request: Request;
1281
- rootUrl: string;
1282
- source: EnterpriseSamlSource;
1283
- config: unknown;
1284
- }) {
1285
- ensureSamlifyValidator();
1286
- const httpRequest = await readEnterpriseSamlHttpRequest(opts.request);
1287
- const runtime = createEnterpriseSamlRuntime({
1288
- rootUrl: opts.rootUrl,
1289
- source: opts.source,
1290
- config: opts.config,
1291
- relayState: httpRequest.relayState,
1292
- });
1293
- const parsedRequest = httpRequest.hasSamlRequest
1294
- ? ((await runtime.sp.parseLogoutRequest(
1295
- runtime.idp as any,
1296
- httpRequest.binding as any,
1297
- {
1298
- query: httpRequest.query,
1299
- body: httpRequest.body,
1300
- },
1301
- )) as any)
1302
- : undefined;
1303
- return {
1304
- ...httpRequest,
1305
- runtime,
1306
- parsedRequest,
1307
- };
1308
- }
1309
-
1310
- /** @internal */
1311
- export async function createEnterpriseOidcRuntime(opts: {
1312
- rootUrl: string;
1313
- enterpriseId: string;
1314
- oidc: Record<string, any>;
1315
- }) {
1316
- const providerId = enterpriseOidcProviderId(opts.enterpriseId);
1317
- const urls = getEnterpriseOidcUrls({
1318
- rootUrl: opts.rootUrl,
1319
- enterpriseId: opts.enterpriseId,
1320
- });
1321
- const { provider, oauthConfig } = await createEnterpriseOidcProvider(
1322
- opts.oidc,
1323
- urls.callbackUrl,
1324
- );
1325
- return {
1326
- oidc: opts.oidc,
1327
- providerId,
1328
- provider,
1329
- oauthConfig,
1330
- ...urls,
1331
- };
1332
- }
1333
-
1334
- /** @internal */
1335
- export function profileFromSamlExtract(
1336
- extract: any,
1337
- mapping?: SAMLAttributeMapping,
1338
- ) {
1339
- const attributes =
1340
- typeof extract?.attributes === "object" && extract.attributes !== null
1341
- ? (extract.attributes as Record<string, unknown>)
1342
- : {};
1343
- const resolveFirst = (...keys: Array<string | undefined>) => {
1344
- for (const key of keys) {
1345
- if (!key) {
1346
- continue;
1347
- }
1348
- const attribute = attributes[key];
1349
- const value = Array.isArray(attribute) ? attribute[0] : attribute;
1350
- if (value !== undefined) {
1351
- return value;
1352
- }
1353
- }
1354
- return undefined;
1355
- };
1356
- const fieldResolvers = {
1357
- email: () => resolveFirst(mapping?.email),
1358
- name: () =>
1359
- resolveFirst(mapping?.name) ??
1360
- ([resolveFirst(mapping?.firstName), resolveFirst(mapping?.lastName)]
1361
- .filter(Boolean)
1362
- .join(" ") ||
1363
- undefined),
1364
- subject: () =>
1365
- resolveFirst(mapping?.subject) ?? (extract?.nameID as string | undefined),
1366
- } as const;
1367
- const subject = fieldResolvers.subject() as string | undefined;
1368
- if (subject === undefined) {
1369
- throw new Error(
1370
- "SAML profile is missing a subject. Configure `attributeMapping.subject` or ensure the assertion includes a NameID.",
1371
- );
1372
- }
1373
- const email = fieldResolvers.email() as string | undefined;
1374
- const name = fieldResolvers.name() as string | undefined;
1375
- return {
1376
- id: subject,
1377
- email,
1378
- emailVerified: typeof email === "string" ? true : undefined,
1379
- name,
1380
- samlAttributes: attributes,
1381
- samlSessionIndex: extract?.sessionIndex?.SessionIndex as string | undefined,
1382
- };
1383
- }
1384
-
1385
- /** @internal */
1386
- export function parseScimPath(pathname: string) {
1387
- const parts = pathname.split("/").filter(Boolean);
1388
- const [api, auth, sso, enterpriseId, protocol, version, ...rest] = parts;
1389
-
1390
- if (
1391
- api !== "api" ||
1392
- auth !== "auth" ||
1393
- sso !== "sso" ||
1394
- !enterpriseId ||
1395
- enterpriseId === "setup" ||
1396
- protocol !== "scim" ||
1397
- version !== "v2"
1398
- ) {
1399
- return {
1400
- enterpriseId: "",
1401
- resource: "",
1402
- resourceId: undefined,
1403
- };
1404
- }
1405
-
1406
- return {
1407
- enterpriseId,
1408
- resource: rest[0] ?? "",
1409
- resourceId: rest[1],
1410
- };
1411
- }
1412
-
1413
- /** @internal */
1414
- export function parseScimListRequest(url: URL): ScimListRequest {
1415
- const startIndex = Math.max(
1416
- 1,
1417
- Number(url.searchParams.get("startIndex") ?? "1"),
1418
- );
1419
- const count = Math.min(
1420
- 100,
1421
- Math.max(1, Number(url.searchParams.get("count") ?? "100")),
1422
- );
1423
- const filterParam = url.searchParams.get("filter");
1424
- const filter = filterParam
1425
- ? (() => {
1426
- const match = filterParam.match(/^([A-Za-z0-9_.]+)\s+eq\s+"([^"]+)"$/);
1427
- if (!match) {
1428
- throw new Error("Unsupported SCIM filter.");
1429
- }
1430
- return { attribute: match[1]!, value: match[2]! };
1431
- })()
1432
- : undefined;
1433
- return { startIndex, count, filter };
1434
- }
1435
-
1436
- /** @internal */
1437
- export function scimJson(data: unknown, status = 200, headers?: HeadersInit) {
1438
- const responseHeaders = new Headers({
1439
- "Content-Type": "application/scim+json",
1440
- });
1441
- if (headers) {
1442
- new Headers(headers).forEach((value, key) => {
1443
- responseHeaders.set(key, value);
1444
- });
1445
- }
1446
- return new Response(JSON.stringify(data), {
1447
- status,
1448
- headers: responseHeaders,
1449
- });
1450
- }
1451
-
1452
- /** @internal */
1453
- export function scimError(status: number, scimType: string, detail: string) {
1454
- return scimJson(
1455
- {
1456
- schemas: ["urn:ietf:params:scim:api:messages:2.0:Error"],
1457
- status: String(status),
1458
- scimType,
1459
- detail,
1460
- },
1461
- status,
1462
- );
1463
- }
1464
-
1465
- /** @internal */
1466
- export function serializeScimUser(args: {
1467
- id: string;
1468
- user: Record<string, any>;
1469
- externalId?: string;
1470
- active?: boolean;
1471
- location?: string;
1472
- }) {
1473
- return {
1474
- schemas: [SCIM_USER_SCHEMA_ID],
1475
- id: args.id,
1476
- externalId: args.externalId,
1477
- meta: {
1478
- resourceType: "User",
1479
- location: args.location,
1480
- },
1481
- userName: args.user.email ?? args.user.phone ?? args.user.name ?? args.id,
1482
- active: args.active ?? true,
1483
- name:
1484
- args.user.name !== undefined ? { formatted: args.user.name } : undefined,
1485
- emails:
1486
- typeof args.user.email === "string"
1487
- ? [{ value: args.user.email, primary: true }]
1488
- : undefined,
1489
- phoneNumbers:
1490
- typeof args.user.phone === "string"
1491
- ? [{ value: args.user.phone, primary: true }]
1492
- : undefined,
1493
- displayName: args.user.name,
1494
- };
1495
- }
1496
-
1497
- /** @internal */
1498
- export function serializeScimGroup(args: {
1499
- id: string;
1500
- group: Record<string, any>;
1501
- externalId?: string;
1502
- members?: Array<{ value: string; display?: string }>;
1503
- location?: string;
1504
- }) {
1505
- return {
1506
- schemas: [SCIM_GROUP_SCHEMA_ID],
1507
- id: args.id,
1508
- externalId: args.externalId,
1509
- meta: {
1510
- resourceType: "Group",
1511
- location: args.location,
1512
- },
1513
- displayName: args.group.name ?? args.id,
1514
- members: args.members ?? [],
1515
- };
1516
- }