@tempoxyz/id 0.0.0-pr-187.sha-eed04d3

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 (712) hide show
  1. package/README.md +81 -0
  2. package/dist/App.d.ts +20 -0
  3. package/dist/App.d.ts.map +1 -0
  4. package/dist/App.js +26 -0
  5. package/dist/App.js.map +1 -0
  6. package/dist/app/client/.assetsignore +2 -0
  7. package/dist/app/client/assets/BRLA-CZvVDNYr.png +0 -0
  8. package/dist/app/client/assets/CHFAU-DCedc4aH.png +0 -0
  9. package/dist/app/client/assets/DLUSD-BnRXm3EI.png +0 -0
  10. package/dist/app/client/assets/PRIME-CiFp2-3e.svg +14 -0
  11. package/dist/app/client/assets/Pilat-Bold-C1t1eIrF.woff2 +0 -0
  12. package/dist/app/client/assets/Pilat-Book-CbS8ofT8.woff2 +0 -0
  13. package/dist/app/client/assets/Pilat-Demi-CADSbPAt.woff2 +0 -0
  14. package/dist/app/client/assets/Pilat-Regular-Bx8vzE7q.woff2 +0 -0
  15. package/dist/app/client/assets/USD1-COVcO_PB.svg +30 -0
  16. package/dist/app/client/assets/YLDS-CRh8Zw0g.svg +3 -0
  17. package/dist/app/client/assets/ccip-Cq1mMiVl.js +1 -0
  18. package/dist/app/client/assets/frxUSD-n9pTpinR.svg +1 -0
  19. package/dist/app/client/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  20. package/dist/app/client/assets/index-B7YJVP00.css +1 -0
  21. package/dist/app/client/assets/index-UXTxXCLk.js +55 -0
  22. package/dist/app/client/assets/pathUSD-BvaO6ooS.svg +25 -0
  23. package/dist/app/client/assets/reset-co-CHrff.css +1 -0
  24. package/dist/app/client/assets/wYLDS-CaCKDrO-.svg +10 -0
  25. package/dist/app/db/auth.d.ts +3207 -0
  26. package/dist/app/db/auth.d.ts.map +1 -0
  27. package/dist/app/db/auth.js +330 -0
  28. package/dist/app/db/auth.js.map +1 -0
  29. package/dist/app/db/schema.d.ts +932 -0
  30. package/dist/app/db/schema.d.ts.map +1 -0
  31. package/dist/app/db/schema.js +171 -0
  32. package/dist/app/db/schema.js.map +1 -0
  33. package/dist/app/server/.vite/manifest.json +489 -0
  34. package/dist/app/server/assets/BRLA-CZvVDNYr.png +0 -0
  35. package/dist/app/server/assets/CHFAU-DCedc4aH.png +0 -0
  36. package/dist/app/server/assets/DLUSD-BnRXm3EI.png +0 -0
  37. package/dist/app/server/assets/PRIME-CiFp2-3e.svg +14 -0
  38. package/dist/app/server/assets/Pilat-Bold-C1t1eIrF.woff2 +0 -0
  39. package/dist/app/server/assets/Pilat-Book-CbS8ofT8.woff2 +0 -0
  40. package/dist/app/server/assets/Pilat-Demi-CADSbPAt.woff2 +0 -0
  41. package/dist/app/server/assets/Pilat-Regular-Bx8vzE7q.woff2 +0 -0
  42. package/dist/app/server/assets/USD1-COVcO_PB.svg +30 -0
  43. package/dist/app/server/assets/YLDS-CRh8Zw0g.svg +3 -0
  44. package/dist/app/server/assets/_tanstack-start-manifest_v-DjQNeRZp.js +1 -0
  45. package/dist/app/server/assets/arktype-aI7TBD0R-D1R3Byo5.js +2 -0
  46. package/dist/app/server/assets/arktype-aI7TBD0R-D1R3Byo5.js.map +1 -0
  47. package/dist/app/server/assets/authorize-C95F-9E1-CbESJKGU.js +31 -0
  48. package/dist/app/server/assets/authorize-C95F-9E1-CbESJKGU.js.map +1 -0
  49. package/dist/app/server/assets/bun-sqlite-dialect-DoOVo4ja-CtyBkVMb.js +2 -0
  50. package/dist/app/server/assets/bun-sqlite-dialect-DoOVo4ja-CtyBkVMb.js.map +1 -0
  51. package/dist/app/server/assets/call-UQamaCKN.js +32 -0
  52. package/dist/app/server/assets/call-UQamaCKN.js.map +1 -0
  53. package/dist/app/server/assets/ccip-CbNKFO4n.js +2 -0
  54. package/dist/app/server/assets/ccip-CbNKFO4n.js.map +1 -0
  55. package/dist/app/server/assets/compiled-query-Ds2NVcFT.js +2 -0
  56. package/dist/app/server/assets/compiled-query-Ds2NVcFT.js.map +1 -0
  57. package/dist/app/server/assets/convert-BIC8n32x.js +2 -0
  58. package/dist/app/server/assets/convert-BIC8n32x.js.map +1 -0
  59. package/dist/app/server/assets/cookie-CS900nSP.css +1 -0
  60. package/dist/app/server/assets/cookie-Cce9CrIP.js +10 -0
  61. package/dist/app/server/assets/cookie-Cce9CrIP.js.map +1 -0
  62. package/dist/app/server/assets/core-B1HCXUmx.js +56 -0
  63. package/dist/app/server/assets/core-B1HCXUmx.js.map +1 -0
  64. package/dist/app/server/assets/d1-sqlite-dialect-m0f01-4z-Cv-csE1u.js +2 -0
  65. package/dist/app/server/assets/d1-sqlite-dialect-m0f01-4z-Cv-csE1u.js.map +1 -0
  66. package/dist/app/server/assets/default-u_dwuiYb-BJJ9PwCi.js +2 -0
  67. package/dist/app/server/assets/default-u_dwuiYb-BJJ9PwCi.js.map +1 -0
  68. package/dist/app/server/assets/dist-BxSwugSY.js +1 -0
  69. package/dist/app/server/assets/dist-CZGFhmQd.js +2 -0
  70. package/dist/app/server/assets/dist-CZGFhmQd.js.map +1 -0
  71. package/dist/app/server/assets/dist-Dg3PQqiW.js +15 -0
  72. package/dist/app/server/assets/dist-Dg3PQqiW.js.map +1 -0
  73. package/dist/app/server/assets/effect-QlVUlMFu-2tSPDT2b.js +2 -0
  74. package/dist/app/server/assets/effect-QlVUlMFu-2tSPDT2b.js.map +1 -0
  75. package/dist/app/server/assets/empty-plugin-adapters-BaGcmrjC.js +2 -0
  76. package/dist/app/server/assets/empty-plugin-adapters-BaGcmrjC.js.map +1 -0
  77. package/dist/app/server/assets/factory-ipHlJXZY.js +5 -0
  78. package/dist/app/server/assets/factory-ipHlJXZY.js.map +1 -0
  79. package/dist/app/server/assets/frxUSD-n9pTpinR.svg +1 -0
  80. package/dist/app/server/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  81. package/dist/app/server/assets/get-id-field-B0x109VU.js +6 -0
  82. package/dist/app/server/assets/get-id-field-B0x109VU.js.map +1 -0
  83. package/dist/app/server/assets/index-CLddUTqr-xpSuLpBt.js +2 -0
  84. package/dist/app/server/assets/index-CLddUTqr-xpSuLpBt.js.map +1 -0
  85. package/dist/app/server/assets/index-DZEfthgZ-8Uq5s5Pt.js +2 -0
  86. package/dist/app/server/assets/index-DZEfthgZ-8Uq5s5Pt.js.map +1 -0
  87. package/dist/app/server/assets/kysely-adapter-MkHAI9Jk.js +1 -0
  88. package/dist/app/server/assets/node-sqlite-dialect-BowWLXvK.js +2 -0
  89. package/dist/app/server/assets/node-sqlite-dialect-BowWLXvK.js.map +1 -0
  90. package/dist/app/server/assets/pathUSD-BvaO6ooS.svg +25 -0
  91. package/dist/app/server/assets/reset-co-CHrff.css +1 -0
  92. package/dist/app/server/assets/rolldown-runtime-B0Z9INg1.js +1 -0
  93. package/dist/app/server/assets/router-CcbyBCI4.css +1 -0
  94. package/dist/app/server/assets/router-xjZtEeaI.js +7 -0
  95. package/dist/app/server/assets/router-xjZtEeaI.js.map +1 -0
  96. package/dist/app/server/assets/schema-check-Cf7QOZZD.js +9 -0
  97. package/dist/app/server/assets/schema-check-Cf7QOZZD.js.map +1 -0
  98. package/dist/app/server/assets/schemas-C2lCYmR5.js +2 -0
  99. package/dist/app/server/assets/schemas-C2lCYmR5.js.map +1 -0
  100. package/dist/app/server/assets/secp256k1-buM5g4AL.js +2 -0
  101. package/dist/app/server/assets/secp256k1-buM5g4AL.js.map +1 -0
  102. package/dist/app/server/assets/sqlite-adapter-C_LAV4e3.js +2 -0
  103. package/dist/app/server/assets/sqlite-adapter-C_LAV4e3.js.map +1 -0
  104. package/dist/app/server/assets/sqlite-introspector-DSCAP82F-Crx7Ny14.js +6 -0
  105. package/dist/app/server/assets/sqlite-introspector-DSCAP82F-Crx7Ny14.js.map +1 -0
  106. package/dist/app/server/assets/standard-json-Cg1N07hM.js +2 -0
  107. package/dist/app/server/assets/standard-json-Cg1N07hM.js.map +1 -0
  108. package/dist/app/server/assets/standard-json-DY77efq0.js +2 -0
  109. package/dist/app/server/assets/standard-json-DY77efq0.js.map +1 -0
  110. package/dist/app/server/assets/standard-json-ig2t0wRt.js +2 -0
  111. package/dist/app/server/assets/standard-json-ig2t0wRt.js.map +1 -0
  112. package/dist/app/server/assets/standard-json-t-LNckA6.js +2 -0
  113. package/dist/app/server/assets/standard-json-t-LNckA6.js.map +1 -0
  114. package/dist/app/server/assets/standard-openapi-CONmVRl8.js +2 -0
  115. package/dist/app/server/assets/standard-openapi-CONmVRl8.js.map +1 -0
  116. package/dist/app/server/assets/start-C63VetBp.js +2 -0
  117. package/dist/app/server/assets/start-C63VetBp.js.map +1 -0
  118. package/dist/app/server/assets/sury-CWZTCd75-xt1mWc9f.js +2 -0
  119. package/dist/app/server/assets/sury-CWZTCd75-xt1mWc9f.js.map +1 -0
  120. package/dist/app/server/assets/typebox-Dei93FPO-D_lCVdAz.js +2 -0
  121. package/dist/app/server/assets/typebox-Dei93FPO-D_lCVdAz.js.map +1 -0
  122. package/dist/app/server/assets/utils-C4ZXZIZW.js +2 -0
  123. package/dist/app/server/assets/utils-C4ZXZIZW.js.map +1 -0
  124. package/dist/app/server/assets/valibot--1zFm7rT-NSoY2xTp.js +2 -0
  125. package/dist/app/server/assets/valibot--1zFm7rT-NSoY2xTp.js.map +1 -0
  126. package/dist/app/server/assets/valibot-D_HTw1Gn-CJwWeOUE.js +2 -0
  127. package/dist/app/server/assets/valibot-D_HTw1Gn-CJwWeOUE.js.map +1 -0
  128. package/dist/app/server/assets/wYLDS-CaCKDrO-.svg +10 -0
  129. package/dist/app/server/assets/zod-Bwrt9trS-CV38yPnQ.js +2 -0
  130. package/dist/app/server/assets/zod-Bwrt9trS-CV38yPnQ.js.map +1 -0
  131. package/dist/app/server/assets/zod-DSgpEGAE-BvlnBE2L.js +2 -0
  132. package/dist/app/server/assets/zod-DSgpEGAE-BvlnBE2L.js.map +1 -0
  133. package/dist/app/server/index.js +574 -0
  134. package/dist/app/server/index.js.map +1 -0
  135. package/dist/app/server/wrangler.json +1 -0
  136. package/dist/cli/Admin.d.ts +38 -0
  137. package/dist/cli/Admin.d.ts.map +1 -0
  138. package/dist/cli/Admin.js +62 -0
  139. package/dist/cli/Admin.js.map +1 -0
  140. package/dist/cli/Apps.d.ts +9 -0
  141. package/dist/cli/Apps.d.ts.map +1 -0
  142. package/dist/cli/Apps.js +36 -0
  143. package/dist/cli/Apps.js.map +1 -0
  144. package/dist/cli/Cli.d.ts +14 -0
  145. package/dist/cli/Cli.d.ts.map +1 -0
  146. package/dist/cli/Cli.js +27 -0
  147. package/dist/cli/Cli.js.map +1 -0
  148. package/dist/cli/Project.d.ts +20 -0
  149. package/dist/cli/Project.d.ts.map +1 -0
  150. package/dist/cli/Project.js +88 -0
  151. package/dist/cli/Project.js.map +1 -0
  152. package/dist/cli/Realms.d.ts +24 -0
  153. package/dist/cli/Realms.d.ts.map +1 -0
  154. package/dist/cli/Realms.js +49 -0
  155. package/dist/cli/Realms.js.map +1 -0
  156. package/dist/cli/Session.d.ts +85 -0
  157. package/dist/cli/Session.d.ts.map +1 -0
  158. package/dist/cli/Session.js +178 -0
  159. package/dist/cli/Session.js.map +1 -0
  160. package/dist/cli/User.d.ts +29 -0
  161. package/dist/cli/User.d.ts.map +1 -0
  162. package/dist/cli/User.js +39 -0
  163. package/dist/cli/User.js.map +1 -0
  164. package/dist/cli/V1.d.ts +35 -0
  165. package/dist/cli/V1.d.ts.map +1 -0
  166. package/dist/cli/V1.js +41 -0
  167. package/dist/cli/V1.js.map +1 -0
  168. package/dist/cli/bin.d.ts +3 -0
  169. package/dist/cli/bin.d.ts.map +1 -0
  170. package/dist/cli/bin.js +46 -0
  171. package/dist/cli/bin.js.map +1 -0
  172. package/dist/cli/commands/admin/apps/create.d.ts +23 -0
  173. package/dist/cli/commands/admin/apps/create.d.ts.map +1 -0
  174. package/dist/cli/commands/admin/apps/create.js +89 -0
  175. package/dist/cli/commands/admin/apps/create.js.map +1 -0
  176. package/dist/cli/commands/admin/apps/delete.d.ts +14 -0
  177. package/dist/cli/commands/admin/apps/delete.d.ts.map +1 -0
  178. package/dist/cli/commands/admin/apps/delete.js +41 -0
  179. package/dist/cli/commands/admin/apps/delete.js.map +1 -0
  180. package/dist/cli/commands/admin/apps/index.d.ts +5 -0
  181. package/dist/cli/commands/admin/apps/index.d.ts.map +1 -0
  182. package/dist/cli/commands/admin/apps/index.js +14 -0
  183. package/dist/cli/commands/admin/apps/index.js.map +1 -0
  184. package/dist/cli/commands/admin/apps/list.d.ts +9 -0
  185. package/dist/cli/commands/admin/apps/list.d.ts.map +1 -0
  186. package/dist/cli/commands/admin/apps/list.js +26 -0
  187. package/dist/cli/commands/admin/apps/list.js.map +1 -0
  188. package/dist/cli/commands/admin/apps/rotate-secret.d.ts +16 -0
  189. package/dist/cli/commands/admin/apps/rotate-secret.d.ts.map +1 -0
  190. package/dist/cli/commands/admin/apps/rotate-secret.js +47 -0
  191. package/dist/cli/commands/admin/apps/rotate-secret.js.map +1 -0
  192. package/dist/cli/commands/admin/apps/update.d.ts +20 -0
  193. package/dist/cli/commands/admin/apps/update.d.ts.map +1 -0
  194. package/dist/cli/commands/admin/apps/update.js +76 -0
  195. package/dist/cli/commands/admin/apps/update.js.map +1 -0
  196. package/dist/cli/commands/admin/index.d.ts +5 -0
  197. package/dist/cli/commands/admin/index.d.ts.map +1 -0
  198. package/dist/cli/commands/admin/index.js +13 -0
  199. package/dist/cli/commands/admin/index.js.map +1 -0
  200. package/dist/cli/commands/admin/migrate.d.ts +9 -0
  201. package/dist/cli/commands/admin/migrate.d.ts.map +1 -0
  202. package/dist/cli/commands/admin/migrate.js +17 -0
  203. package/dist/cli/commands/admin/migrate.js.map +1 -0
  204. package/dist/cli/commands/admin/realms/add.d.ts +12 -0
  205. package/dist/cli/commands/admin/realms/add.d.ts.map +1 -0
  206. package/dist/cli/commands/admin/realms/add.js +42 -0
  207. package/dist/cli/commands/admin/realms/add.js.map +1 -0
  208. package/dist/cli/commands/admin/realms/index.d.ts +5 -0
  209. package/dist/cli/commands/admin/realms/index.d.ts.map +1 -0
  210. package/dist/cli/commands/admin/realms/index.js +13 -0
  211. package/dist/cli/commands/admin/realms/index.js.map +1 -0
  212. package/dist/cli/commands/admin/realms/list.d.ts +13 -0
  213. package/dist/cli/commands/admin/realms/list.d.ts.map +1 -0
  214. package/dist/cli/commands/admin/realms/list.js +36 -0
  215. package/dist/cli/commands/admin/realms/list.js.map +1 -0
  216. package/dist/cli/commands/admin/realms/passkeys/index.d.ts +5 -0
  217. package/dist/cli/commands/admin/realms/passkeys/index.d.ts.map +1 -0
  218. package/dist/cli/commands/admin/realms/passkeys/index.js +13 -0
  219. package/dist/cli/commands/admin/realms/passkeys/index.js.map +1 -0
  220. package/dist/cli/commands/admin/realms/passkeys/set.d.ts +16 -0
  221. package/dist/cli/commands/admin/realms/passkeys/set.d.ts.map +1 -0
  222. package/dist/cli/commands/admin/realms/passkeys/set.js +46 -0
  223. package/dist/cli/commands/admin/realms/passkeys/set.js.map +1 -0
  224. package/dist/cli/commands/admin/realms/privy/index.d.ts +5 -0
  225. package/dist/cli/commands/admin/realms/privy/index.d.ts.map +1 -0
  226. package/dist/cli/commands/admin/realms/privy/index.js +13 -0
  227. package/dist/cli/commands/admin/realms/privy/index.js.map +1 -0
  228. package/dist/cli/commands/admin/realms/privy/set.d.ts +18 -0
  229. package/dist/cli/commands/admin/realms/privy/set.d.ts.map +1 -0
  230. package/dist/cli/commands/admin/realms/privy/set.js +66 -0
  231. package/dist/cli/commands/admin/realms/privy/set.js.map +1 -0
  232. package/dist/cli/commands/admin/realms/remove.d.ts +11 -0
  233. package/dist/cli/commands/admin/realms/remove.d.ts.map +1 -0
  234. package/dist/cli/commands/admin/realms/remove.js +35 -0
  235. package/dist/cli/commands/admin/realms/remove.js.map +1 -0
  236. package/dist/cli/commands/admin/realms/secp256k1/disable.d.ts +15 -0
  237. package/dist/cli/commands/admin/realms/secp256k1/disable.d.ts.map +1 -0
  238. package/dist/cli/commands/admin/realms/secp256k1/disable.js +42 -0
  239. package/dist/cli/commands/admin/realms/secp256k1/disable.js.map +1 -0
  240. package/dist/cli/commands/admin/realms/secp256k1/enable.d.ts +15 -0
  241. package/dist/cli/commands/admin/realms/secp256k1/enable.d.ts.map +1 -0
  242. package/dist/cli/commands/admin/realms/secp256k1/enable.js +42 -0
  243. package/dist/cli/commands/admin/realms/secp256k1/enable.js.map +1 -0
  244. package/dist/cli/commands/admin/realms/secp256k1/index.d.ts +9 -0
  245. package/dist/cli/commands/admin/realms/secp256k1/index.d.ts.map +1 -0
  246. package/dist/cli/commands/admin/realms/secp256k1/index.js +17 -0
  247. package/dist/cli/commands/admin/realms/secp256k1/index.js.map +1 -0
  248. package/dist/cli/commands/admin/realms/set.d.ts +16 -0
  249. package/dist/cli/commands/admin/realms/set.d.ts.map +1 -0
  250. package/dist/cli/commands/admin/realms/set.js +46 -0
  251. package/dist/cli/commands/admin/realms/set.js.map +1 -0
  252. package/dist/cli/commands/admin/rotate-key.d.ts +15 -0
  253. package/dist/cli/commands/admin/rotate-key.d.ts.map +1 -0
  254. package/dist/cli/commands/admin/rotate-key.js +46 -0
  255. package/dist/cli/commands/admin/rotate-key.js.map +1 -0
  256. package/dist/cli/commands/init.d.ts +11 -0
  257. package/dist/cli/commands/init.d.ts.map +1 -0
  258. package/dist/cli/commands/init.js +34 -0
  259. package/dist/cli/commands/init.js.map +1 -0
  260. package/dist/cli/commands/login.d.ts +12 -0
  261. package/dist/cli/commands/login.d.ts.map +1 -0
  262. package/dist/cli/commands/login.js +29 -0
  263. package/dist/cli/commands/login.js.map +1 -0
  264. package/dist/cli/commands/logout.d.ts +8 -0
  265. package/dist/cli/commands/logout.d.ts.map +1 -0
  266. package/dist/cli/commands/logout.js +19 -0
  267. package/dist/cli/commands/logout.js.map +1 -0
  268. package/dist/cli/commands/whoami.d.ts +12 -0
  269. package/dist/cli/commands/whoami.d.ts.map +1 -0
  270. package/dist/cli/commands/whoami.js +24 -0
  271. package/dist/cli/commands/whoami.js.map +1 -0
  272. package/dist/client/Actions.d.ts +101 -0
  273. package/dist/client/Actions.d.ts.map +1 -0
  274. package/dist/client/Actions.js +90 -0
  275. package/dist/client/Actions.js.map +1 -0
  276. package/dist/client/Auth.d.ts +161 -0
  277. package/dist/client/Auth.d.ts.map +1 -0
  278. package/dist/client/Auth.js +113 -0
  279. package/dist/client/Auth.js.map +1 -0
  280. package/dist/client/Authorization.d.ts +29 -0
  281. package/dist/client/Authorization.d.ts.map +1 -0
  282. package/dist/client/Authorization.js +12 -0
  283. package/dist/client/Authorization.js.map +1 -0
  284. package/dist/client/Decorator.d.ts +170 -0
  285. package/dist/client/Decorator.d.ts.map +1 -0
  286. package/dist/client/Decorator.js +227 -0
  287. package/dist/client/Decorator.js.map +1 -0
  288. package/dist/client/Key.d.ts +85 -0
  289. package/dist/client/Key.d.ts.map +1 -0
  290. package/dist/client/Key.js +83 -0
  291. package/dist/client/Key.js.map +1 -0
  292. package/dist/client/Pending.d.ts +39 -0
  293. package/dist/client/Pending.d.ts.map +1 -0
  294. package/dist/client/Pending.js +12 -0
  295. package/dist/client/Pending.js.map +1 -0
  296. package/dist/client/Permissions.d.ts +34 -0
  297. package/dist/client/Permissions.d.ts.map +1 -0
  298. package/dist/client/Permissions.js +16 -0
  299. package/dist/client/Permissions.js.map +1 -0
  300. package/dist/client/Provider.d.ts +16 -0
  301. package/dist/client/Provider.d.ts.map +1 -0
  302. package/dist/client/Provider.js +12 -0
  303. package/dist/client/Provider.js.map +1 -0
  304. package/dist/client/Step.d.ts +21 -0
  305. package/dist/client/Step.d.ts.map +1 -0
  306. package/dist/client/Step.js +16 -0
  307. package/dist/client/Step.js.map +1 -0
  308. package/dist/client/Storage.d.ts +27 -0
  309. package/dist/client/Storage.d.ts.map +1 -0
  310. package/dist/client/Storage.js +16 -0
  311. package/dist/client/Storage.js.map +1 -0
  312. package/dist/client/TempoAccount.d.ts +40 -0
  313. package/dist/client/TempoAccount.d.ts.map +1 -0
  314. package/dist/client/TempoAccount.js +16 -0
  315. package/dist/client/TempoAccount.js.map +1 -0
  316. package/dist/client/Tokens.d.ts +25 -0
  317. package/dist/client/Tokens.d.ts.map +1 -0
  318. package/dist/client/Tokens.js +12 -0
  319. package/dist/client/Tokens.js.map +1 -0
  320. package/dist/client/User.d.ts +21 -0
  321. package/dist/client/User.d.ts.map +1 -0
  322. package/dist/client/User.js +12 -0
  323. package/dist/client/User.js.map +1 -0
  324. package/dist/client/actions/account.d.ts +25 -0
  325. package/dist/client/actions/account.d.ts.map +1 -0
  326. package/dist/client/actions/account.js +34 -0
  327. package/dist/client/actions/account.js.map +1 -0
  328. package/dist/client/actions/device.d.ts +53 -0
  329. package/dist/client/actions/device.d.ts.map +1 -0
  330. package/dist/client/actions/device.js +79 -0
  331. package/dist/client/actions/device.js.map +1 -0
  332. package/dist/client/actions/email.d.ts +84 -0
  333. package/dist/client/actions/email.d.ts.map +1 -0
  334. package/dist/client/actions/email.js +63 -0
  335. package/dist/client/actions/email.js.map +1 -0
  336. package/dist/client/actions/oauth.d.ts +115 -0
  337. package/dist/client/actions/oauth.d.ts.map +1 -0
  338. package/dist/client/actions/oauth.js +105 -0
  339. package/dist/client/actions/oauth.js.map +1 -0
  340. package/dist/client/actions/passkey.d.ts +88 -0
  341. package/dist/client/actions/passkey.d.ts.map +1 -0
  342. package/dist/client/actions/passkey.js +60 -0
  343. package/dist/client/actions/passkey.js.map +1 -0
  344. package/dist/client/actions/provider.d.ts +49 -0
  345. package/dist/client/actions/provider.d.ts.map +1 -0
  346. package/dist/client/actions/provider.js +34 -0
  347. package/dist/client/actions/provider.js.map +1 -0
  348. package/dist/client/actions/realm.d.ts +24 -0
  349. package/dist/client/actions/realm.d.ts.map +1 -0
  350. package/dist/client/actions/realm.js +15 -0
  351. package/dist/client/actions/realm.js.map +1 -0
  352. package/dist/client/actions/redirect.d.ts +153 -0
  353. package/dist/client/actions/redirect.d.ts.map +1 -0
  354. package/dist/client/actions/redirect.js +236 -0
  355. package/dist/client/actions/redirect.js.map +1 -0
  356. package/dist/client/internal/accessKey.d.ts +21 -0
  357. package/dist/client/internal/accessKey.d.ts.map +1 -0
  358. package/dist/client/internal/accessKey.js +38 -0
  359. package/dist/client/internal/accessKey.js.map +1 -0
  360. package/dist/client/internal/account.d.ts +14 -0
  361. package/dist/client/internal/account.d.ts.map +1 -0
  362. package/dist/client/internal/account.js +3 -0
  363. package/dist/client/internal/account.js.map +1 -0
  364. package/dist/client/internal/api.d.ts +106 -0
  365. package/dist/client/internal/api.d.ts.map +1 -0
  366. package/dist/client/internal/api.js +129 -0
  367. package/dist/client/internal/api.js.map +1 -0
  368. package/dist/client/internal/headless.d.ts +33 -0
  369. package/dist/client/internal/headless.d.ts.map +1 -0
  370. package/dist/client/internal/headless.js +46 -0
  371. package/dist/client/internal/headless.js.map +1 -0
  372. package/dist/client/internal/indexedDb.d.ts +9 -0
  373. package/dist/client/internal/indexedDb.d.ts.map +1 -0
  374. package/dist/client/internal/indexedDb.js +32 -0
  375. package/dist/client/internal/indexedDb.js.map +1 -0
  376. package/dist/client/internal/request.d.ts +58 -0
  377. package/dist/client/internal/request.d.ts.map +1 -0
  378. package/dist/client/internal/request.js +45 -0
  379. package/dist/client/internal/request.js.map +1 -0
  380. package/dist/client/internal/store.d.ts +12 -0
  381. package/dist/client/internal/store.d.ts.map +1 -0
  382. package/dist/client/internal/store.js +15 -0
  383. package/dist/client/internal/store.js.map +1 -0
  384. package/dist/client.d.ts +20 -0
  385. package/dist/client.d.ts.map +1 -0
  386. package/dist/client.js +20 -0
  387. package/dist/client.js.map +1 -0
  388. package/dist/index.d.ts +7 -0
  389. package/dist/index.d.ts.map +1 -0
  390. package/dist/index.js +7 -0
  391. package/dist/index.js.map +1 -0
  392. package/dist/internal/Apps.d.ts +3051 -0
  393. package/dist/internal/Apps.d.ts.map +1 -0
  394. package/dist/internal/Apps.js +472 -0
  395. package/dist/internal/Apps.js.map +1 -0
  396. package/dist/internal/Auth.d.ts +4058 -0
  397. package/dist/internal/Auth.d.ts.map +1 -0
  398. package/dist/internal/Auth.js +137 -0
  399. package/dist/internal/Auth.js.map +1 -0
  400. package/dist/internal/Authorize.d.ts +532 -0
  401. package/dist/internal/Authorize.d.ts.map +1 -0
  402. package/dist/internal/Authorize.js +326 -0
  403. package/dist/internal/Authorize.js.map +1 -0
  404. package/dist/internal/Device.d.ts +371 -0
  405. package/dist/internal/Device.d.ts.map +1 -0
  406. package/dist/internal/Device.js +271 -0
  407. package/dist/internal/Device.js.map +1 -0
  408. package/dist/internal/Env.d.ts +57 -0
  409. package/dist/internal/Env.d.ts.map +1 -0
  410. package/dist/internal/Env.js +2 -0
  411. package/dist/internal/Env.js.map +1 -0
  412. package/dist/internal/ErrorResponse.d.ts +609 -0
  413. package/dist/internal/ErrorResponse.d.ts.map +1 -0
  414. package/dist/internal/ErrorResponse.js +679 -0
  415. package/dist/internal/ErrorResponse.js.map +1 -0
  416. package/dist/internal/Id.d.ts +10 -0
  417. package/dist/internal/Id.d.ts.map +1 -0
  418. package/dist/internal/Id.js +34 -0
  419. package/dist/internal/Id.js.map +1 -0
  420. package/dist/internal/KeyAuthorization.d.ts +1456 -0
  421. package/dist/internal/KeyAuthorization.d.ts.map +1 -0
  422. package/dist/internal/KeyAuthorization.js +1030 -0
  423. package/dist/internal/KeyAuthorization.js.map +1 -0
  424. package/dist/internal/Limits.d.ts +65 -0
  425. package/dist/internal/Limits.d.ts.map +1 -0
  426. package/dist/internal/Limits.js +35 -0
  427. package/dist/internal/Limits.js.map +1 -0
  428. package/dist/internal/Log.d.ts +49 -0
  429. package/dist/internal/Log.d.ts.map +1 -0
  430. package/dist/internal/Log.js +101 -0
  431. package/dist/internal/Log.js.map +1 -0
  432. package/dist/internal/LogLine.d.ts +33 -0
  433. package/dist/internal/LogLine.d.ts.map +1 -0
  434. package/dist/internal/LogLine.js +56 -0
  435. package/dist/internal/LogLine.js.map +1 -0
  436. package/dist/internal/Migrations.d.ts +19 -0
  437. package/dist/internal/Migrations.d.ts.map +1 -0
  438. package/dist/internal/Migrations.js +57 -0
  439. package/dist/internal/Migrations.js.map +1 -0
  440. package/dist/internal/Owner.d.ts +146 -0
  441. package/dist/internal/Owner.d.ts.map +1 -0
  442. package/dist/internal/Owner.js +65 -0
  443. package/dist/internal/Owner.js.map +1 -0
  444. package/dist/internal/Passkey.d.ts +528 -0
  445. package/dist/internal/Passkey.d.ts.map +1 -0
  446. package/dist/internal/Passkey.js +658 -0
  447. package/dist/internal/Passkey.js.map +1 -0
  448. package/dist/internal/PendingKey.d.ts +49 -0
  449. package/dist/internal/PendingKey.d.ts.map +1 -0
  450. package/dist/internal/PendingKey.js +58 -0
  451. package/dist/internal/PendingKey.js.map +1 -0
  452. package/dist/internal/Privy.d.ts +105 -0
  453. package/dist/internal/Privy.d.ts.map +1 -0
  454. package/dist/internal/Privy.js +224 -0
  455. package/dist/internal/Privy.js.map +1 -0
  456. package/dist/internal/RateLimit.d.ts +190 -0
  457. package/dist/internal/RateLimit.d.ts.map +1 -0
  458. package/dist/internal/RateLimit.js +148 -0
  459. package/dist/internal/RateLimit.js.map +1 -0
  460. package/dist/internal/Realm.d.ts +281 -0
  461. package/dist/internal/Realm.d.ts.map +1 -0
  462. package/dist/internal/Realm.js +477 -0
  463. package/dist/internal/Realm.js.map +1 -0
  464. package/dist/internal/RequestId.d.ts +16 -0
  465. package/dist/internal/RequestId.d.ts.map +1 -0
  466. package/dist/internal/RequestId.js +78 -0
  467. package/dist/internal/RequestId.js.map +1 -0
  468. package/dist/internal/SigningKey.d.ts +115 -0
  469. package/dist/internal/SigningKey.d.ts.map +1 -0
  470. package/dist/internal/SigningKey.js +197 -0
  471. package/dist/internal/SigningKey.js.map +1 -0
  472. package/dist/internal/TestInbox.d.ts +42 -0
  473. package/dist/internal/TestInbox.d.ts.map +1 -0
  474. package/dist/internal/TestInbox.js +20 -0
  475. package/dist/internal/TestInbox.js.map +1 -0
  476. package/dist/internal/Timing.d.ts +51 -0
  477. package/dist/internal/Timing.d.ts.map +1 -0
  478. package/dist/internal/Timing.js +103 -0
  479. package/dist/internal/Timing.js.map +1 -0
  480. package/dist/internal/Validation.d.ts +52 -0
  481. package/dist/internal/Validation.d.ts.map +1 -0
  482. package/dist/internal/Validation.js +22 -0
  483. package/dist/internal/Validation.js.map +1 -0
  484. package/dist/internal/Wallet.d.ts +37 -0
  485. package/dist/internal/Wallet.d.ts.map +1 -0
  486. package/dist/internal/Wallet.js +43 -0
  487. package/dist/internal/Wallet.js.map +1 -0
  488. package/dist/worker.d.ts +12 -0
  489. package/dist/worker.d.ts.map +1 -0
  490. package/dist/worker.js +12 -0
  491. package/dist/worker.js.map +1 -0
  492. package/package.json +82 -0
  493. package/src/Api.ts +151 -0
  494. package/src/App.ts +56 -0
  495. package/src/api/account.ts +311 -0
  496. package/src/api/authenticate.ts +1290 -0
  497. package/src/api/authorize.ts +1030 -0
  498. package/src/api/device.ts +874 -0
  499. package/src/api/health.ts +72 -0
  500. package/src/api/lib/AccountStep.ts +335 -0
  501. package/src/api/lib/Auth.ts +134 -0
  502. package/src/api/lib/Consent.ts +339 -0
  503. package/src/api/lib/Cors.ts +107 -0
  504. package/src/api/lib/Database.ts +10 -0
  505. package/src/api/lib/DeviceRequest.ts +265 -0
  506. package/src/api/lib/FirstAccount.ts +90 -0
  507. package/src/api/lib/KeySigner.ts +393 -0
  508. package/src/api/lib/PasskeyLink.ts +162 -0
  509. package/src/api/lib/Session.ts +34 -0
  510. package/src/api/lib/SignInConsent.ts +316 -0
  511. package/src/api/oauth.ts +1282 -0
  512. package/src/api/provider.ts +441 -0
  513. package/src/app/components/Button.tsx +128 -0
  514. package/src/app/components/CodeInput.tsx +186 -0
  515. package/src/app/components/ErrorMessage.tsx +66 -0
  516. package/src/app/components/Grant.tsx +323 -0
  517. package/src/app/components/Input.tsx +48 -0
  518. package/src/app/components/Tooltip.tsx +45 -0
  519. package/src/app/components/icons/BRLA.png +0 -0
  520. package/src/app/components/icons/BRZ.svg +1 -0
  521. package/src/app/components/icons/CHFAU.png +0 -0
  522. package/src/app/components/icons/DLUSD.png +0 -0
  523. package/src/app/components/icons/EURAU.svg +1 -0
  524. package/src/app/components/icons/EURC.e.svg +1 -0
  525. package/src/app/components/icons/GBPA.svg +4 -0
  526. package/src/app/components/icons/GUSD.svg +1 -0
  527. package/src/app/components/icons/MACH.svg +4 -0
  528. package/src/app/components/icons/OUSD.svg +5 -0
  529. package/src/app/components/icons/PRIME.svg +14 -0
  530. package/src/app/components/icons/SBC.svg +1 -0
  531. package/src/app/components/icons/SEKAU.svg +5 -0
  532. package/src/app/components/icons/USD1.svg +30 -0
  533. package/src/app/components/icons/USDB.svg +11 -0
  534. package/src/app/components/icons/USDC.e.svg +1 -0
  535. package/src/app/components/icons/USDT0.svg +1 -0
  536. package/src/app/components/icons/USDY.svg +16 -0
  537. package/src/app/components/icons/USDe.svg +1 -0
  538. package/src/app/components/icons/YLDS.svg +3 -0
  539. package/src/app/components/icons/alphaUSD.svg +1 -0
  540. package/src/app/components/icons/betaUSD.svg +1 -0
  541. package/src/app/components/icons/cUSD.svg +1 -0
  542. package/src/app/components/icons/cbBTC.svg +1 -0
  543. package/src/app/components/icons/frxUSD.svg +1 -0
  544. package/src/app/components/icons/goUSD.svg +5 -0
  545. package/src/app/components/icons/iUSD.svg +1 -0
  546. package/src/app/components/icons/pathUSD.svg +25 -0
  547. package/src/app/components/icons/rUSD.svg +1 -0
  548. package/src/app/components/icons/reUSD.svg +1 -0
  549. package/src/app/components/icons/sUSDe.svg +1 -0
  550. package/src/app/components/icons/siUSD.svg +1 -0
  551. package/src/app/components/icons/stcUSD.svg +1 -0
  552. package/src/app/components/icons/syrupUSDC.svg +5 -0
  553. package/src/app/components/icons/thetaUSD.svg +1 -0
  554. package/src/app/components/icons/wYLDS.svg +10 -0
  555. package/src/app/components/icons/wsrUSD.svg +1 -0
  556. package/src/app/components/logos/OUSD.dark.svg +17 -0
  557. package/src/app/components/logos/OUSD.light.svg +17 -0
  558. package/src/app/crons/cleanup.ts +42 -0
  559. package/src/app/db/auth.ts +416 -0
  560. package/src/app/db/migrations/0000_better_auth.sql +53 -0
  561. package/src/app/db/migrations/0001_oauth_provider.sql +162 -0
  562. package/src/app/db/migrations/0002_device_code.sql +17 -0
  563. package/src/app/db/migrations/0003_realms.sql +27 -0
  564. package/src/app/db/migrations/0004_default_realm.sql +2 -0
  565. package/src/app/db/migrations/0005_passkeys.sql +30 -0
  566. package/src/app/db/migrations/0006_first_accounts.sql +9 -0
  567. package/src/app/db/migrations/0007_pending_keys.sql +13 -0
  568. package/src/app/db/migrations/0008_secp256k1_owners.sql +18 -0
  569. package/src/app/db/migrations/0009_device_requests.sql +8 -0
  570. package/src/app/db/migrations/0010_device_keys.sql +4 -0
  571. package/src/app/db/migrations/meta/0000_snapshot.json +362 -0
  572. package/src/app/db/migrations/meta/0001_snapshot.json +1487 -0
  573. package/src/app/db/migrations/meta/0002_snapshot.json +1603 -0
  574. package/src/app/db/migrations/meta/0003_snapshot.json +1760 -0
  575. package/src/app/db/migrations/meta/0004_snapshot.json +1760 -0
  576. package/src/app/db/migrations/meta/0005_snapshot.json +1911 -0
  577. package/src/app/db/migrations/meta/0006_snapshot.json +1967 -0
  578. package/src/app/db/migrations/meta/0007_snapshot.json +2047 -0
  579. package/src/app/db/migrations/meta/0008_snapshot.json +2157 -0
  580. package/src/app/db/migrations/meta/0009_snapshot.json +2207 -0
  581. package/src/app/db/migrations/meta/0010_snapshot.json +2231 -0
  582. package/src/app/db/migrations/meta/_journal.json +83 -0
  583. package/src/app/db/schema.ts +218 -0
  584. package/src/app/env.ts +49 -0
  585. package/src/app/lib/Client.ts +82 -0
  586. package/src/app/lib/Loader.ts +24 -0
  587. package/src/app/package.json +8 -0
  588. package/src/app/routeTree.gen.ts +198 -0
  589. package/src/app/router.tsx +48 -0
  590. package/src/app/routes/[_].flows.tsx +3001 -0
  591. package/src/app/routes/__root.tsx +91 -0
  592. package/src/app/routes/authorize.tsx +197 -0
  593. package/src/app/routes/choose-account.tsx +111 -0
  594. package/src/app/routes/deposit.tsx +105 -0
  595. package/src/app/routes/device.tsx +227 -0
  596. package/src/app/routes/index.tsx +115 -0
  597. package/src/app/routes/login.tsx +506 -0
  598. package/src/app/screens/Account.tsx +347 -0
  599. package/src/app/screens/Approval.tsx +276 -0
  600. package/src/app/screens/ChooseAccount.tsx +240 -0
  601. package/src/app/screens/Deposit.tsx +762 -0
  602. package/src/app/screens/DeviceApproval.tsx +480 -0
  603. package/src/app/screens/SignIn.tsx +362 -0
  604. package/src/app/screens/chains/base.svg +3 -0
  605. package/src/app/screens/chains/ethereum.svg +11 -0
  606. package/src/app/screens/chains/solana.svg +4 -0
  607. package/src/app/screens/chains/tempo.svg +4 -0
  608. package/src/app/server.ts +204 -0
  609. package/src/app/vite.config.ts +95 -0
  610. package/src/app/void.config.ts +29 -0
  611. package/src/app/zyzz.config.ts +274 -0
  612. package/src/cli/Admin.ts +73 -0
  613. package/src/cli/Apps.ts +29 -0
  614. package/src/cli/Cli.ts +42 -0
  615. package/src/cli/Project.ts +117 -0
  616. package/src/cli/Realms.ts +50 -0
  617. package/src/cli/Session.ts +239 -0
  618. package/src/cli/User.ts +45 -0
  619. package/src/cli/V1.ts +57 -0
  620. package/src/cli/bin.ts +44 -0
  621. package/src/cli/commands/admin/apps/create.ts +101 -0
  622. package/src/cli/commands/admin/apps/delete.ts +38 -0
  623. package/src/cli/commands/admin/apps/index.ts +15 -0
  624. package/src/cli/commands/admin/apps/list.ts +25 -0
  625. package/src/cli/commands/admin/apps/rotate-secret.ts +47 -0
  626. package/src/cli/commands/admin/apps/update.ts +86 -0
  627. package/src/cli/commands/admin/index.ts +14 -0
  628. package/src/cli/commands/admin/migrate.ts +16 -0
  629. package/src/cli/commands/admin/realms/add.ts +40 -0
  630. package/src/cli/commands/admin/realms/index.ts +14 -0
  631. package/src/cli/commands/admin/realms/list.ts +38 -0
  632. package/src/cli/commands/admin/realms/passkeys/index.ts +15 -0
  633. package/src/cli/commands/admin/realms/passkeys/set.ts +44 -0
  634. package/src/cli/commands/admin/realms/privy/index.ts +14 -0
  635. package/src/cli/commands/admin/realms/privy/set.ts +69 -0
  636. package/src/cli/commands/admin/realms/remove.ts +33 -0
  637. package/src/cli/commands/admin/realms/secp256k1/disable.ts +40 -0
  638. package/src/cli/commands/admin/realms/secp256k1/enable.ts +40 -0
  639. package/src/cli/commands/admin/realms/secp256k1/index.ts +19 -0
  640. package/src/cli/commands/admin/realms/set.ts +46 -0
  641. package/src/cli/commands/admin/rotate-key.ts +47 -0
  642. package/src/cli/commands/init.ts +42 -0
  643. package/src/cli/commands/login.ts +32 -0
  644. package/src/cli/commands/logout.ts +19 -0
  645. package/src/cli/commands/whoami.ts +23 -0
  646. package/src/client/Actions.ts +168 -0
  647. package/src/client/Auth.ts +285 -0
  648. package/src/client/Authorization.ts +34 -0
  649. package/src/client/Decorator.ts +410 -0
  650. package/src/client/Key.ts +140 -0
  651. package/src/client/Pending.ts +46 -0
  652. package/src/client/Permissions.ts +39 -0
  653. package/src/client/Provider.ts +18 -0
  654. package/src/client/Step.ts +24 -0
  655. package/src/client/Storage.ts +29 -0
  656. package/src/client/TempoAccount.ts +42 -0
  657. package/src/client/Tokens.ts +27 -0
  658. package/src/client/User.ts +23 -0
  659. package/src/client/actions/account.ts +57 -0
  660. package/src/client/actions/device.ts +151 -0
  661. package/src/client/actions/email.ts +141 -0
  662. package/src/client/actions/oauth.ts +182 -0
  663. package/src/client/actions/passkey.ts +141 -0
  664. package/src/client/actions/provider.ts +70 -0
  665. package/src/client/actions/realm.ts +36 -0
  666. package/src/client/actions/redirect.ts +408 -0
  667. package/src/client/internal/accessKey.ts +54 -0
  668. package/src/client/internal/account.ts +15 -0
  669. package/src/client/internal/api.ts +224 -0
  670. package/src/client/internal/headless.ts +77 -0
  671. package/src/client/internal/indexedDb.ts +44 -0
  672. package/src/client/internal/request.ts +71 -0
  673. package/src/client/internal/store.ts +20 -0
  674. package/src/client.ts +19 -0
  675. package/src/index.ts +6 -0
  676. package/src/internal/Apps.ts +718 -0
  677. package/src/internal/Auth.ts +203 -0
  678. package/src/internal/Authorize.ts +424 -0
  679. package/src/internal/Balance.ts +168 -0
  680. package/src/internal/BodyLimit.ts +36 -0
  681. package/src/internal/Chain.ts +16 -0
  682. package/src/internal/Device.ts +469 -0
  683. package/src/internal/Env.ts +58 -0
  684. package/src/internal/ErrorResponse.ts +854 -0
  685. package/src/internal/Id.ts +41 -0
  686. package/src/internal/KeyAuthorization.ts +1339 -0
  687. package/src/internal/Limits.ts +39 -0
  688. package/src/internal/Log.ts +178 -0
  689. package/src/internal/LogLine.ts +87 -0
  690. package/src/internal/Logout.ts +336 -0
  691. package/src/internal/Migrations.ts +86 -0
  692. package/src/internal/OpenApi.ts +300 -0
  693. package/src/internal/Owner.ts +211 -0
  694. package/src/internal/Passkey.ts +1052 -0
  695. package/src/internal/PendingKey.ts +104 -0
  696. package/src/internal/Privy.ts +402 -0
  697. package/src/internal/Provider.ts +125 -0
  698. package/src/internal/RateLimit.ts +259 -0
  699. package/src/internal/Realm.ts +768 -0
  700. package/src/internal/RequestId.ts +118 -0
  701. package/src/internal/SignInGrant.ts +168 -0
  702. package/src/internal/SigningKey.ts +286 -0
  703. package/src/internal/TempoAccount.ts +342 -0
  704. package/src/internal/TestInbox.ts +51 -0
  705. package/src/internal/Timing.ts +158 -0
  706. package/src/internal/Validation.ts +52 -0
  707. package/src/internal/Wallet.ts +62 -0
  708. package/src/internal/owners/Passkey.ts +72 -0
  709. package/src/internal/owners/Privy.ts +81 -0
  710. package/src/internal/owners/Secp256k1.ts +143 -0
  711. package/src/types/app.d.ts +19 -0
  712. package/src/worker.ts +11 -0
@@ -0,0 +1,1290 @@
1
+ /** The sign-in routes under `/v1/auth`: email codes, passkeys, sign-out, and the session. @module */
2
+ import { verifyOAuthQueryParams } from '@better-auth/oauth-provider'
3
+ import { isAPIError } from 'better-auth/api'
4
+ import { and, eq, isNull } from 'drizzle-orm'
5
+ import type { Context } from 'hono'
6
+ import { Hono } from 'hono'
7
+ import { Authentication } from 'webauthx/server'
8
+ import { env } from 'void/env'
9
+ import * as z from 'zod'
10
+ import * as Schema from '../app/db/schema.js'
11
+ import * as Apps from '../internal/Apps.js'
12
+ import * as core_Auth from '../internal/Auth.js'
13
+ import * as Authorize from '../internal/Authorize.js'
14
+ import * as BodyLimit from '../internal/BodyLimit.js'
15
+ import * as Device from '../internal/Device.js'
16
+ import type { Env } from '../internal/Env.js'
17
+ import * as ErrorResponse from '../internal/ErrorResponse.js'
18
+ import * as KeyAuthorization from '../internal/KeyAuthorization.js'
19
+ import * as Log from '../internal/Log.js'
20
+ import * as OpenApi from '../internal/OpenApi.js'
21
+ import * as Passkey from '../internal/Passkey.js'
22
+ import * as RateLimit from '../internal/RateLimit.js'
23
+ import * as Realm from '../internal/Realm.js'
24
+ import * as Timing from '../internal/Timing.js'
25
+ import * as Wallet from '../internal/Wallet.js'
26
+ import * as AccountStep from './lib/AccountStep.js'
27
+ import * as Auth from './lib/Auth.js'
28
+ import * as Cors from './lib/Cors.js'
29
+ import { db } from './lib/Database.js'
30
+ import * as FirstAccount from './lib/FirstAccount.js'
31
+ import * as PasskeyLink from './lib/PasskeyLink.js'
32
+ import * as Session from './lib/Session.js'
33
+ import * as SignInConsent from './lib/SignInConsent.js'
34
+
35
+ /** A hostname label: 1 to 63 letters, digits, and hyphens, with no hyphen at either end. */
36
+ const hostnameLabel = /^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/
37
+
38
+ namespace schema {
39
+ export namespace code {
40
+ export const json = z.object({
41
+ email: z
42
+ .string()
43
+ .trim()
44
+ .toLowerCase()
45
+ .pipe(
46
+ // A malformed or long address stops at its first failed check, so the refusal names it once.
47
+ z
48
+ .email({ abort: true })
49
+ .max(254, { abort: true, error: 'Invalid email address' })
50
+ // The email service refuses a local part over 64 characters, and a domain label that is
51
+ // not a hostname label: over 63 characters, or with a hyphen at either end.
52
+ .refine((value) => {
53
+ const [local = '', domain = ''] = value.split('@')
54
+ return (
55
+ local.length <= 64 &&
56
+ domain.split('.').every((label) => hostnameLabel.test(label))
57
+ )
58
+ }, 'Invalid email address'),
59
+ )
60
+ .meta({
61
+ description: "The user's email address.",
62
+ examples: ['ada@example.com'],
63
+ }),
64
+ })
65
+
66
+ export const response = z.object({
67
+ sent: z.literal(true).meta({
68
+ description:
69
+ 'The request is accepted. The value is the same for every address.',
70
+ }),
71
+ })
72
+ }
73
+
74
+ export namespace verify {
75
+ export const json = z
76
+ .object({
77
+ assertion: Passkey.schema.assertion.optional().meta({
78
+ description:
79
+ "The assertion of the email step's passkey, over a challenge from `POST /v1/auth/passkey/options` with `link`. Required with `link`, since only the person who holds the passkey can link it.",
80
+ }),
81
+ code: z
82
+ .string()
83
+ .trim()
84
+ .regex(/^\d{6}$/)
85
+ .meta({
86
+ description: 'The six-digit emailed code.',
87
+ examples: ['123456'],
88
+ }),
89
+ email: code.json.shape.email,
90
+ link: z
91
+ .string()
92
+ .max(64)
93
+ .optional()
94
+ .meta({
95
+ description:
96
+ "The email step's `link`, which links the wallet.tempo passkey that asked for it to this user, with that passkey's `assertion`. An expired link refuses the sign-in with `passkey_link_expired`, and the code stays valid.",
97
+ examples: ['dGhlIG5vbmNlIHRoYXQgdGhlIHN0ZXAgY2Fycmllcw'],
98
+ }),
99
+ // The bound covers an authorization request's query, and limits what an unsigned query can
100
+ // write, since the passkey challenge stores its client ID before the signature is checked.
101
+ oauth_query: z
102
+ .string()
103
+ .max(8192)
104
+ .optional()
105
+ .meta({
106
+ description:
107
+ "The sign-in page's query string when an app's authorization request sent the browser there, which the sign-in continues. Better Auth signs it. At most 8,192 characters.",
108
+ examples: [
109
+ 'client_id=client_01JZ8Q6W3N1R5B4V7C2X9K0M3T&response_type=code&exp=1760000000&sig=Gx3kB0',
110
+ ],
111
+ }),
112
+ })
113
+ .refine((value) => !value.link || value.assertion, {
114
+ message: 'Required with `link`.',
115
+ path: ['assertion'],
116
+ })
117
+
118
+ /** The user fields that the API returns. */
119
+ export const user = z
120
+ .object({
121
+ email: z.email().meta({
122
+ description: "The user's email address.",
123
+ examples: ['ada@example.com'],
124
+ }),
125
+ id: z.string().meta({
126
+ description:
127
+ "The user's ID, which is the `sub` of every token Tempo ID issues for the user.",
128
+ examples: ['user_01JZ8Q6W3N1R5B4V7C2X9K0M3T'],
129
+ }),
130
+ })
131
+ .meta({ description: 'The signed-in user.', id: 'User' })
132
+
133
+ export const response = z.object({
134
+ redirect: z
135
+ .url()
136
+ .nullable()
137
+ .meta({
138
+ description:
139
+ "Where the browser continues: the app's `redirect_uri` with an authorization code, or `null` for a sign-in that no app waits on.",
140
+ examples: [
141
+ 'https://app.example.com/callback?code=SplxlOBeZQQYbYS6WxSbIA',
142
+ ],
143
+ }),
144
+ user,
145
+ })
146
+
147
+ /** What Better Auth returns: the user, or the authorization that the sign-in continued. */
148
+ export const signedIn = z.union([
149
+ z.object({ url: z.string() }),
150
+ z.object({ user: z.object({ email: z.string(), id: z.string() }) }),
151
+ ])
152
+ }
153
+
154
+ export namespace passkeyOptions {
155
+ export const json = z.object({
156
+ client_id: z
157
+ .string()
158
+ .min(1)
159
+ .max(64)
160
+ .optional()
161
+ .meta({
162
+ description:
163
+ "The client ID of the first-party app whose page runs the ceremony. It selects the app's realms, and the request's `Origin` must be the origin of one of the app's redirect URIs. Required from an app's page.",
164
+ examples: ['client_01JZ8Q6W3N1R5B4V7C2X9K0M3T'],
165
+ }),
166
+ credentialId: z
167
+ .union([
168
+ Passkey.schema.credentialId,
169
+ z.array(Passkey.schema.credentialId).max(16),
170
+ ])
171
+ .optional()
172
+ .meta({
173
+ description:
174
+ 'The credential ID, or up to 16 IDs, that the browser offers as `allowCredentials`, base64url. Defaults to every discoverable passkey of the relying party, as an empty list does.',
175
+ examples: ['KEbWNCc7NgaYnUyrNeFGX9_3Y-8'],
176
+ }),
177
+ link: verify.json.shape.link.meta({
178
+ description:
179
+ "The email step's `link`. The options then name only that step's passkey, at its relying party, in place of `credentialId`. The emailed-code sign-in sends the passkey's assertion with the `link`.",
180
+ examples: ['dGhlIG5vbmNlIHRoYXQgdGhlIHN0ZXAgY2Fycmllcw'],
181
+ }),
182
+ mediation: z
183
+ .enum(['conditional', 'optional', 'required', 'silent'])
184
+ .optional()
185
+ .meta({
186
+ description:
187
+ 'Returned in the options for `navigator.credentials.get()`, such as `conditional` for passkey autofill.',
188
+ examples: ['conditional'],
189
+ }),
190
+ oauth_query: verify.json.shape.oauth_query,
191
+ user_code: z
192
+ .string()
193
+ .max(32)
194
+ .optional()
195
+ .meta({
196
+ description:
197
+ 'The user code of the device grant that the sign-in returns to. While the code is pending and bound to no user, it selects the realms of the app that requested it. Any other code selects the `default` realm. Ignored with `oauth_query` or `client_id`.',
198
+ examples: ['WDJB-MJHT'],
199
+ }),
200
+ })
201
+
202
+ export const response = z.object({
203
+ options: z
204
+ .object({
205
+ mediation: passkeyOptions.json.shape.mediation,
206
+ publicKey: z
207
+ .object({
208
+ allowCredentials: z
209
+ .array(
210
+ z
211
+ .object({
212
+ id: z.string().meta({
213
+ description: 'A credential ID, base64url.',
214
+ examples: ['KEbWNCc7NgaYnUyrNeFGX9_3Y-8'],
215
+ }),
216
+ type: z.literal('public-key').meta({
217
+ description: 'The credential type.',
218
+ }),
219
+ })
220
+ .meta({ description: 'A passkey the browser offers.' }),
221
+ )
222
+ .optional()
223
+ .meta({
224
+ description:
225
+ 'The passkeys the browser offers, from `credentialId`. Absent, it offers every discoverable passkey of the relying party.',
226
+ }),
227
+ challenge: z.templateLiteral(['0x', z.string()]).meta({
228
+ description:
229
+ 'The single-use challenge, as hex. It expires after 5 minutes.',
230
+ examples: [
231
+ '0xf6310589abb21116acc6e123967ed0a125b5041c43f8e157237093e07eaa354c',
232
+ ],
233
+ }),
234
+ rpId: z.string().meta({
235
+ description: 'The relying party ID of the passkeys.',
236
+ examples: ['tempo.xyz'],
237
+ }),
238
+ userVerification: z.literal('required').meta({
239
+ description:
240
+ 'Every passkey sign-in requires user verification.',
241
+ }),
242
+ })
243
+ .meta({
244
+ description:
245
+ 'The `PublicKeyCredentialRequestOptions`, serialized with the challenge as hex.',
246
+ }),
247
+ })
248
+ .meta({
249
+ description: 'The options for `navigator.credentials.get()`.',
250
+ }),
251
+ })
252
+ }
253
+
254
+ export namespace passkeyVerify {
255
+ export const json = z.object({
256
+ assertion: Passkey.schema.assertion,
257
+ oauth_query: verify.json.shape.oauth_query,
258
+ })
259
+
260
+ export const response = z.union([
261
+ verify.response.meta({ description: 'The signed-in user.' }),
262
+ z
263
+ .object({
264
+ step: z
265
+ .object({
266
+ link: z.string().meta({
267
+ description:
268
+ 'Send as `link` with the emailed-code sign-in, which links the passkey to that user.',
269
+ examples: ['dGhlIG5vbmNlIHRoYXQgdGhlIHN0ZXAgY2Fycmllcw'],
270
+ }),
271
+ signed: z.literal(false).meta({
272
+ description:
273
+ 'Whether the assertion already signed a key authorization. This route signs none.',
274
+ }),
275
+ type: z.literal('email').meta({
276
+ description:
277
+ 'Sign in with an emailed code, which links the passkey to that user.',
278
+ }),
279
+ })
280
+ .meta({
281
+ description:
282
+ 'The email step, for a wallet.tempo passkey whose account has no email.',
283
+ }),
284
+ })
285
+ .meta({
286
+ description:
287
+ 'A passkey sign-in that continues when the user verifies an email address.',
288
+ }),
289
+ ])
290
+ }
291
+
292
+ export namespace logout {
293
+ export const response = z.object({
294
+ signed_out: z.literal(true).meta({
295
+ description: 'The session is over, or there was none.',
296
+ }),
297
+ })
298
+ }
299
+
300
+ export namespace session {
301
+ export const response = z.object({
302
+ user: verify.user
303
+ .extend({
304
+ name: z
305
+ .string()
306
+ .nullable()
307
+ .meta({
308
+ description:
309
+ "The user's name, or `null` for a user who signed up without one.",
310
+ examples: ['Ada Lovelace'],
311
+ }),
312
+ })
313
+ .nullable()
314
+ .meta({
315
+ description: 'The signed-in user, or `null` without a session.',
316
+ id: 'SessionUser',
317
+ }),
318
+ })
319
+ }
320
+
321
+ export namespace methods {
322
+ export const query = z.object({
323
+ oauth_query: verify.json.shape.oauth_query,
324
+ return_to: z
325
+ .string()
326
+ .max(2048)
327
+ .optional()
328
+ .meta({
329
+ description:
330
+ "The page that sign-in returns to, from the sign-in page's query. Only a path on the device page passes, and any other value reads as absent.",
331
+ examples: ['/device?code=WDJB-MJHT'],
332
+ }),
333
+ })
334
+
335
+ export const response = z.object({
336
+ google: z.boolean().meta({
337
+ description:
338
+ 'Whether Tempo ID offers Google sign-in, which the deployment configures.',
339
+ examples: [true],
340
+ }),
341
+ passkeys: z.boolean().meta({
342
+ description:
343
+ "Whether Tempo ID offers passkey sign-in: one of the realms of the app that `oauth_query` or the device code of `return_to` names, or the `default` realm without one, holds passkeys that sign in at Tempo ID's origin.",
344
+ examples: [true],
345
+ }),
346
+ request: z
347
+ .object({
348
+ app: z.string().meta({
349
+ description:
350
+ "The app's display name, or its client ID without one.",
351
+ examples: ['Tempo Cash'],
352
+ }),
353
+ key: KeyAuthorization.schema.description,
354
+ })
355
+ .nullable()
356
+ .meta({
357
+ description:
358
+ 'The app and the key of the signed authorization request in `oauth_query`, which the sign-in grants, or `null` for a forged or expired query, an unknown app, a request without a key, or a key with a `challenge`, which the approval page shows instead.',
359
+ }),
360
+ return_to: z
361
+ .string()
362
+ .nullable()
363
+ .meta({
364
+ description:
365
+ 'The page that sign-in returns to, a path on the device page, or `null` for the account.',
366
+ examples: ['/device?code=WDJB-MJHT'],
367
+ }),
368
+ user_code: z
369
+ .string()
370
+ .nullable()
371
+ .meta({
372
+ description:
373
+ 'The user code of the device page that sign-in returns to, or `null` without one.',
374
+ examples: ['WDJBMJHT'],
375
+ }),
376
+ })
377
+ }
378
+ }
379
+
380
+ /** The sign-in routes under `/v1/auth`. */
381
+ export const authenticate = new Hono<Env>()
382
+ .post(
383
+ '/v1/auth/code',
384
+ OpenApi.describeRoute({
385
+ description:
386
+ 'Emails a sign-in code. The response is the same for every address, so it reveals no account.',
387
+ responses: OpenApi.responses({
388
+ errors: [
389
+ ...BodyLimit.errors,
390
+ ...Auth.sendCode.errors,
391
+ ...OpenApi.validate.errors,
392
+ ...RateLimit.check.errors,
393
+ ErrorResponse.CrossSiteRequestError,
394
+ ErrorResponse.UpstreamUnavailableError,
395
+ ],
396
+ success: {
397
+ description:
398
+ 'The response for every address: the email service accepted the code, or does not deliver to the address.',
399
+ schema: schema.code.response,
400
+ },
401
+ }),
402
+ tags: ['Authenticate'],
403
+ }),
404
+ OpenApi.validate('json', schema.code.json),
405
+ async (c) => {
406
+ const { email } = c.req.valid('json')
407
+ // The IP address's limit counts first, so a refused flood spends no address's sends.
408
+ const refused = await Timing.measure(
409
+ c,
410
+ RateLimit.check(c, {
411
+ limits: [
412
+ { binding: 'SIGN_IN_IP_LIMIT', key: RateLimit.ip(c) },
413
+ { binding: 'SIGN_IN_CODE_LIMIT', key: RateLimit.email({ email }) },
414
+ ],
415
+ }),
416
+ 'RateLimit.check',
417
+ )
418
+ if (refused) return refused
419
+
420
+ // Every address receives a code, so the response reveals no account. The route waits for the
421
+ // email service, so a failed send refuses the request instead of a sign-in that never arrives.
422
+ const code = await Timing.measure(
423
+ c,
424
+ c.var.auth.api.createVerificationOTP({
425
+ body: { email, type: 'sign-in' },
426
+ }),
427
+ 'auth.createVerificationOTP',
428
+ )
429
+ const failure = await Timing.measure(
430
+ c,
431
+ Auth.sendCode(c, { code, email: email.toLowerCase() }),
432
+ 'Auth.sendCode',
433
+ ).catch((error: unknown) => error)
434
+ if (failure) {
435
+ // The refusal is the email service's, as for any upstream service, so it is a warning.
436
+ Log.warn(c, {
437
+ error: failure,
438
+ message: 'The email service refused a sign-in code.',
439
+ })
440
+ // A refusal of the address itself responds as a sent code, so the response is the same for
441
+ // every address. The email service's rate limit is one the caller can wait out. Any other
442
+ // refusal is an outage.
443
+ if (!(failure instanceof Auth.UndeliverableError))
444
+ return ErrorResponse.json(
445
+ c,
446
+ failure instanceof ErrorResponse.RateLimitedError
447
+ ? failure
448
+ : new ErrorResponse.UpstreamUnavailableError(),
449
+ )
450
+ }
451
+
452
+ return c.json({ sent: true as const }, 200)
453
+ },
454
+ )
455
+ .post(
456
+ '/v1/auth/code/verify',
457
+ OpenApi.describeRoute({
458
+ description:
459
+ 'Signs in with an emailed code, and sets the session cookie. For a user with no Tempo Account in the realm of the sign-in, the first realm of the app that `oauth_query` names or the `default` realm, it creates one when the realm creates accounts. A failed creation leaves the sign-in signed in, and the next sign-in or `POST /v1/account` creates the account.',
460
+ responses: OpenApi.responses({
461
+ errors: [
462
+ ...BodyLimit.errors,
463
+ ...Cors.ownPage.errors,
464
+ ...OpenApi.validate.errors,
465
+ ...RateLimit.check.errors,
466
+ core_Auth.AuthorizationExpiredError,
467
+ core_Auth.InvalidCodeError,
468
+ Passkey.LinkExpiredError,
469
+ Passkey.WebAuthnRejectedError,
470
+ ],
471
+ success: {
472
+ description: 'The signed-in user.',
473
+ schema: schema.verify.response,
474
+ },
475
+ }),
476
+ tags: ['Authenticate'],
477
+ }),
478
+ OpenApi.validate('json', schema.verify.json),
479
+ async (c) => {
480
+ const { assertion, code, email, link, oauth_query } = c.req.valid('json')
481
+ const crossSite = Cors.ownPage(c, { oauth_query })
482
+ if (crossSite) return ErrorResponse.json(c, crossSite)
483
+ // Better Auth counts the guesses at each code. The IP address's limit bounds guesses across
484
+ // addresses.
485
+ const refused = await Timing.measure(
486
+ c,
487
+ RateLimit.check(c, {
488
+ limits: [{ binding: 'SIGN_IN_IP_LIMIT', key: RateLimit.ip(c) }],
489
+ }),
490
+ 'RateLimit.check',
491
+ )
492
+ if (refused) return refused
493
+
494
+ // The email step's link is checked before the code, so an expired one refuses the sign-in and
495
+ // leaves the code unspent. The sign-in then never ends without the passkey it was meant to link.
496
+ const deferred = link
497
+ ? await Timing.measure(
498
+ c,
499
+ PasskeyLink.claim(c, { nonce: link }),
500
+ 'PasskeyLink.claim',
501
+ )
502
+ : undefined
503
+ if (link && !deferred)
504
+ return ErrorResponse.json(c, new Passkey.LinkExpiredError())
505
+
506
+ // The link's passkey signs again before the code is checked, so a page left at the email step
507
+ // links nothing for the next person, and a refusal leaves the code unspent.
508
+ if (deferred) {
509
+ const challenge = assertion
510
+ ? await Timing.measure(
511
+ c,
512
+ Passkey.take(db, {
513
+ clientDataJSON: assertion.metadata.clientDataJSON,
514
+ }),
515
+ 'Passkey.take',
516
+ )
517
+ : undefined
518
+ // Only a challenge that the options issued for this link confirms it.
519
+ const bound =
520
+ challenge && link
521
+ ? await Timing.measure(
522
+ c,
523
+ PasskeyLink.matches({ expected: challenge.link, nonce: link }),
524
+ 'PasskeyLink.matches',
525
+ )
526
+ : false
527
+ if (
528
+ !assertion ||
529
+ !challenge ||
530
+ !bound ||
531
+ challenge.clientId !== client({ oauth_query }) ||
532
+ challenge.origin !== Cors.appOrigin(c) ||
533
+ challenge.rpId !== deferred.rpId ||
534
+ assertion.id !== deferred.id ||
535
+ !Passkey.verify(assertion, {
536
+ challenge: challenge.challenge,
537
+ origin: challenge.origin ?? new URL(env.URL).origin,
538
+ publicKey: deferred.publicKey,
539
+ rpId: deferred.rpId,
540
+ })
541
+ )
542
+ return ErrorResponse.json(c, new Passkey.WebAuthnRejectedError())
543
+ }
544
+
545
+ const result = await Timing.measure(
546
+ c,
547
+ c.var.auth.api.signInEmailOTP({
548
+ // A call with a request returns a `Response` unless `asResponse` is false.
549
+ asResponse: false,
550
+ body: { email, otp: code, ...(oauth_query ? { oauth_query } : {}) },
551
+ // Better Auth sets `accept` to continue an authorization as JSON, and a request's own headers
552
+ // are immutable.
553
+ headers: new Headers(c.req.raw.headers),
554
+ // Better Auth continues an authorization only for a call that carries its request.
555
+ request: c.req.raw,
556
+ returnHeaders: true,
557
+ }),
558
+ 'auth.signInEmailOTP',
559
+ ).catch((error: unknown) => {
560
+ // Better Auth checks the authorization request's signature before the code, so a forged or
561
+ // expired one leaves the code unspent.
562
+ if (isAPIError(error) && error.body?.error === 'invalid_signature')
563
+ return new core_Auth.AuthorizationExpiredError()
564
+ // Better Auth refuses a wrong, expired, or spent code, and a code past its guesses. Any other
565
+ // failure fails the request.
566
+ if (isAPIError(error)) return new core_Auth.InvalidCodeError()
567
+ throw error
568
+ })
569
+ if (result instanceof ErrorResponse.ResponseError)
570
+ return ErrorResponse.json(c, result)
571
+
572
+ for (const cookie of result.headers.getSetCookie())
573
+ c.header('set-cookie', cookie, { append: true })
574
+ c.header('cache-control', 'private, no-store')
575
+
576
+ // With `oauth_query`, Better Auth continues the app's authorization once the session exists, and
577
+ // returns its redirect in place of the user.
578
+ const signedIn = schema.verify.signedIn.parse(result.response)
579
+ const user = await (async () => {
580
+ if ('user' in signedIn)
581
+ return { email: signedIn.user.email, id: signedIn.user.id }
582
+ const context = await Timing.measure(
583
+ c,
584
+ c.var.auth.$context,
585
+ 'auth.context',
586
+ )
587
+ const found = await Timing.measure(
588
+ c,
589
+ context.internalAdapter.findUserByEmail(email),
590
+ 'db.user.findByEmail',
591
+ )
592
+ if (!found) throw new Error('The signed-in user is missing.')
593
+ return { email: found.user.email, id: found.user.id }
594
+ })()
595
+
596
+ // The email step's passkey links to the user that the code signed in.
597
+ if (deferred) {
598
+ const linked = await Timing.measure(
599
+ c,
600
+ Passkey.link(db, {
601
+ credential: deferred,
602
+ id: deferred.id,
603
+ rpId: deferred.rpId,
604
+ userId: user.id,
605
+ }),
606
+ 'Passkey.link',
607
+ )
608
+ // A concurrent sign-in can link the passkey to another user first. Its row decides, so this
609
+ // sign-in links nothing, and the cookie stays until it expires.
610
+ if (linked === user.id) PasskeyLink.release(c)
611
+ }
612
+
613
+ // A linked passkey owns its account, so the first account follows the link. The sign-in grant
614
+ // below can then sign the key with it.
615
+ await Timing.measure(
616
+ c,
617
+ FirstAccount.create(c, {
618
+ clientId: client({ oauth_query }),
619
+ userId: user.id,
620
+ }),
621
+ 'FirstAccount.create',
622
+ )
623
+
624
+ // The sign-in page showed the request's key, so the sign-in grants it.
625
+ const redirect =
626
+ 'user' in signedIn
627
+ ? null
628
+ : await Timing.measure(
629
+ c,
630
+ SignInConsent.afterSignIn(c, {
631
+ redirect: signedIn.url,
632
+ setCookie: result.headers.getSetCookie(),
633
+ }),
634
+ 'SignInConsent.afterSignIn',
635
+ )
636
+ return c.json({ redirect, user }, 200)
637
+ },
638
+ )
639
+ .post(
640
+ '/v1/auth/passkey/options',
641
+ OpenApi.describeRoute({
642
+ description:
643
+ "Issues a single-use challenge, and returns request options for the passkeys of the relying party that the sign-in reaches. Without `oauth_query` or `client_id`, that is the relying party of the `default` realm. With either, it is the first of the app's realms that holds passkeys. From a first-party app's page, with `client_id`, the relying party must cover the page's origin and Tempo ID's, and only that page's assertion spends the challenge. With `link`, the options name only the email step's passkey.",
644
+ responses: OpenApi.responses({
645
+ errors: [
646
+ ...BodyLimit.errors,
647
+ ...Cors.ownPage.errors,
648
+ ...OpenApi.validate.errors,
649
+ ...Passkey.checkPage.errors,
650
+ ...RateLimit.check.errors,
651
+ Passkey.LinkExpiredError,
652
+ Passkey.NotPermittedError,
653
+ ],
654
+ success: {
655
+ description: 'The request options.',
656
+ schema: schema.passkeyOptions.response,
657
+ },
658
+ }),
659
+ tags: ['Authenticate'],
660
+ }),
661
+ OpenApi.validate('json', schema.passkeyOptions.json),
662
+ async (c) => {
663
+ const {
664
+ client_id,
665
+ credentialId,
666
+ link,
667
+ mediation,
668
+ oauth_query,
669
+ user_code,
670
+ } = c.req.valid('json')
671
+ const crossSite = Cors.ownPage(c, { oauth_query })
672
+ if (crossSite) return ErrorResponse.json(c, crossSite)
673
+ // Each view of the sign-in page requests options for its autofill, so the options requests
674
+ // count on their own key, and spend none of the address's quota for email sign-in.
675
+ const ip = RateLimit.ip(c)
676
+ const refused = await Timing.measure(
677
+ c,
678
+ RateLimit.check(c, {
679
+ limits: [
680
+ {
681
+ binding: 'SIGN_IN_IP_LIMIT',
682
+ key: ip ? `passkey-options:${ip}` : undefined,
683
+ },
684
+ ],
685
+ }),
686
+ 'RateLimit.check',
687
+ )
688
+ if (refused) return refused
689
+
690
+ const deferred = link
691
+ ? await Timing.measure(
692
+ c,
693
+ PasskeyLink.claim(c, { nonce: link }),
694
+ 'PasskeyLink.claim',
695
+ )
696
+ : undefined
697
+ if (link && !deferred)
698
+ return ErrorResponse.json(c, new Passkey.LinkExpiredError())
699
+
700
+ // A first-party app's page runs the ceremony at its own origin, which the challenge stores.
701
+ // Only an assertion for that origin, from a request that origin sends, spends it.
702
+ const page = Cors.appOrigin(c)
703
+ if (page || client_id) {
704
+ const app = client_id
705
+ ? await Timing.measure(
706
+ c,
707
+ // A disabled app can share its origin with an enabled one, which CORS admits.
708
+ Apps.get(db, { clientId: client_id, enabled: true }).catch(
709
+ (error: unknown) => {
710
+ if (error instanceof Apps.NotFoundError) return undefined
711
+ throw error
712
+ },
713
+ ),
714
+ 'Apps.get',
715
+ )
716
+ : undefined
717
+ const refusal = Passkey.checkPage({
718
+ app,
719
+ clientId: client_id,
720
+ origin: c.req.header('origin'),
721
+ })
722
+ if (refusal) return ErrorResponse.json(c, refusal)
723
+ }
724
+
725
+ // The signature of `oauth_query` is checked when the session is created. Until then, its app
726
+ // selects only the relying party. An app's page continues no authorization, so its challenge
727
+ // names no app.
728
+ const clientId = client({ oauth_query })
729
+ // A device app comes from the code's own row, never from the request, and selects only the
730
+ // relying party, so the challenge names no app.
731
+ const userCode = user_code ? Device.normalize(user_code) : undefined
732
+ const deviceApp =
733
+ !clientId && !client_id && userCode
734
+ ? await Timing.measure(
735
+ c,
736
+ Device.client(db, { userCode }),
737
+ 'Device.client',
738
+ )
739
+ : undefined
740
+ const appId = clientId ?? client_id ?? deviceApp
741
+ const stored = appId
742
+ ? await Timing.measure(
743
+ c,
744
+ Realm.forApp(db, { clientId: appId }),
745
+ 'Realm.forApp',
746
+ )
747
+ : [
748
+ await Timing.measure(
749
+ c,
750
+ Realm.get(db, { id: Realm.defaultId }),
751
+ 'Realm.get',
752
+ ),
753
+ ]
754
+ const realms = await Timing.measure(
755
+ c,
756
+ Realm.withDefaults(db, { origin: env.URL, realms: stored }),
757
+ 'Realm.withDefaults',
758
+ )
759
+ const relyingParty = Passkey.relyingParty(realms, {
760
+ origin: env.URL,
761
+ page,
762
+ })
763
+ // Only a challenge of the `default` realm reads wallet.tempo, so a link's passkey signs again
764
+ // only for a sign-in whose realms select that realm, at the link's relying party.
765
+ const refusesLink =
766
+ deferred &&
767
+ (relyingParty?.realmId !== Realm.defaultId ||
768
+ relyingParty.rpId !== deferred.rpId)
769
+ if (!relyingParty || refusesLink)
770
+ return ErrorResponse.json(
771
+ c,
772
+ new Passkey.NotPermittedError({
773
+ // A device app stays unnamed, so the refusal names no app that a code reveals.
774
+ clientId: (clientId ?? client_id) || undefined,
775
+ origin: page,
776
+ }),
777
+ )
778
+
779
+ // A challenge for a link stores the link's digest, so only that link's code sign-in takes it.
780
+ const linked = link
781
+ ? await Timing.measure(
782
+ c,
783
+ PasskeyLink.digest({ nonce: link }),
784
+ 'PasskeyLink.digest',
785
+ )
786
+ : undefined
787
+ // On the sign-in page, the challenge of a request with a key is the key's digest, so one
788
+ // passkey assertion signs in and signs the key. A link's step and an app's page sign in only.
789
+ const oneTap =
790
+ oauth_query && !link && !page
791
+ ? await Timing.measure(
792
+ c,
793
+ SignInConsent.oneTap(c, { oauthQuery: oauth_query }),
794
+ 'SignInConsent.oneTap',
795
+ )
796
+ : undefined
797
+ const challenge = await Timing.measure(
798
+ c,
799
+ Passkey.challenge(db, {
800
+ clientId,
801
+ link: linked,
802
+ oneTap,
803
+ origin: page,
804
+ ...relyingParty,
805
+ }),
806
+ 'Passkey.challenge',
807
+ )
808
+ // Without a credential ID, the browser offers every discoverable passkey of the relying party,
809
+ // as WebAuthn defines for an empty list. The challenge stays Tempo ID's either way.
810
+ const allowed = (() => {
811
+ if (deferred) return [deferred.id]
812
+ return credentialId === undefined ? [] : [credentialId].flat()
813
+ })()
814
+ const options = schema.passkeyOptions.response.shape.options.parse({
815
+ ...Authentication.getOptions({
816
+ challenge,
817
+ ...(allowed.length > 0 ? { credentialId: allowed } : {}),
818
+ rpId: relyingParty.rpId,
819
+ userVerification: 'required',
820
+ }).options,
821
+ ...(mediation ? { mediation } : {}),
822
+ })
823
+ c.header('cache-control', 'private, no-store')
824
+ return c.json({ options }, 200)
825
+ },
826
+ )
827
+ .post(
828
+ '/v1/auth/passkey/verify',
829
+ OpenApi.describeRoute({
830
+ description:
831
+ 'Signs in with a passkey assertion over a challenge from the options, sent from the page that requested them, and sets the session cookie. For a user with no Tempo Account in the realm of the sign-in, the first realm of the app that `oauth_query` names or the `default` realm, it creates one when the realm creates accounts. A failed creation leaves the sign-in signed in, and the next sign-in or `POST /v1/account` creates the account.',
832
+ responses: OpenApi.responses({
833
+ errors: [
834
+ ...BodyLimit.errors,
835
+ ...Cors.ownPage.errors,
836
+ ...OpenApi.validate.errors,
837
+ ...ErrorResponse.fromUpstream.errors,
838
+ ...RateLimit.check.errors,
839
+ core_Auth.AuthorizationExpiredError,
840
+ Passkey.WebAuthnRejectedError,
841
+ ],
842
+ success: {
843
+ description:
844
+ 'The signed-in user, or the email step for a wallet.tempo passkey without an email.',
845
+ schema: schema.passkeyVerify.response,
846
+ },
847
+ }),
848
+ tags: ['Authenticate'],
849
+ }),
850
+ OpenApi.validate('json', schema.passkeyVerify.json),
851
+ async (c) => {
852
+ const { assertion, oauth_query } = c.req.valid('json')
853
+ const crossSite = Cors.ownPage(c, { oauth_query })
854
+ if (crossSite) return ErrorResponse.json(c, crossSite)
855
+ const refused = await Timing.measure(
856
+ c,
857
+ RateLimit.check(c, {
858
+ limits: [{ binding: 'SIGN_IN_IP_LIMIT', key: RateLimit.ip(c) }],
859
+ }),
860
+ 'RateLimit.check',
861
+ )
862
+ if (refused) return refused
863
+
864
+ // The challenge is spent before any other check, so a failed attempt cannot be retried.
865
+ const challenge = await Timing.measure(
866
+ c,
867
+ Passkey.take(db, {
868
+ clientDataJSON: assertion.metadata.clientDataJSON,
869
+ }),
870
+ 'Passkey.take',
871
+ )
872
+ // A challenge serves only the sign-in it was issued for: the same app, or none, from the same
873
+ // page. A challenge for an email step's link serves only that link's code sign-in.
874
+ if (
875
+ !challenge ||
876
+ challenge.link !== undefined ||
877
+ challenge.clientId !== client({ oauth_query }) ||
878
+ challenge.origin !== Cors.appOrigin(c)
879
+ )
880
+ return ErrorResponse.json(c, new Passkey.WebAuthnRejectedError())
881
+
882
+ const { id } = assertion
883
+ const key = await Timing.measure(
884
+ c,
885
+ Passkey.get(db, { id, rpId: challenge.rpId }),
886
+ 'Passkey.get',
887
+ )
888
+ // A passkey that Tempo ID does not store yet may be wallet.tempo's, which shares the `default`
889
+ // realm's relying party, and links once it verifies.
890
+ const credential = await (async () => {
891
+ if (key || !c.env.WALLET || challenge.realmId !== Realm.defaultId)
892
+ return undefined
893
+ try {
894
+ return await Timing.measure(
895
+ c,
896
+ Wallet.credential(c.env.WALLET, { id }),
897
+ 'Wallet.credential',
898
+ )
899
+ } catch (error) {
900
+ // Only the binding call throws, so the failure is wallet.tempo's.
901
+ Log.warn(c, {
902
+ error,
903
+ message: 'The wallet service failed a passkey lookup.',
904
+ })
905
+ return (
906
+ (error instanceof Error && ErrorResponse.fromUpstream(error)) ||
907
+ new ErrorResponse.UpstreamUnavailableError()
908
+ )
909
+ }
910
+ })()
911
+ if (credential instanceof ErrorResponse.ResponseError)
912
+ return ErrorResponse.json(c, credential)
913
+ const publicKey = key?.publicKey ?? credential?.publicKey
914
+ if (
915
+ !publicKey ||
916
+ !Passkey.verify(assertion, {
917
+ challenge: challenge.challenge,
918
+ origin: challenge.origin ?? new URL(env.URL).origin,
919
+ publicKey: publicKey as `0x${string}`,
920
+ rpId: challenge.rpId,
921
+ })
922
+ )
923
+ return ErrorResponse.json(c, new Passkey.WebAuthnRejectedError())
924
+
925
+ // A wallet.tempo account without an email links once the user verifies one with a code.
926
+ if (credential && !credential.email) {
927
+ const link = await Timing.measure(
928
+ c,
929
+ PasskeyLink.defer(c, { credential, id, rpId: challenge.rpId }),
930
+ 'PasskeyLink.defer',
931
+ )
932
+ c.header('cache-control', 'private, no-store')
933
+ return c.json(
934
+ { step: { link, signed: false, type: 'email' as const } },
935
+ 200,
936
+ )
937
+ }
938
+
939
+ const userId = await (async () => {
940
+ if (key) return key.userId
941
+ // wallet.tempo verified the email, so its user is found or created by that address.
942
+ const user = await Timing.measure(
943
+ c,
944
+ emailUser(c, { email: credential!.email! }),
945
+ 'authenticate.emailUser',
946
+ )
947
+ return Timing.measure(
948
+ c,
949
+ Passkey.link(db, {
950
+ credential: credential!,
951
+ id,
952
+ rpId: challenge.rpId,
953
+ userId: user,
954
+ }),
955
+ 'Passkey.link',
956
+ )
957
+ })()
958
+ // Another passkey holds the address that wallet.tempo returned.
959
+ if (!userId)
960
+ return ErrorResponse.json(c, new Passkey.WebAuthnRejectedError())
961
+
962
+ const result = await Timing.measure(
963
+ c,
964
+ c.var.auth.api.signInPasskey({
965
+ asResponse: false,
966
+ body: {
967
+ userId,
968
+ ...(oauth_query ? { oauth_query } : {}),
969
+ },
970
+ // Better Auth sets `accept` to continue an authorization as JSON, and a request's own headers
971
+ // are immutable.
972
+ headers: new Headers(c.req.raw.headers),
973
+ request: c.req.raw,
974
+ returnHeaders: true,
975
+ }),
976
+ 'auth.signInPasskey',
977
+ ).catch((error: unknown) => {
978
+ if (isAPIError(error) && error.body?.error === 'invalid_signature')
979
+ return new core_Auth.AuthorizationExpiredError()
980
+ throw error
981
+ })
982
+ if (result instanceof ErrorResponse.ResponseError)
983
+ return ErrorResponse.json(c, result)
984
+
985
+ for (const cookie of result.headers.getSetCookie())
986
+ c.header('set-cookie', cookie, { append: true })
987
+ c.header('cache-control', 'private, no-store')
988
+
989
+ // With `oauth_query`, Better Auth continues the app's authorization once the session exists, and
990
+ // returns its redirect in place of the user.
991
+ const signedIn = schema.verify.signedIn.parse(result.response)
992
+ // A wallet.tempo passkey owns its account, so only a Tempo ID passkey's user can receive one.
993
+ await Timing.measure(
994
+ c,
995
+ FirstAccount.create(c, { clientId: client({ oauth_query }), userId }),
996
+ 'FirstAccount.create',
997
+ )
998
+ if ('user' in signedIn) {
999
+ const { user } = signedIn
1000
+ return c.json(
1001
+ { redirect: null, user: { email: user.email, id: user.id } },
1002
+ 200,
1003
+ )
1004
+ }
1005
+ const [user] = await Timing.measure(
1006
+ c,
1007
+ db
1008
+ .select({ email: Schema.user.email, id: Schema.user.id })
1009
+ .from(Schema.user)
1010
+ .where(eq(Schema.user.id, userId)),
1011
+ 'db.user.get',
1012
+ )
1013
+ if (!user) throw new Error('The signed-in user is missing.')
1014
+
1015
+ // The sign-in page showed the request's key, so the sign-in grants it. A one-tap challenge was
1016
+ // the key's digest, so the same assertion signs it for an account that this passkey owns.
1017
+ const redirect = await Timing.measure(
1018
+ c,
1019
+ SignInConsent.afterSignIn(c, {
1020
+ oneTap: challenge.witness
1021
+ ? { assertion, witness: challenge.witness }
1022
+ : undefined,
1023
+ redirect: signedIn.url,
1024
+ setCookie: result.headers.getSetCookie(),
1025
+ }),
1026
+ 'SignInConsent.afterSignIn',
1027
+ )
1028
+ return c.json({ redirect, user }, 200)
1029
+ },
1030
+ )
1031
+ .post(
1032
+ '/v1/auth/logout',
1033
+ OpenApi.describeRoute({
1034
+ description:
1035
+ 'Signs out: ends the session, revokes the refresh tokens that apps received through it, and clears its cookie.',
1036
+ responses: OpenApi.responses({
1037
+ errors: [...BodyLimit.errors, ErrorResponse.CrossSiteRequestError],
1038
+ success: {
1039
+ description: 'The session is over.',
1040
+ schema: schema.logout.response,
1041
+ },
1042
+ }),
1043
+ tags: ['Authenticate'],
1044
+ }),
1045
+ async (c) => {
1046
+ const headers = c.req.raw.headers
1047
+ const session = await Timing.measure(
1048
+ c,
1049
+ c.var.auth.api.getSession({ headers }),
1050
+ 'auth.getSession',
1051
+ )
1052
+ // Better Auth revokes a session's tokens when it deletes the session, except `offline_access`
1053
+ // refresh tokens, which every app requests. Sign-out ends the apps' access too.
1054
+ if (session)
1055
+ await Timing.measure(
1056
+ c,
1057
+ db
1058
+ .update(Schema.oauthRefreshToken)
1059
+ .set({ revoked: new Date() })
1060
+ .where(
1061
+ and(
1062
+ eq(Schema.oauthRefreshToken.sessionId, session.session.id),
1063
+ isNull(Schema.oauthRefreshToken.revoked),
1064
+ ),
1065
+ ),
1066
+ 'db.oauthRefreshToken.revoke',
1067
+ )
1068
+
1069
+ const result = await Timing.measure(
1070
+ c,
1071
+ c.var.auth.api.signOut({ headers, returnHeaders: true }),
1072
+ 'auth.signOut',
1073
+ )
1074
+ for (const cookie of result.headers.getSetCookie())
1075
+ c.header('set-cookie', cookie, { append: true })
1076
+ // The next sign-in chooses each app's account again.
1077
+ c.header('set-cookie', AccountStep.clear(), { append: true })
1078
+ c.header('cache-control', 'private, no-store')
1079
+ return c.json({ signed_out: true as const }, 200)
1080
+ },
1081
+ )
1082
+ .get(
1083
+ '/v1/auth/session',
1084
+ OpenApi.describeRoute({
1085
+ description: 'Reads the signed-in user, or `null` without a session.',
1086
+ responses: OpenApi.responses({
1087
+ success: {
1088
+ description: 'The session.',
1089
+ schema: schema.session.response,
1090
+ },
1091
+ }),
1092
+ tags: ['Authenticate'],
1093
+ }),
1094
+ async (c) => {
1095
+ const session = await Timing.measure(c, Session.get(c), 'Session.get')
1096
+ c.header('cache-control', 'private, no-store')
1097
+ if (!session) return c.json({ user: null }, 200)
1098
+ return c.json(
1099
+ {
1100
+ user: {
1101
+ email: session.user.email,
1102
+ id: session.user.id,
1103
+ // Better Auth stores an empty name for a user who signed up without one.
1104
+ name: session.user.name || null,
1105
+ },
1106
+ },
1107
+ 200,
1108
+ )
1109
+ },
1110
+ )
1111
+ .get(
1112
+ '/v1/auth/methods',
1113
+ OpenApi.describeRoute({
1114
+ description:
1115
+ "Reads the sign-in methods that the sign-in page offers, the key that a signed authorization request in `oauth_query` grants, and the page that sign-in returns to. A device code in `return_to` names the app that requested it, while the code is pending and bound to no user. The read binds nothing, and counts against the IP address's sign-in limit. Without `oauth_query`, a client ID reads the `default` realm, as the other sign-in routes do.",
1116
+ responses: OpenApi.responses({
1117
+ errors: [...OpenApi.validate.errors],
1118
+ success: {
1119
+ description: 'The sign-in methods.',
1120
+ schema: schema.methods.response,
1121
+ },
1122
+ }),
1123
+ tags: ['Authenticate'],
1124
+ }),
1125
+ OpenApi.validate('query', schema.methods.query),
1126
+ async (c) => {
1127
+ const input = c.req.valid('query')
1128
+ // Sign-in returns only to an allowlisted path, so `return_to` never redirects to another site.
1129
+ const returnTo = Device.returnTo(input.return_to, { origin: env.URL })
1130
+ // A failed Google sign-in's `error` is not part of the signed request, so the route drops it.
1131
+ const query = (() => {
1132
+ if (!input.oauth_query) return undefined
1133
+ const query = new URLSearchParams(input.oauth_query)
1134
+ query.delete('error')
1135
+ return query.has('sig') ? query : undefined
1136
+ })()
1137
+
1138
+ // The user code comes from `return_to`, but the app comes from the code's own row, so the
1139
+ // visitor selects no app by itself.
1140
+ const userCode = returnTo
1141
+ ? Device.normalize(
1142
+ new URL(returnTo, env.URL).searchParams.get('code') ?? '',
1143
+ )
1144
+ : undefined
1145
+ const clientId = await (async () => {
1146
+ if (query) return query.get('client_id') ?? undefined
1147
+ if (!userCode) return undefined
1148
+ // A live code's realms change the page, so each IP address's lookups count, and keep a guess
1149
+ // of a live code out of reach (RFC 8628 §5.1). Past the limit, the page shows the `default`
1150
+ // realm.
1151
+ const ip = RateLimit.ip(c)
1152
+ if (!ip) return undefined
1153
+ const outcome = await Timing.measure(
1154
+ c,
1155
+ c.env.SIGN_IN_IP_LIMIT.limit({ key: `device-lookup:${ip}` }),
1156
+ 'ratelimit.limit',
1157
+ )
1158
+ if (!outcome.success) return undefined
1159
+ return await Timing.measure(
1160
+ c,
1161
+ Device.client(db, { userCode }),
1162
+ 'Device.client',
1163
+ )
1164
+ })()
1165
+ const stored = clientId
1166
+ ? await Timing.measure(
1167
+ c,
1168
+ Realm.forApp(db, { clientId }),
1169
+ 'Realm.forApp',
1170
+ )
1171
+ : [
1172
+ await Timing.measure(
1173
+ c,
1174
+ Realm.get(db, { id: Realm.defaultId }),
1175
+ 'Realm.get',
1176
+ ),
1177
+ ]
1178
+ const realms = await Timing.measure(
1179
+ c,
1180
+ Realm.withDefaults(db, { origin: env.URL, realms: stored }),
1181
+ 'Realm.withDefaults',
1182
+ )
1183
+ const request = query
1184
+ ? await Timing.measure(c, granted(c, { query }), 'authenticate.granted')
1185
+ : null
1186
+ return c.json(
1187
+ {
1188
+ google: Boolean(Auth.google()),
1189
+ passkeys: Boolean(Passkey.relyingParty(realms, { origin: env.URL })),
1190
+ request,
1191
+ return_to: returnTo ?? null,
1192
+ user_code: userCode || null,
1193
+ },
1194
+ 200,
1195
+ )
1196
+ },
1197
+ )
1198
+
1199
+ /** Returns the client ID of the app that `oauth_query` names, or `null` without one. */
1200
+ function client(options: client.Options) {
1201
+ if (!options.oauth_query) return null
1202
+ return new URLSearchParams(options.oauth_query).get('client_id')
1203
+ }
1204
+
1205
+ declare namespace client {
1206
+ type Options = {
1207
+ /** The sign-in page's query string, as the request sends it. */
1208
+ oauth_query?: string | undefined
1209
+ }
1210
+ }
1211
+
1212
+ /**
1213
+ * Returns the ID of the user with `email`, which wallet.tempo verified, and creates that user when
1214
+ * none exists. A concurrent first sign-in can create the user first, so the read repeats.
1215
+ */
1216
+ async function emailUser(c: Context<Env>, options: emailUser.Options) {
1217
+ const email = options.email.trim().toLowerCase()
1218
+ const context = await Timing.measure(c, c.var.auth.$context, 'auth.context')
1219
+ const found = await Timing.measure(
1220
+ c,
1221
+ context.internalAdapter.findUserByEmail(email),
1222
+ 'db.user.findByEmail',
1223
+ )
1224
+ if (found) return found.user.id
1225
+
1226
+ try {
1227
+ const created = await Timing.measure(
1228
+ c,
1229
+ context.internalAdapter.createUser(
1230
+ { email, emailVerified: true, name: '' },
1231
+ { method: 'passkey' },
1232
+ ),
1233
+ 'db.user.create',
1234
+ )
1235
+ return created.id
1236
+ } catch (error) {
1237
+ const again = await Timing.measure(
1238
+ c,
1239
+ context.internalAdapter.findUserByEmail(email),
1240
+ 'db.user.findByEmail',
1241
+ )
1242
+ if (again) return again.user.id
1243
+ throw error
1244
+ }
1245
+ }
1246
+
1247
+ declare namespace emailUser {
1248
+ type Options = {
1249
+ /** The email address that wallet.tempo verified. */
1250
+ email: string
1251
+ }
1252
+ }
1253
+
1254
+ /**
1255
+ * Returns the app and the key of the signed authorization request `query`, which the sign-in grants,
1256
+ * or `null` for a forged or expired query, an unknown app, or a request without a key. A request
1257
+ * with a `challenge` shows its key on the approval page instead, beside its message.
1258
+ */
1259
+ async function granted(c: Context<Env>, options: granted.Options) {
1260
+ const { query } = options
1261
+ if (!query.has('key_authorization') || query.has('challenge')) return null
1262
+
1263
+ const context = await Timing.measure(c, c.var.auth.$context, 'auth.context')
1264
+ const verified = await Timing.measure(
1265
+ c,
1266
+ verifyOAuthQueryParams(query.toString(), context.secret),
1267
+ 'auth.verifyOAuthQueryParams',
1268
+ )
1269
+ const clientId = query.get('client_id')
1270
+ if (!verified || !clientId) return null
1271
+
1272
+ const app = await Timing.measure(
1273
+ c,
1274
+ Apps.get(db, { clientId }).catch((error: unknown) => {
1275
+ if (error instanceof Apps.NotFoundError) return undefined
1276
+ throw error
1277
+ }),
1278
+ 'Apps.get',
1279
+ )
1280
+ const key = Authorize.fromQuery(query)
1281
+ if (!app || !key || key instanceof ErrorResponse.ResponseError) return null
1282
+ return { app: app.name ?? clientId, key: KeyAuthorization.describe(key) }
1283
+ }
1284
+
1285
+ declare namespace granted {
1286
+ type Options = {
1287
+ /** The sign-in page's query, without a failed Google sign-in's `error`. */
1288
+ query: URLSearchParams
1289
+ }
1290
+ }