@authup/server-core 1.0.0-beta.54 → 1.0.0-beta.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (328) hide show
  1. package/dist/adapters/database/data-source/options/module.mjs.map +1 -1
  2. package/dist/adapters/database/domains/client/entity.mjs +2 -2
  3. package/dist/adapters/database/domains/client/entity.mjs.map +1 -1
  4. package/dist/adapters/database/domains/client/subscriber.mjs +2 -2
  5. package/dist/adapters/database/domains/client/subscriber.mjs.map +1 -1
  6. package/dist/adapters/database/domains/client-permission/entity.mjs +2 -2
  7. package/dist/adapters/database/domains/client-permission/entity.mjs.map +1 -1
  8. package/dist/adapters/database/domains/client-permission/subscriber.mjs +2 -2
  9. package/dist/adapters/database/domains/client-permission/subscriber.mjs.map +1 -1
  10. package/dist/adapters/database/domains/client-role/entity.mjs +2 -2
  11. package/dist/adapters/database/domains/client-role/entity.mjs.map +1 -1
  12. package/dist/adapters/database/domains/client-role/subscriber.mjs +2 -2
  13. package/dist/adapters/database/domains/client-role/subscriber.mjs.map +1 -1
  14. package/dist/adapters/database/domains/client-scope/entity.mjs +2 -2
  15. package/dist/adapters/database/domains/client-scope/entity.mjs.map +1 -1
  16. package/dist/adapters/database/domains/client-scope/subscriber.mjs +2 -2
  17. package/dist/adapters/database/domains/client-scope/subscriber.mjs.map +1 -1
  18. package/dist/adapters/database/domains/consent/entity.mjs +2 -2
  19. package/dist/adapters/database/domains/consent/entity.mjs.map +1 -1
  20. package/dist/adapters/database/domains/consent/subscriber.mjs +2 -2
  21. package/dist/adapters/database/domains/consent/subscriber.mjs.map +1 -1
  22. package/dist/adapters/database/domains/constants.mjs.map +1 -1
  23. package/dist/adapters/database/domains/event/entity.mjs +2 -2
  24. package/dist/adapters/database/domains/event/entity.mjs.map +1 -1
  25. package/dist/adapters/database/domains/identity-provider/entity.mjs +2 -2
  26. package/dist/adapters/database/domains/identity-provider/entity.mjs.map +1 -1
  27. package/dist/adapters/database/domains/identity-provider/subscriber.mjs +2 -2
  28. package/dist/adapters/database/domains/identity-provider/subscriber.mjs.map +1 -1
  29. package/dist/adapters/database/domains/identity-provider-account/entity.mjs +2 -2
  30. package/dist/adapters/database/domains/identity-provider-account/entity.mjs.map +1 -1
  31. package/dist/adapters/database/domains/identity-provider-account/subscriber.mjs +2 -2
  32. package/dist/adapters/database/domains/identity-provider-account/subscriber.mjs.map +1 -1
  33. package/dist/adapters/database/domains/identity-provider-attribute/entity.mjs +2 -2
  34. package/dist/adapters/database/domains/identity-provider-attribute/entity.mjs.map +1 -1
  35. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs +2 -2
  36. package/dist/adapters/database/domains/identity-provider-attribute/subscriber.mjs.map +1 -1
  37. package/dist/adapters/database/domains/identity-provider-attribute-mapping/entity.mjs +2 -2
  38. package/dist/adapters/database/domains/identity-provider-attribute-mapping/entity.mjs.map +1 -1
  39. package/dist/adapters/database/domains/identity-provider-permission-mapping/entity.mjs +2 -2
  40. package/dist/adapters/database/domains/identity-provider-permission-mapping/entity.mjs.map +1 -1
  41. package/dist/adapters/database/domains/identity-provider-role-mapping/entity.mjs +2 -2
  42. package/dist/adapters/database/domains/identity-provider-role-mapping/entity.mjs.map +1 -1
  43. package/dist/adapters/database/domains/identity-provider-role-mapping/subscriber.mjs +2 -2
  44. package/dist/adapters/database/domains/identity-provider-role-mapping/subscriber.mjs.map +1 -1
  45. package/dist/adapters/database/domains/key/entity.mjs +2 -2
  46. package/dist/adapters/database/domains/key/entity.mjs.map +1 -1
  47. package/dist/adapters/database/domains/permission/entity.mjs +2 -2
  48. package/dist/adapters/database/domains/permission/entity.mjs.map +1 -1
  49. package/dist/adapters/database/domains/permission/subscriber.mjs +2 -2
  50. package/dist/adapters/database/domains/permission/subscriber.mjs.map +1 -1
  51. package/dist/adapters/database/domains/permission-policy/entity.mjs +2 -2
  52. package/dist/adapters/database/domains/permission-policy/entity.mjs.map +1 -1
  53. package/dist/adapters/database/domains/policy/entity.mjs +2 -2
  54. package/dist/adapters/database/domains/policy/entity.mjs.map +1 -1
  55. package/dist/adapters/database/domains/policy/subscriber.mjs +2 -2
  56. package/dist/adapters/database/domains/policy/subscriber.mjs.map +1 -1
  57. package/dist/adapters/database/domains/policy-attribute/entity.mjs +2 -2
  58. package/dist/adapters/database/domains/policy-attribute/entity.mjs.map +1 -1
  59. package/dist/adapters/database/domains/policy-attribute/subscriber.mjs +2 -2
  60. package/dist/adapters/database/domains/policy-attribute/subscriber.mjs.map +1 -1
  61. package/dist/adapters/database/domains/realm/entity.mjs +2 -2
  62. package/dist/adapters/database/domains/realm/entity.mjs.map +1 -1
  63. package/dist/adapters/database/domains/realm/subscriber.mjs +2 -2
  64. package/dist/adapters/database/domains/realm/subscriber.mjs.map +1 -1
  65. package/dist/adapters/database/domains/role/entity.mjs +2 -2
  66. package/dist/adapters/database/domains/role/entity.mjs.map +1 -1
  67. package/dist/adapters/database/domains/role/subscriber.mjs +2 -2
  68. package/dist/adapters/database/domains/role/subscriber.mjs.map +1 -1
  69. package/dist/adapters/database/domains/role-attribute/entity.mjs +2 -2
  70. package/dist/adapters/database/domains/role-attribute/entity.mjs.map +1 -1
  71. package/dist/adapters/database/domains/role-attribute/subscriber.mjs +2 -2
  72. package/dist/adapters/database/domains/role-attribute/subscriber.mjs.map +1 -1
  73. package/dist/adapters/database/domains/role-permission/entity.mjs +2 -2
  74. package/dist/adapters/database/domains/role-permission/entity.mjs.map +1 -1
  75. package/dist/adapters/database/domains/role-permission/subscriber.mjs +2 -2
  76. package/dist/adapters/database/domains/role-permission/subscriber.mjs.map +1 -1
  77. package/dist/adapters/database/domains/scope/entity.mjs +2 -2
  78. package/dist/adapters/database/domains/scope/entity.mjs.map +1 -1
  79. package/dist/adapters/database/domains/scope/subscriber.mjs +2 -2
  80. package/dist/adapters/database/domains/scope/subscriber.mjs.map +1 -1
  81. package/dist/adapters/database/domains/session/entity.mjs +2 -2
  82. package/dist/adapters/database/domains/session/entity.mjs.map +1 -1
  83. package/dist/adapters/database/domains/session-token/entity.mjs +2 -2
  84. package/dist/adapters/database/domains/session-token/entity.mjs.map +1 -1
  85. package/dist/adapters/database/domains/trust-anchor/entity.mjs +2 -2
  86. package/dist/adapters/database/domains/trust-anchor/entity.mjs.map +1 -1
  87. package/dist/adapters/database/domains/user/entity.mjs +2 -2
  88. package/dist/adapters/database/domains/user/entity.mjs.map +1 -1
  89. package/dist/adapters/database/domains/user/subscriber.mjs +2 -2
  90. package/dist/adapters/database/domains/user/subscriber.mjs.map +1 -1
  91. package/dist/adapters/database/domains/user-attribute/entity.mjs +2 -2
  92. package/dist/adapters/database/domains/user-attribute/entity.mjs.map +1 -1
  93. package/dist/adapters/database/domains/user-attribute/subscriber.mjs +2 -2
  94. package/dist/adapters/database/domains/user-attribute/subscriber.mjs.map +1 -1
  95. package/dist/adapters/database/domains/user-authenticator/entity.mjs +2 -2
  96. package/dist/adapters/database/domains/user-authenticator/entity.mjs.map +1 -1
  97. package/dist/adapters/database/domains/user-permission/entity.mjs +2 -2
  98. package/dist/adapters/database/domains/user-permission/entity.mjs.map +1 -1
  99. package/dist/adapters/database/domains/user-permission/subscriber.mjs +2 -2
  100. package/dist/adapters/database/domains/user-permission/subscriber.mjs.map +1 -1
  101. package/dist/adapters/database/domains/user-role/entity.mjs +2 -2
  102. package/dist/adapters/database/domains/user-role/entity.mjs.map +1 -1
  103. package/dist/adapters/database/domains/user-role/subscriber.mjs +2 -2
  104. package/dist/adapters/database/domains/user-role/subscriber.mjs.map +1 -1
  105. package/dist/adapters/database/errors/driver.mjs.map +1 -1
  106. package/dist/adapters/database/extra-attribute-repository/adapter.mjs.map +1 -1
  107. package/dist/adapters/database/extra-attribute-repository/tree/adapter.mjs.map +1 -1
  108. package/dist/adapters/database/extra-attribute-repository/tree/repository.mjs.map +1 -1
  109. package/dist/adapters/database/migrations/mysql/1779267068441-Default.mjs.map +1 -1
  110. package/dist/adapters/database/migrations/mysql/1779300000000-Default.mjs.map +1 -1
  111. package/dist/adapters/database/migrations/mysql/1784289540000-CamelCaseAttributes.mjs.map +1 -1
  112. package/dist/adapters/database/migrations/postgres/1779267068441-Default.mjs.map +1 -1
  113. package/dist/adapters/database/migrations/postgres/1779300000000-Default.mjs.map +1 -1
  114. package/dist/adapters/database/migrations/postgres/1784289540000-CamelCaseAttributes.mjs.map +1 -1
  115. package/dist/adapters/database/subscriber/module.mjs.map +1 -1
  116. package/dist/adapters/http/adapters/oauth2/authorization/module.mjs.map +1 -1
  117. package/dist/adapters/http/adapters/oauth2/grant-types/utils/realm.mjs.map +1 -1
  118. package/dist/adapters/http/controllers/entities/client/module.mjs +2 -2
  119. package/dist/adapters/http/controllers/entities/client/module.mjs.map +1 -1
  120. package/dist/adapters/http/controllers/entities/client-permission/module.mjs +2 -2
  121. package/dist/adapters/http/controllers/entities/client-permission/module.mjs.map +1 -1
  122. package/dist/adapters/http/controllers/entities/client-role/module.mjs +2 -2
  123. package/dist/adapters/http/controllers/entities/client-role/module.mjs.map +1 -1
  124. package/dist/adapters/http/controllers/entities/client-scope/module.mjs +2 -2
  125. package/dist/adapters/http/controllers/entities/client-scope/module.mjs.map +1 -1
  126. package/dist/adapters/http/controllers/entities/consent/module.mjs +2 -2
  127. package/dist/adapters/http/controllers/entities/consent/module.mjs.map +1 -1
  128. package/dist/adapters/http/controllers/entities/event/module.mjs +2 -2
  129. package/dist/adapters/http/controllers/entities/event/module.mjs.map +1 -1
  130. package/dist/adapters/http/controllers/entities/identity-provide-role/module.mjs +2 -2
  131. package/dist/adapters/http/controllers/entities/identity-provide-role/module.mjs.map +1 -1
  132. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs +2 -2
  133. package/dist/adapters/http/controllers/entities/identity-provider/module.mjs.map +1 -1
  134. package/dist/adapters/http/controllers/entities/key/module.mjs +2 -2
  135. package/dist/adapters/http/controllers/entities/key/module.mjs.map +1 -1
  136. package/dist/adapters/http/controllers/entities/permission/module.mjs +2 -2
  137. package/dist/adapters/http/controllers/entities/permission/module.mjs.map +1 -1
  138. package/dist/adapters/http/controllers/entities/permission-policy/module.mjs +2 -2
  139. package/dist/adapters/http/controllers/entities/permission-policy/module.mjs.map +1 -1
  140. package/dist/adapters/http/controllers/entities/policy/module.mjs +2 -2
  141. package/dist/adapters/http/controllers/entities/policy/module.mjs.map +1 -1
  142. package/dist/adapters/http/controllers/entities/realm/module.mjs +2 -2
  143. package/dist/adapters/http/controllers/entities/realm/module.mjs.map +1 -1
  144. package/dist/adapters/http/controllers/entities/role/module.mjs +2 -2
  145. package/dist/adapters/http/controllers/entities/role/module.mjs.map +1 -1
  146. package/dist/adapters/http/controllers/entities/role-attribute/module.mjs +2 -2
  147. package/dist/adapters/http/controllers/entities/role-attribute/module.mjs.map +1 -1
  148. package/dist/adapters/http/controllers/entities/role-permission/module.mjs +2 -2
  149. package/dist/adapters/http/controllers/entities/role-permission/module.mjs.map +1 -1
  150. package/dist/adapters/http/controllers/entities/scope/module.mjs +2 -2
  151. package/dist/adapters/http/controllers/entities/scope/module.mjs.map +1 -1
  152. package/dist/adapters/http/controllers/entities/session/module.mjs +2 -2
  153. package/dist/adapters/http/controllers/entities/session/module.mjs.map +1 -1
  154. package/dist/adapters/http/controllers/entities/trust-anchor/module.mjs +2 -2
  155. package/dist/adapters/http/controllers/entities/trust-anchor/module.mjs.map +1 -1
  156. package/dist/adapters/http/controllers/entities/user/module.mjs +2 -2
  157. package/dist/adapters/http/controllers/entities/user/module.mjs.map +1 -1
  158. package/dist/adapters/http/controllers/entities/user-attribute/module.mjs +2 -2
  159. package/dist/adapters/http/controllers/entities/user-attribute/module.mjs.map +1 -1
  160. package/dist/adapters/http/controllers/entities/user-authenticator/module.mjs +2 -2
  161. package/dist/adapters/http/controllers/entities/user-authenticator/module.mjs.map +1 -1
  162. package/dist/adapters/http/controllers/entities/user-permission/module.mjs +2 -2
  163. package/dist/adapters/http/controllers/entities/user-permission/module.mjs.map +1 -1
  164. package/dist/adapters/http/controllers/entities/user-role/module.mjs +2 -2
  165. package/dist/adapters/http/controllers/entities/user-role/module.mjs.map +1 -1
  166. package/dist/adapters/http/controllers/workflows/activate/module.mjs +2 -2
  167. package/dist/adapters/http/controllers/workflows/authenticator-challenge/module.mjs +2 -2
  168. package/dist/adapters/http/controllers/workflows/authorize/module.mjs +2 -2
  169. package/dist/adapters/http/controllers/workflows/authorize/module.mjs.map +1 -1
  170. package/dist/adapters/http/controllers/workflows/jwks/handlers/read.mjs.map +1 -1
  171. package/dist/adapters/http/controllers/workflows/jwks/module.mjs +2 -2
  172. package/dist/adapters/http/controllers/workflows/jwks/module.mjs.map +1 -1
  173. package/dist/adapters/http/controllers/workflows/logout/module.mjs +2 -2
  174. package/dist/adapters/http/controllers/workflows/logout/module.mjs.map +1 -1
  175. package/dist/adapters/http/controllers/workflows/openid/module.mjs +2 -2
  176. package/dist/adapters/http/controllers/workflows/password-forgot/module.mjs +2 -2
  177. package/dist/adapters/http/controllers/workflows/password-reset/module.mjs +2 -2
  178. package/dist/adapters/http/controllers/workflows/register/module.mjs +2 -2
  179. package/dist/adapters/http/controllers/workflows/status/module.mjs +2 -2
  180. package/dist/adapters/http/controllers/workflows/token/module.mjs +2 -2
  181. package/dist/adapters/http/controllers/workflows/token/utils/extract.mjs.map +1 -1
  182. package/dist/adapters/http/middleware/built-in/authorization/module.mjs.map +1 -1
  183. package/dist/adapters/http/middleware/built-in/cors.mjs.map +1 -1
  184. package/dist/adapters/http/middleware/built-in/logger.mjs.map +1 -1
  185. package/dist/adapters/http/request/constants.mjs.map +1 -1
  186. package/dist/adapters/http/request/helpers/param-id.mjs.map +1 -1
  187. package/dist/adapters/http/request/helpers/redirect.mjs.map +1 -1
  188. package/dist/app/factory.mjs.map +1 -1
  189. package/dist/app/modules/authentication/module.mjs.map +1 -1
  190. package/dist/app/modules/authentication/repositories/constants.mjs.map +1 -1
  191. package/dist/app/modules/authentication/repositories/session.mjs.map +1 -1
  192. package/dist/app/modules/cache/module.mjs.map +1 -1
  193. package/dist/app/modules/components/module.mjs.map +1 -1
  194. package/dist/app/modules/config/constants.mjs.map +1 -1
  195. package/dist/app/modules/config/origins.mjs.map +1 -1
  196. package/dist/app/modules/config/read/env.mjs.map +1 -1
  197. package/dist/app/modules/config/read/fs.mjs.map +1 -1
  198. package/dist/app/modules/config/validator.mjs.map +1 -1
  199. package/dist/app/modules/database/module.mjs.map +1 -1
  200. package/dist/app/modules/database/repositories/bindings.mjs.map +1 -1
  201. package/dist/app/modules/database/repositories/client/repository.mjs.map +1 -1
  202. package/dist/app/modules/database/repositories/event/repository.mjs.map +1 -1
  203. package/dist/app/modules/database/repositories/helpers.mjs.map +1 -1
  204. package/dist/app/modules/database/repositories/permission-provider/module.mjs.map +1 -1
  205. package/dist/app/modules/database/repositories/user/repository.mjs.map +1 -1
  206. package/dist/app/modules/database/repositories/user-attribute/repository.mjs.map +1 -1
  207. package/dist/app/modules/database/repositories/user-authenticator/repository.mjs.map +1 -1
  208. package/dist/app/modules/http/module.mjs.map +1 -1
  209. package/dist/app/modules/http/modules/controller.mjs.map +1 -1
  210. package/dist/app/modules/identity/module.mjs.map +1 -1
  211. package/dist/app/modules/logger/module.mjs.map +1 -1
  212. package/dist/app/modules/mail/module.mjs.map +1 -1
  213. package/dist/app/modules/metrics/prom.mjs.map +1 -1
  214. package/dist/app/modules/oauth2/module.mjs.map +1 -1
  215. package/dist/app/modules/oauth2/repositories/consent/repository.mjs.map +1 -1
  216. package/dist/app/modules/oauth2/repositories/constants.mjs.map +1 -1
  217. package/dist/app/modules/provisioning/module.mjs.map +1 -1
  218. package/dist/app/modules/provisioning/sources/composite/module.mjs.map +1 -1
  219. package/dist/app/modules/provisioning/sources/default/module.mjs.map +1 -1
  220. package/dist/app/modules/provisioning/sources/file/module.mjs.map +1 -1
  221. package/dist/app/modules/runtime/module.mjs.map +1 -1
  222. package/dist/cli/commands/migration.mjs.map +1 -1
  223. package/dist/components/database/unique-entries/module.mjs.map +1 -1
  224. package/dist/core/authentication/entities/client/module.mjs.map +1 -1
  225. package/dist/core/authentication/session/module.mjs.map +1 -1
  226. package/dist/core/authentication/session/types.mjs.map +1 -1
  227. package/dist/core/client-certificate/module.mjs.map +1 -1
  228. package/dist/core/entities/client/schema.mjs.map +1 -1
  229. package/dist/core/entities/client/service.mjs.map +1 -1
  230. package/dist/core/entities/client-permission/schema.mjs.map +1 -1
  231. package/dist/core/entities/client-permission/service.mjs.map +1 -1
  232. package/dist/core/entities/client-role/schema.mjs.map +1 -1
  233. package/dist/core/entities/client-role/service.mjs.map +1 -1
  234. package/dist/core/entities/client-scope/schema.mjs.map +1 -1
  235. package/dist/core/entities/client-scope/service.mjs.map +1 -1
  236. package/dist/core/entities/consent/schema.mjs.map +1 -1
  237. package/dist/core/entities/consent/types.mjs.map +1 -1
  238. package/dist/core/entities/event/diff.mjs.map +1 -1
  239. package/dist/core/entities/event/sanitize.mjs.map +1 -1
  240. package/dist/core/entities/event/schema.mjs.map +1 -1
  241. package/dist/core/entities/event/service.mjs.map +1 -1
  242. package/dist/core/entities/identity-provider/schema.mjs.map +1 -1
  243. package/dist/core/entities/identity-provider-role-mapping/schema.mjs.map +1 -1
  244. package/dist/core/entities/identity-provider-role-mapping/service.mjs.map +1 -1
  245. package/dist/core/entities/key/schema.mjs.map +1 -1
  246. package/dist/core/entities/key/service.mjs.map +1 -1
  247. package/dist/core/entities/permission/schema.d.ts.map +1 -1
  248. package/dist/core/entities/permission/schema.mjs +12 -0
  249. package/dist/core/entities/permission/schema.mjs.map +1 -1
  250. package/dist/core/entities/permission/service.mjs.map +1 -1
  251. package/dist/core/entities/permission-policy/schema.mjs.map +1 -1
  252. package/dist/core/entities/permission-policy/service.mjs.map +1 -1
  253. package/dist/core/entities/policy/schema.mjs.map +1 -1
  254. package/dist/core/entities/policy/service.mjs.map +1 -1
  255. package/dist/core/entities/realm/schema.mjs.map +1 -1
  256. package/dist/core/entities/role/schema.mjs.map +1 -1
  257. package/dist/core/entities/role/service.mjs.map +1 -1
  258. package/dist/core/entities/role-attribute/schema.mjs.map +1 -1
  259. package/dist/core/entities/role-attribute/service.mjs.map +1 -1
  260. package/dist/core/entities/role-permission/schema.mjs.map +1 -1
  261. package/dist/core/entities/role-permission/service.mjs.map +1 -1
  262. package/dist/core/entities/scope/schema.mjs.map +1 -1
  263. package/dist/core/entities/scope/service.mjs.map +1 -1
  264. package/dist/core/entities/session/schema.mjs.map +1 -1
  265. package/dist/core/entities/session/service.mjs.map +1 -1
  266. package/dist/core/entities/trust-anchor/schema.mjs.map +1 -1
  267. package/dist/core/entities/trust-anchor/service.mjs.map +1 -1
  268. package/dist/core/entities/user/schema.mjs.map +1 -1
  269. package/dist/core/entities/user/service.mjs.map +1 -1
  270. package/dist/core/entities/user-attribute/schema.mjs.map +1 -1
  271. package/dist/core/entities/user-attribute/service.mjs.map +1 -1
  272. package/dist/core/entities/user-authenticator/schema.mjs.map +1 -1
  273. package/dist/core/entities/user-authenticator/service.mjs.map +1 -1
  274. package/dist/core/entities/user-authenticator/types.mjs.map +1 -1
  275. package/dist/core/entities/user-permission/schema.mjs.map +1 -1
  276. package/dist/core/entities/user-permission/service.mjs.map +1 -1
  277. package/dist/core/entities/user-role/schema.mjs.map +1 -1
  278. package/dist/core/entities/user-role/service.mjs.map +1 -1
  279. package/dist/core/identity/permission/junction-grant.mjs.map +1 -1
  280. package/dist/core/identity/permission/module.mjs.map +1 -1
  281. package/dist/core/identity/provider/authentication/presets/facebook.mjs.map +1 -1
  282. package/dist/core/identity/provider/authentication/presets/github.mjs.map +1 -1
  283. package/dist/core/identity/provider/authentication/presets/google.mjs.map +1 -1
  284. package/dist/core/identity/provider/authentication/presets/instagram.mjs.map +1 -1
  285. package/dist/core/identity/provider/authentication/presets/paypal.mjs.map +1 -1
  286. package/dist/core/identity/provider/authentication/protocols/ldap/module.mjs.map +1 -1
  287. package/dist/core/identity/provider/authentication/protocols/oauth2/module.mjs.map +1 -1
  288. package/dist/core/identity/provider/authentication/protocols/open-id/module.mjs.map +1 -1
  289. package/dist/core/identity/provider/constants.mjs.map +1 -1
  290. package/dist/core/identity/provider/mapper/base.mjs.map +1 -1
  291. package/dist/core/identity/provider/mapper/constants.mjs.map +1 -1
  292. package/dist/core/identity/registration/service.mjs.map +1 -1
  293. package/dist/core/key/provisioner.mjs.map +1 -1
  294. package/dist/core/key/realm-cipher.mjs.map +1 -1
  295. package/dist/core/mail/template/templates/mfa-email-otp.mjs.map +1 -1
  296. package/dist/core/mail/template/templates/password-reset.mjs.map +1 -1
  297. package/dist/core/mail/template/templates/registration-activation.mjs.map +1 -1
  298. package/dist/core/mail/template/types.mjs.map +1 -1
  299. package/dist/core/oauth2/authorization/code/verifier/module.mjs.map +1 -1
  300. package/dist/core/oauth2/authorization/code-request/verifier/module.mjs.map +1 -1
  301. package/dist/core/oauth2/authorization/module.mjs.map +1 -1
  302. package/dist/core/oauth2/end-session/service.mjs.map +1 -1
  303. package/dist/core/oauth2/grant-types/refresh-token.mjs.map +1 -1
  304. package/dist/core/oauth2/mfa-login/module.mjs.map +1 -1
  305. package/dist/core/oauth2/openid/claims.mjs.map +1 -1
  306. package/dist/core/oauth2/scope/resolver.mjs.map +1 -1
  307. package/dist/core/oauth2/token/issuer/access/module.mjs.map +1 -1
  308. package/dist/core/oauth2/token/issuer/mfa/module.mjs.map +1 -1
  309. package/dist/core/oauth2/token/issuer/open-id/module.mjs.map +1 -1
  310. package/dist/core/oauth2/token/issuer/refresh/module.mjs.map +1 -1
  311. package/dist/core/oauth2/token/verifier/module.mjs.map +1 -1
  312. package/dist/core/provisioning/entities/policy/validator.mjs.map +1 -1
  313. package/dist/core/provisioning/entities/utils.mjs.map +1 -1
  314. package/dist/core/provisioning/strategy/constants.mjs.map +1 -1
  315. package/dist/core/provisioning/strategy/validator.mjs.map +1 -1
  316. package/dist/core/provisioning/synchronizer/client/module.mjs.map +1 -1
  317. package/dist/core/provisioning/synchronizer/role/module.mjs.map +1 -1
  318. package/dist/core/provisioning/synchronizer/user/module.mjs.map +1 -1
  319. package/dist/core/query/module.mjs.map +1 -1
  320. package/dist/core/query/relations.mjs.map +1 -1
  321. package/dist/core/security/policy/evaluator.mjs.map +1 -1
  322. package/dist/ui/client/.vite/ssr-manifest.json +2 -2
  323. package/dist/ui/client/assets/{index-BmJ2X468.js → index-C05uGFDe.js} +5 -5
  324. package/dist/ui/client/assets/index-CiHrf73J.css +2 -0
  325. package/dist/ui/client/index.html +2 -2
  326. package/dist/ui/server/server.js +26961 -26463
  327. package/package.json +26 -26
  328. package/dist/ui/client/assets/index-DiOvW76Q.css +0 -2
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["ScopeName","isClientPublic","isSimpleMatch","isUUID","OAuth2ClientError","OAuth2GrantError","OAuth2RequestError","OAuth2ScopeError","OAuth2TokenGrant","hasOAuth2Scopes","assertClientGrantAllowed","OAuth2AuthorizationCodeRequestVerifier","clientRepository","scopeRepository","ctx","verify","data","client_id","invalid","realm_id","malformed","client","findOneByIdOrName","active","inactive","AUTHORIZATION_CODE","redirectUri","redirectUriMismatch","code_challenge","state","id","realmId","scopes","findByClientId","scopeNames","map","scope","name","GLOBAL","insufficient","join","redirectUriVerified","redirect_uri","redirectUris","split"],"sources":["../../../../../../src/core/oauth2/authorization/code-request/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2AuthorizationCodeRequest } from '@authup/core-kit';\nimport { ScopeName, isClientPublic } from '@authup/core-kit';\nimport { isSimpleMatch, isUUID } from '@authup/kit';\nimport {\n OAuth2ClientError,\n OAuth2GrantError,\n OAuth2RequestError,\n OAuth2ScopeError,\n OAuth2TokenGrant,\n hasOAuth2Scopes,\n} from '@authup/specs';\nimport type { IOAuth2ClientRepository } from '../../../client/index.ts';\nimport { assertClientGrantAllowed } from '../../../client/index.ts';\nimport type { IOAuth2ScopeRepository } from '../../../scope/index.ts';\nimport type {\n IOAuth2AuthorizationCodeRequestVerifier,\n OAuth2AuthorizationCodeRequestVerificationResult,\n OAuth2AuthorizationCodeRequestVerifierContext,\n} from './types.ts';\n\nexport class OAuth2AuthorizationCodeRequestVerifier implements IOAuth2AuthorizationCodeRequestVerifier {\n protected clientRepository: IOAuth2ClientRepository;\n\n protected scopeRepository: IOAuth2ScopeRepository;\n\n constructor(ctx: OAuth2AuthorizationCodeRequestVerifierContext) {\n this.clientRepository = ctx.clientRepository;\n this.scopeRepository = ctx.scopeRepository;\n }\n\n /**\n * Verify validated authorization code request.\n * @param data\n */\n async verify(\n data: OAuth2AuthorizationCodeRequest,\n ) : Promise<OAuth2AuthorizationCodeRequestVerificationResult> {\n if (!data.client_id) {\n throw OAuth2ClientError.invalid();\n }\n\n // A name-identified client needs a realm hint to resolve deterministically\n // — every realm has a built-in `web` client, so `client_id=web` without a\n // realm would bind to an arbitrary realm's client (and, post realm-gate,\n // produce a confusing mismatch against a random realm). Require the hint.\n if (!isUUID(data.client_id) && !data.realm_id) {\n throw OAuth2RequestError.malformed('A realm is required to resolve a client by name.');\n }\n\n const client = await this.clientRepository.findOneByIdOrName(data.client_id, data.realm_id);\n if (!client) {\n throw OAuth2ClientError.invalid();\n }\n\n if (!client.active) {\n throw OAuth2ClientError.inactive();\n }\n\n // A non-null grant_types allowlist must cover the code flow — an RP\n // misconfiguration fails at the front door, not at code redemption.\n assertClientGrantAllowed(client, OAuth2TokenGrant.AUTHORIZATION_CODE);\n\n // OAuth 2.1 posture: a client with no registered redirect_uri pattern\n // can never be matched — reject outright instead of trusting whatever\n // redirect_uri the request carries (the server would otherwise issue a\n // code to an attacker-supplied URI).\n if (!client.redirectUri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n // Public clients MUST use PKCE (RFC 7636 §4.4.1, OAuth 2.1). Without\n // PKCE a public client's code flow has no second factor — anyone who\n // intercepts the redirect can redeem the code at /token. The code flow\n // is the only supported response type, so this holds unconditionally.\n if (isClientPublic(client) && !data.code_challenge) {\n throw OAuth2RequestError.malformed('PKCE code_challenge is required for public clients.');\n }\n\n // Public clients SHOULD include state to bind the redirect to the\n // initiating session and prevent CSRF (RFC 6749 §10.12). Confidential\n // clients are exempt because the /token exchange already authenticates\n // them via client_secret.\n if (isClientPublic(client) && !data.state) {\n throw OAuth2RequestError.malformed('state is required for public clients in the code flow.');\n }\n\n data.client_id = client.id;\n data.realm_id = client.realmId;\n\n const scopes = await this.scopeRepository.findByClientId(client.id);\n const scopeNames = scopes.map((scope) => scope.name);\n if (data.scope) {\n if (\n !hasOAuth2Scopes(scopeNames, data.scope) &&\n !hasOAuth2Scopes(data.scope, ScopeName.GLOBAL)\n ) {\n throw OAuth2ScopeError.insufficient();\n }\n } else {\n data.scope = scopeNames.join(' ');\n }\n\n // Verified only when the request's redirect_uri matched a registered\n // pattern (pattern-less clients were rejected above). A request without\n // a redirect_uri (e.g. the GET page render) stays unverified so\n // consumers never auto-redirect without a match.\n const redirectUriVerified = !!data.redirect_uri;\n if (data.redirect_uri) {\n const redirectUris = client.redirectUri.split(',');\n\n if (!isSimpleMatch(data.redirect_uri, redirectUris)) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n }\n\n return {\n data,\n client,\n scopes,\n redirectUriVerified,\n };\n }\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAaW,yCAAb,MAAaA;CACCC;CAEAC;CAEV,YAAYC,KAAoD;EAC5D,KAAKF,mBAAmBE,IAAIF;EAC5B,KAAKC,kBAAkBC,IAAID;CAC/B;;;;IAMA,MAAME,OACFC,MAC0D;EAC1D,IAAI,CAACA,KAAKC,WACN,MAAMb,kBAAkBc,QAAO;EAOnC,IAAI,CAACf,OAAOa,KAAKC,SAAS,KAAK,CAACD,KAAKG,UACjC,MAAMb,mBAAmBc,UAAU,kDAAA;EAGvC,MAAMC,SAAS,MAAM,KAAKT,iBAAiBU,kBAAkBN,KAAKC,WAAWD,KAAKG,QAAQ;EAC1F,IAAI,CAACE,QACD,MAAMjB,kBAAkBc,QAAO;EAGnC,IAAI,CAACG,OAAOE,QACR,MAAMnB,kBAAkBoB,SAAQ;EAKpCd,yBAAyBW,QAAQb,iBAAiBiB,kBAAkB;EAMpE,IAAI,CAACJ,OAAOK,aACR,MAAMrB,iBAAiBsB,oBAAmB;EAO9C,IAAI1B,eAAeoB,MAAAA,KAAW,CAACL,KAAKY,gBAChC,MAAMtB,mBAAmBc,UAAU,qDAAA;EAOvC,IAAInB,eAAeoB,MAAAA,KAAW,CAACL,KAAKa,OAChC,MAAMvB,mBAAmBc,UAAU,wDAAA;EAGvCJ,KAAKC,YAAYI,OAAOS;EACxBd,KAAKG,WAAWE,OAAOU;EAEvB,MAAMC,SAAS,MAAM,KAAKnB,gBAAgBoB,eAAeZ,OAAOS,EAAE;EAClE,MAAMI,aAAaF,OAAOG,KAAKC,UAAUA,MAAMC,IAAI;EACnD,IAAIrB,KAAKoB;OAED,CAAC3B,gBAAgByB,YAAYlB,KAAKoB,KAAK,KACvC,CAAC3B,gBAAgBO,KAAKoB,OAAOpC,UAAUsC,MAAM,GAE7C,MAAM/B,iBAAiBgC,aAAY;EAAA,OAGvCvB,KAAKoB,QAAQF,WAAWM,KAAK,GAAA;EAOjC,MAAMC,sBAAsB,CAAC,CAACzB,KAAK0B;EACnC,IAAI1B,KAAK0B,cAAc;GACnB,MAAMC,eAAetB,OAAOK,YAAYkB,MAAM,GAAA;GAE9C,IAAI,CAAC1C,cAAcc,KAAK0B,cAAcC,YAAAA,GAClC,MAAMtC,iBAAiBsB,oBAAmB;EAElD;EAEA,OAAO;GACHX;GACAK;GACAW;GACAS;EACJ;CACJ;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["ScopeName","isClientPublic","isSimpleMatch","isUUID","OAuth2ClientError","OAuth2GrantError","OAuth2RequestError","OAuth2ScopeError","OAuth2TokenGrant","hasOAuth2Scopes","assertClientGrantAllowed","OAuth2AuthorizationCodeRequestVerifier","clientRepository","scopeRepository","ctx","verify","data","client_id","invalid","realm_id","malformed","client","findOneByIdOrName","active","inactive","AUTHORIZATION_CODE","redirectUri","redirectUriMismatch","code_challenge","state","id","realmId","scopes","findByClientId","scopeNames","map","scope","name","GLOBAL","insufficient","join","redirectUriVerified","redirect_uri","redirectUris","split"],"sources":["../../../../../../src/core/oauth2/authorization/code-request/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2AuthorizationCodeRequest } from '@authup/core-kit';\nimport { ScopeName, isClientPublic } from '@authup/core-kit';\nimport { isSimpleMatch, isUUID } from '@authup/kit';\nimport {\n OAuth2ClientError,\n OAuth2GrantError,\n OAuth2RequestError,\n OAuth2ScopeError,\n OAuth2TokenGrant,\n hasOAuth2Scopes,\n} from '@authup/specs';\nimport type { IOAuth2ClientRepository } from '../../../client/index.ts';\nimport { assertClientGrantAllowed } from '../../../client/index.ts';\nimport type { IOAuth2ScopeRepository } from '../../../scope/index.ts';\nimport type {\n IOAuth2AuthorizationCodeRequestVerifier,\n OAuth2AuthorizationCodeRequestVerificationResult,\n OAuth2AuthorizationCodeRequestVerifierContext,\n} from './types.ts';\n\nexport class OAuth2AuthorizationCodeRequestVerifier implements IOAuth2AuthorizationCodeRequestVerifier {\n protected clientRepository: IOAuth2ClientRepository;\n\n protected scopeRepository: IOAuth2ScopeRepository;\n\n constructor(ctx: OAuth2AuthorizationCodeRequestVerifierContext) {\n this.clientRepository = ctx.clientRepository;\n this.scopeRepository = ctx.scopeRepository;\n }\n\n /**\n * Verify validated authorization code request.\n * @param data\n */\n async verify(\n data: OAuth2AuthorizationCodeRequest,\n ) : Promise<OAuth2AuthorizationCodeRequestVerificationResult> {\n if (!data.client_id) {\n throw OAuth2ClientError.invalid();\n }\n\n // A name-identified client needs a realm hint to resolve deterministically\n // — every realm has a built-in `web` client, so `client_id=web` without a\n // realm would bind to an arbitrary realm's client (and, post realm-gate,\n // produce a confusing mismatch against a random realm). Require the hint.\n if (!isUUID(data.client_id) && !data.realm_id) {\n throw OAuth2RequestError.malformed('A realm is required to resolve a client by name.');\n }\n\n const client = await this.clientRepository.findOneByIdOrName(data.client_id, data.realm_id);\n if (!client) {\n throw OAuth2ClientError.invalid();\n }\n\n if (!client.active) {\n throw OAuth2ClientError.inactive();\n }\n\n // A non-null grant_types allowlist must cover the code flow — an RP\n // misconfiguration fails at the front door, not at code redemption.\n assertClientGrantAllowed(client, OAuth2TokenGrant.AUTHORIZATION_CODE);\n\n // OAuth 2.1 posture: a client with no registered redirect_uri pattern\n // can never be matched — reject outright instead of trusting whatever\n // redirect_uri the request carries (the server would otherwise issue a\n // code to an attacker-supplied URI).\n if (!client.redirectUri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n // Public clients MUST use PKCE (RFC 7636 §4.4.1, OAuth 2.1). Without\n // PKCE a public client's code flow has no second factor — anyone who\n // intercepts the redirect can redeem the code at /token. The code flow\n // is the only supported response type, so this holds unconditionally.\n if (isClientPublic(client) && !data.code_challenge) {\n throw OAuth2RequestError.malformed('PKCE code_challenge is required for public clients.');\n }\n\n // Public clients SHOULD include state to bind the redirect to the\n // initiating session and prevent CSRF (RFC 6749 §10.12). Confidential\n // clients are exempt because the /token exchange already authenticates\n // them via client_secret.\n if (isClientPublic(client) && !data.state) {\n throw OAuth2RequestError.malformed('state is required for public clients in the code flow.');\n }\n\n data.client_id = client.id;\n data.realm_id = client.realmId;\n\n const scopes = await this.scopeRepository.findByClientId(client.id);\n const scopeNames = scopes.map((scope) => scope.name);\n if (data.scope) {\n if (\n !hasOAuth2Scopes(scopeNames, data.scope) &&\n !hasOAuth2Scopes(data.scope, ScopeName.GLOBAL)\n ) {\n throw OAuth2ScopeError.insufficient();\n }\n } else {\n data.scope = scopeNames.join(' ');\n }\n\n // Verified only when the request's redirect_uri matched a registered\n // pattern (pattern-less clients were rejected above). A request without\n // a redirect_uri (e.g. the GET page render) stays unverified so\n // consumers never auto-redirect without a match.\n const redirectUriVerified = !!data.redirect_uri;\n if (data.redirect_uri) {\n const redirectUris = client.redirectUri.split(',');\n\n if (!isSimpleMatch(data.redirect_uri, redirectUris)) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n }\n\n return {\n data,\n client,\n scopes,\n redirectUriVerified,\n };\n }\n}\n"],"mappings":";;;;;;;;;AA2BA,IAAaW,yCAAb,MAAaA;CACCC;CAEAC;CAEV,YAAYC,KAAoD;EAC5D,KAAKF,mBAAmBE,IAAIF;EAC5B,KAAKC,kBAAkBC,IAAID;CAC/B;;;;IAMA,MAAME,OACFC,MAC0D;EAC1D,IAAI,CAACA,KAAKC,WACN,MAAMb,kBAAkBc,QAAO;EAOnC,IAAI,CAACf,OAAOa,KAAKC,SAAS,KAAK,CAACD,KAAKG,UACjC,MAAMb,mBAAmBc,UAAU,kDAAA;EAGvC,MAAMC,SAAS,MAAM,KAAKT,iBAAiBU,kBAAkBN,KAAKC,WAAWD,KAAKG,QAAQ;EAC1F,IAAI,CAACE,QACD,MAAMjB,kBAAkBc,QAAO;EAGnC,IAAI,CAACG,OAAOE,QACR,MAAMnB,kBAAkBoB,SAAQ;EAKpCd,yBAAyBW,QAAQb,iBAAiBiB,kBAAkB;EAMpE,IAAI,CAACJ,OAAOK,aACR,MAAMrB,iBAAiBsB,oBAAmB;EAO9C,IAAI1B,eAAeoB,MAAAA,KAAW,CAACL,KAAKY,gBAChC,MAAMtB,mBAAmBc,UAAU,qDAAA;EAOvC,IAAInB,eAAeoB,MAAAA,KAAW,CAACL,KAAKa,OAChC,MAAMvB,mBAAmBc,UAAU,wDAAA;EAGvCJ,KAAKC,YAAYI,OAAOS;EACxBd,KAAKG,WAAWE,OAAOU;EAEvB,MAAMC,SAAS,MAAM,KAAKnB,gBAAgBoB,eAAeZ,OAAOS,EAAE;EAClE,MAAMI,aAAaF,OAAOG,KAAKC,UAAUA,MAAMC,IAAI;EACnD,IAAIrB,KAAKoB,OAED;OAAA,CAAC3B,gBAAgByB,YAAYlB,KAAKoB,KAAK,KACvC,CAAC3B,gBAAgBO,KAAKoB,OAAOpC,UAAUsC,MAAM,GAE7C,MAAM/B,iBAAiBgC,aAAY;EAAA,OAGvCvB,KAAKoB,QAAQF,WAAWM,KAAK,GAAA;EAOjC,MAAMC,sBAAsB,CAAC,CAACzB,KAAK0B;EACnC,IAAI1B,KAAK0B,cAAc;GACnB,MAAMC,eAAetB,OAAOK,YAAYkB,MAAM,GAAA;GAE9C,IAAI,CAAC1C,cAAcc,KAAK0B,cAAcC,YAAAA,GAClC,MAAMtC,iBAAiBsB,oBAAmB;EAElD;EAEA,OAAO;GACHX;GACAK;GACAW;GACAS;EACJ;CACJ;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["EventName","EventRefType","EventScope","IdentityType","hasInstanceof","OAUTH2_ACCESS_DENIED_ERROR_INSTANCE","OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE","OAUTH2_MFA_REQUIRED_ERROR_INSTANCE","OAuth2AccessDeniedError","OAuth2AuthenticationContextClass","OAuth2AuthorizationPrompt","OAuth2AuthorizationResponseType","OAuth2GrantError","OAuth2LoginRequiredError","OAuth2MfaRequiredError","OAuth2RequestError","OAuth2ResponseTypeError","toIdentityPolicyData","DEFAULT_PROMPT_LOGIN_MAX_AGE","DEFAULT_MFA_FRESHNESS_MAX_AGE","OAuth2Authorization","codeIssuer","sessionManager","eventService","metrics","promptLoginMaxAge","mfaFreshnessMaxAge","mfaChallengeProvider","accessPolicyEvaluator","ctx","authorize","data","identity","options","result","authorizeInner","record","scope","OAUTH2","name","AUTHORIZE","refType","CLIENT","refId","client","id","client_id","clientId","actorType","type","actorId","actorName","realmId","realm_id","reason","builtIn","recordAuthorize","e","AUTHORIZE_FAILED","responseTypes","response_type","Array","isArray","split","responseType","CODE","unsupported","includes","redirect_uri","redirectUriMismatch","output","redirectUri","state","identityInvalid","realmMismatch","nowSeconds","Math","floor","Date","now","authTime","session","sessionId","findOneById","createdAt","getTime","USER","challenge","issueMaterial","required","mfaAt","challengeRequired","enrollmentRequired","acr_values","acrValues","MFA","mfaAtSeconds","stepUpRequired","prompts","prompt","LOGIN","reauthenticationRequired","max_age","maxAge","Number","isFinite","accessPolicyId","allowed","subject","evaluate","forClient","redirectUriVerified","undefined","codeEntity","issue","authMethod","authorizationCode"],"sources":["../../../../src/core/oauth2/authorization/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Identity, \n OAuth2AuthorizationCode, \n OAuth2AuthorizationCodeRequest, \n Session,\n} from '@authup/core-kit';\nimport {\n EventName, \n EventRefType, \n EventScope, \n IdentityType,\n} from '@authup/core-kit';\nimport { hasInstanceof } from '@authup/errors';\nimport {\n OAUTH2_ACCESS_DENIED_ERROR_INSTANCE,\n OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE,\n OAUTH2_MFA_REQUIRED_ERROR_INSTANCE,\n OAuth2AccessDeniedError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthorizationPrompt,\n OAuth2AuthorizationResponseType,\n OAuth2GrantError,\n OAuth2LoginRequiredError,\n OAuth2MfaRequiredError,\n OAuth2RequestError,\n OAuth2ResponseTypeError,\n} from '@authup/specs';\nimport type { IOAuth2AuthorizationCodeIssuer } from './code/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type {\n OAuth2AuthorizationManagerContext,\n OAuth2AuthorizationOptions,\n OAuth2AuthorizationResult,\n} from './types.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IEventService, IUserAuthenticatorChallengeProvider } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\n\nconst DEFAULT_PROMPT_LOGIN_MAX_AGE = 60;\n\n// Deliberately 60 (not 0, deviating from the plan-050 sketch): the hosted\n// challenge round-trip (stamp mfa_at → retry POST /authorize) takes seconds,\n// so a 0-window step-up could never be satisfied and would loop the ladder.\nconst DEFAULT_MFA_FRESHNESS_MAX_AGE = 60;\n\nexport class OAuth2Authorization {\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected sessionManager : ISessionManager;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected promptLoginMaxAge : number;\n\n protected mfaFreshnessMaxAge : number;\n\n protected mfaChallengeProvider? : IUserAuthenticatorChallengeProvider;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n constructor(ctx: OAuth2AuthorizationManagerContext) {\n this.codeIssuer = ctx.codeIssuer;\n this.sessionManager = ctx.sessionManager;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.promptLoginMaxAge = ctx.promptLoginMaxAge ?? DEFAULT_PROMPT_LOGIN_MAX_AGE;\n this.mfaFreshnessMaxAge = ctx.mfaFreshnessMaxAge ?? DEFAULT_MFA_FRESHNESS_MAX_AGE;\n this.mfaChallengeProvider = ctx.mfaChallengeProvider;\n }\n\n /**\n * Authorize with validated codeRequest.\n *\n * @param data\n * @param identity\n * @param options\n */\n async authorize(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n try {\n const result = await this.authorizeInner(data, identity, options);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: {\n reason: options.client?.builtIn ? 'autoConsent' : 'consent',\n ...(data.scope ? { scope: data.scope } : {}),\n },\n });\n this.metrics?.recordAuthorize('issued');\n\n return result;\n } catch (e) {\n if (hasInstanceof(e, OAUTH2_ACCESS_DENIED_ERROR_INSTANCE)) {\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE_FAILED,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: { reason: 'accessPolicy' },\n });\n this.metrics?.recordAuthorize('denied');\n } else if (hasInstanceof(e, OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('login_required');\n } else if (hasInstanceof(e, OAUTH2_MFA_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('mfa_required');\n } else {\n this.metrics?.recordAuthorize('error');\n }\n\n throw e;\n }\n }\n\n protected async authorizeInner(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n // OAuth 2.1 posture: only the authorization-code response type is\n // supported — implicit/hybrid (token, id_token, none) were dropped\n // (plan 042 item 3). Defense in depth behind the request validator.\n let responseTypes : string[] = [];\n if (data.response_type) {\n responseTypes = Array.isArray(data.response_type) ?\n data.response_type :\n data.response_type.split(' ');\n }\n\n for (const responseType of responseTypes) {\n if (responseType !== OAuth2AuthorizationResponseType.CODE) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n }\n\n if (!responseTypes.includes(OAuth2AuthorizationResponseType.CODE)) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n\n if (!data.redirect_uri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n const output : OAuth2AuthorizationResult = {\n redirectUri: data.redirect_uri,\n ...(data.state ? { state: data.state } : {}),\n };\n\n if (!identity) {\n throw OAuth2RequestError.identityInvalid();\n }\n\n // Realm binding: the code-request verifier stamped data.realm_id with the\n // resolved client's realm. The authenticated identity must belong to that\n // same realm — otherwise a lingering session for realm A could silently\n // mint a code/token for realm B's client (confused deputy). The error body\n // deliberately carries no identity data (no realm-enumeration oracle).\n // The comparison reads the scalar realmId column, NOT the realm\n // relation — the relation may simply not be loaded on the resolved\n // identity. An identity without a realmId fails closed the same way —\n // clean login_required, never a TypeError.\n if (\n data.realm_id &&\n identity.data.realmId !== data.realm_id\n ) {\n throw OAuth2LoginRequiredError.realmMismatch();\n }\n\n // Authentication time = the backing session's creation instant (NOT\n // refreshed_at — a token refresh must not reset it). Session-less flows\n // (e.g. HTTP Basic authorize) present live credentials on this request,\n // so the authentication time is \"now\".\n const nowSeconds = Math.floor(Date.now() / 1000);\n let authTime = nowSeconds;\n let session : Session | null = null;\n if (options.sessionId) {\n session = await this.sessionManager.findOneById(options.sessionId);\n if (session && session.createdAt) {\n authTime = Math.floor(new Date(session.createdAt).getTime() / 1000);\n }\n }\n\n // MFA backstop (plan 049) — the authoritative server-side gate; the\n // hosted UI's challenge step is convenience. The proof is session-bound\n // (mfa_at — stamped by the challenge endpoint or the password grant's\n // otp param), so a session-less flow (HTTP Basic) cannot carry one and\n // fails closed while the user holds a confirmed device. A user without\n // a device under mfaRequired is routed to inline enrollment.\n if (this.mfaChallengeProvider && identity.type === IdentityType.USER) {\n // requirement flags only — the interactive challenge material (the\n // webauthn nonce) is issued by the status endpoint, not this backstop.\n const challenge = await this.mfaChallengeProvider.challenge(\n identity.data.id,\n { issueMaterial: false },\n );\n if (challenge.required && !session?.mfaAt) {\n throw OAuth2MfaRequiredError.challengeRequired();\n }\n\n if (challenge.enrollmentRequired) {\n throw OAuth2MfaRequiredError.enrollmentRequired();\n }\n\n // Step-up (plan 050 stage 3): a requested `acr_values` containing\n // urn:authup:mfa is a TRIGGER (Auth0/Keycloak stance) — the proof\n // must additionally be FRESH (mfaFreshnessMaxAge window, mirroring\n // promptLoginMaxAge's absorb-the-round-trip semantics). Enforced\n // only while the user actually holds a factor — per OIDC Core\n // §5.5.1.1 acr is voluntary, so an unsatisfiable request degrades\n // to the achieved acr instead of bricking the RP.\n if (challenge.required && data.acr_values) {\n const acrValues = data.acr_values.split(' ');\n if (acrValues.includes(OAuth2AuthenticationContextClass.MFA)) {\n const mfaAtSeconds = session?.mfaAt ?\n Math.floor(new Date(session.mfaAt).getTime() / 1000) :\n null;\n if (mfaAtSeconds === null || nowSeconds - mfaAtSeconds > this.mfaFreshnessMaxAge) {\n throw OAuth2MfaRequiredError.stepUpRequired();\n }\n }\n }\n }\n\n // OIDC §3.1.2.1 prompt=login / max_age freshness (enforced only when\n // requested — a plain authorize never throws here). The hosted page\n // renders the login form for prompt=login; this is the server backstop.\n const prompts = data.prompt ? data.prompt.split(' ') : [];\n if (\n prompts.includes(OAuth2AuthorizationPrompt.LOGIN) &&\n nowSeconds - authTime > this.promptLoginMaxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n\n if (\n typeof data.max_age !== 'undefined' &&\n data.max_age !== null\n ) {\n const maxAge = Number(data.max_age);\n if (\n Number.isFinite(maxAge) &&\n nowSeconds - authTime > maxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n }\n\n // Application access policy (plan 052) — the last gate before\n // issuance: a denial is only revealed to a fully-authenticated\n // (incl. second-factor) identity. A client carrying a policy id with\n // no wired evaluator denies too (never silently allow a configured\n // gate). The redirect target rides the error only when the\n // redirect_uri was pattern-verified (RFC 6749 §4.1.2.1).\n if (options.client?.accessPolicyId) {\n let allowed = false;\n\n const subject = toIdentityPolicyData(identity);\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n options.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n throw OAuth2AccessDeniedError.forClient(\n options.redirectUriVerified && data.redirect_uri ?\n { redirectUri: data.redirect_uri, state: data.state ?? null } :\n undefined,\n );\n }\n }\n\n // The id_token is NOT minted here — the /token exchange mints it after\n // resolving the real backing session, so its `sid` is authoritative\n // (plan 042 item 6). The code carries the authentication instant.\n const codeEntity : OAuth2AuthorizationCode = await this.codeIssuer.issue(\n data,\n identity,\n {\n sessionId: options.sessionId,\n authTime,\n authMethod: session?.authMethod ?? null,\n },\n );\n\n output.authorizationCode = codeEntity.id;\n\n return output;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"module.mjs","names":["EventName","EventRefType","EventScope","IdentityType","hasInstanceof","OAUTH2_ACCESS_DENIED_ERROR_INSTANCE","OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE","OAUTH2_MFA_REQUIRED_ERROR_INSTANCE","OAuth2AccessDeniedError","OAuth2AuthenticationContextClass","OAuth2AuthorizationPrompt","OAuth2AuthorizationResponseType","OAuth2GrantError","OAuth2LoginRequiredError","OAuth2MfaRequiredError","OAuth2RequestError","OAuth2ResponseTypeError","toIdentityPolicyData","DEFAULT_PROMPT_LOGIN_MAX_AGE","DEFAULT_MFA_FRESHNESS_MAX_AGE","OAuth2Authorization","codeIssuer","sessionManager","eventService","metrics","promptLoginMaxAge","mfaFreshnessMaxAge","mfaChallengeProvider","accessPolicyEvaluator","ctx","authorize","data","identity","options","result","authorizeInner","record","scope","OAUTH2","name","AUTHORIZE","refType","CLIENT","refId","client","id","client_id","clientId","actorType","type","actorId","actorName","realmId","realm_id","reason","builtIn","recordAuthorize","e","AUTHORIZE_FAILED","responseTypes","response_type","Array","isArray","split","responseType","CODE","unsupported","includes","redirect_uri","redirectUriMismatch","output","redirectUri","state","identityInvalid","realmMismatch","nowSeconds","Math","floor","Date","now","authTime","session","sessionId","findOneById","createdAt","getTime","USER","challenge","issueMaterial","required","mfaAt","challengeRequired","enrollmentRequired","acr_values","acrValues","MFA","mfaAtSeconds","stepUpRequired","prompts","prompt","LOGIN","reauthenticationRequired","max_age","maxAge","Number","isFinite","accessPolicyId","allowed","subject","evaluate","forClient","redirectUriVerified","undefined","codeEntity","issue","authMethod","authorizationCode"],"sources":["../../../../src/core/oauth2/authorization/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2024.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Identity, \n OAuth2AuthorizationCode, \n OAuth2AuthorizationCodeRequest, \n Session,\n} from '@authup/core-kit';\nimport {\n EventName, \n EventRefType, \n EventScope, \n IdentityType,\n} from '@authup/core-kit';\nimport { hasInstanceof } from '@authup/errors';\nimport {\n OAUTH2_ACCESS_DENIED_ERROR_INSTANCE,\n OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE,\n OAUTH2_MFA_REQUIRED_ERROR_INSTANCE,\n OAuth2AccessDeniedError,\n OAuth2AuthenticationContextClass,\n OAuth2AuthorizationPrompt,\n OAuth2AuthorizationResponseType,\n OAuth2GrantError,\n OAuth2LoginRequiredError,\n OAuth2MfaRequiredError,\n OAuth2RequestError,\n OAuth2ResponseTypeError,\n} from '@authup/specs';\nimport type { IOAuth2AuthorizationCodeIssuer } from './code/index.ts';\nimport type { IOAuth2AccessPolicyEvaluator } from '../access-policy/index.ts';\nimport { toIdentityPolicyData } from '../../identity/permission/identity-policy-data.ts';\nimport type {\n OAuth2AuthorizationManagerContext,\n OAuth2AuthorizationOptions,\n OAuth2AuthorizationResult,\n} from './types.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IEventService, IUserAuthenticatorChallengeProvider } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\n\nconst DEFAULT_PROMPT_LOGIN_MAX_AGE = 60;\n\n// Deliberately 60 (not 0, deviating from the plan-050 sketch): the hosted\n// challenge round-trip (stamp mfa_at → retry POST /authorize) takes seconds,\n// so a 0-window step-up could never be satisfied and would loop the ladder.\nconst DEFAULT_MFA_FRESHNESS_MAX_AGE = 60;\n\nexport class OAuth2Authorization {\n protected codeIssuer : IOAuth2AuthorizationCodeIssuer;\n\n protected sessionManager : ISessionManager;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected promptLoginMaxAge : number;\n\n protected mfaFreshnessMaxAge : number;\n\n protected mfaChallengeProvider? : IUserAuthenticatorChallengeProvider;\n\n protected accessPolicyEvaluator? : IOAuth2AccessPolicyEvaluator;\n\n constructor(ctx: OAuth2AuthorizationManagerContext) {\n this.codeIssuer = ctx.codeIssuer;\n this.sessionManager = ctx.sessionManager;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.accessPolicyEvaluator = ctx.accessPolicyEvaluator;\n this.promptLoginMaxAge = ctx.promptLoginMaxAge ?? DEFAULT_PROMPT_LOGIN_MAX_AGE;\n this.mfaFreshnessMaxAge = ctx.mfaFreshnessMaxAge ?? DEFAULT_MFA_FRESHNESS_MAX_AGE;\n this.mfaChallengeProvider = ctx.mfaChallengeProvider;\n }\n\n /**\n * Authorize with validated codeRequest.\n *\n * @param data\n * @param identity\n * @param options\n */\n async authorize(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n try {\n const result = await this.authorizeInner(data, identity, options);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: {\n reason: options.client?.builtIn ? 'autoConsent' : 'consent',\n ...(data.scope ? { scope: data.scope } : {}),\n },\n });\n this.metrics?.recordAuthorize('issued');\n\n return result;\n } catch (e) {\n if (hasInstanceof(e, OAUTH2_ACCESS_DENIED_ERROR_INSTANCE)) {\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.AUTHORIZE_FAILED,\n refType: EventRefType.CLIENT,\n refId: options.client?.id ?? data.client_id ?? null,\n clientId: options.client?.id ?? data.client_id ?? null,\n actorType: identity.type,\n actorId: identity.data.id,\n actorName: identity.data.name,\n realmId: data.realm_id ?? null,\n data: { reason: 'accessPolicy' },\n });\n this.metrics?.recordAuthorize('denied');\n } else if (hasInstanceof(e, OAUTH2_LOGIN_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('login_required');\n } else if (hasInstanceof(e, OAUTH2_MFA_REQUIRED_ERROR_INSTANCE)) {\n this.metrics?.recordAuthorize('mfa_required');\n } else {\n this.metrics?.recordAuthorize('error');\n }\n\n throw e;\n }\n }\n\n protected async authorizeInner(\n data: OAuth2AuthorizationCodeRequest,\n identity: Identity,\n options: OAuth2AuthorizationOptions = {},\n ) : Promise<OAuth2AuthorizationResult> {\n // OAuth 2.1 posture: only the authorization-code response type is\n // supported — implicit/hybrid (token, id_token, none) were dropped\n // (plan 042 item 3). Defense in depth behind the request validator.\n let responseTypes : string[] = [];\n if (data.response_type) {\n responseTypes = Array.isArray(data.response_type) ?\n data.response_type :\n data.response_type.split(' ');\n }\n\n for (const responseType of responseTypes) {\n if (responseType !== OAuth2AuthorizationResponseType.CODE) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n }\n\n if (!responseTypes.includes(OAuth2AuthorizationResponseType.CODE)) {\n throw OAuth2ResponseTypeError.unsupported();\n }\n\n if (!data.redirect_uri) {\n throw OAuth2GrantError.redirectUriMismatch();\n }\n\n const output : OAuth2AuthorizationResult = {\n redirectUri: data.redirect_uri,\n ...(data.state ? { state: data.state } : {}),\n };\n\n if (!identity) {\n throw OAuth2RequestError.identityInvalid();\n }\n\n // Realm binding: the code-request verifier stamped data.realm_id with the\n // resolved client's realm. The authenticated identity must belong to that\n // same realm — otherwise a lingering session for realm A could silently\n // mint a code/token for realm B's client (confused deputy). The error body\n // deliberately carries no identity data (no realm-enumeration oracle).\n // The comparison reads the scalar realmId column, NOT the realm\n // relation — the relation may simply not be loaded on the resolved\n // identity. An identity without a realmId fails closed the same way —\n // clean login_required, never a TypeError.\n if (\n data.realm_id &&\n identity.data.realmId !== data.realm_id\n ) {\n throw OAuth2LoginRequiredError.realmMismatch();\n }\n\n // Authentication time = the backing session's creation instant (NOT\n // refreshed_at — a token refresh must not reset it). Session-less flows\n // (e.g. HTTP Basic authorize) present live credentials on this request,\n // so the authentication time is \"now\".\n const nowSeconds = Math.floor(Date.now() / 1000);\n let authTime = nowSeconds;\n let session : Session | null = null;\n if (options.sessionId) {\n session = await this.sessionManager.findOneById(options.sessionId);\n if (session && session.createdAt) {\n authTime = Math.floor(new Date(session.createdAt).getTime() / 1000);\n }\n }\n\n // MFA backstop (plan 049) — the authoritative server-side gate; the\n // hosted UI's challenge step is convenience. The proof is session-bound\n // (mfa_at — stamped by the challenge endpoint or the password grant's\n // otp param), so a session-less flow (HTTP Basic) cannot carry one and\n // fails closed while the user holds a confirmed device. A user without\n // a device under mfaRequired is routed to inline enrollment.\n if (this.mfaChallengeProvider && identity.type === IdentityType.USER) {\n // requirement flags only — the interactive challenge material (the\n // webauthn nonce) is issued by the status endpoint, not this backstop.\n const challenge = await this.mfaChallengeProvider.challenge(\n identity.data.id,\n { issueMaterial: false },\n );\n if (challenge.required && !session?.mfaAt) {\n throw OAuth2MfaRequiredError.challengeRequired();\n }\n\n if (challenge.enrollmentRequired) {\n throw OAuth2MfaRequiredError.enrollmentRequired();\n }\n\n // Step-up (plan 050 stage 3): a requested `acr_values` containing\n // urn:authup:mfa is a TRIGGER (Auth0/Keycloak stance) — the proof\n // must additionally be FRESH (mfaFreshnessMaxAge window, mirroring\n // promptLoginMaxAge's absorb-the-round-trip semantics). Enforced\n // only while the user actually holds a factor — per OIDC Core\n // §5.5.1.1 acr is voluntary, so an unsatisfiable request degrades\n // to the achieved acr instead of bricking the RP.\n if (challenge.required && data.acr_values) {\n const acrValues = data.acr_values.split(' ');\n if (acrValues.includes(OAuth2AuthenticationContextClass.MFA)) {\n const mfaAtSeconds = session?.mfaAt ?\n Math.floor(new Date(session.mfaAt).getTime() / 1000) :\n null;\n if (mfaAtSeconds === null || nowSeconds - mfaAtSeconds > this.mfaFreshnessMaxAge) {\n throw OAuth2MfaRequiredError.stepUpRequired();\n }\n }\n }\n }\n\n // OIDC §3.1.2.1 prompt=login / max_age freshness (enforced only when\n // requested — a plain authorize never throws here). The hosted page\n // renders the login form for prompt=login; this is the server backstop.\n const prompts = data.prompt ? data.prompt.split(' ') : [];\n if (\n prompts.includes(OAuth2AuthorizationPrompt.LOGIN) &&\n nowSeconds - authTime > this.promptLoginMaxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n\n if (\n typeof data.max_age !== 'undefined' &&\n data.max_age !== null\n ) {\n const maxAge = Number(data.max_age);\n if (\n Number.isFinite(maxAge) &&\n nowSeconds - authTime > maxAge\n ) {\n throw OAuth2LoginRequiredError.reauthenticationRequired();\n }\n }\n\n // Application access policy (plan 052) — the last gate before\n // issuance: a denial is only revealed to a fully-authenticated\n // (incl. second-factor) identity. A client carrying a policy id with\n // no wired evaluator denies too (never silently allow a configured\n // gate). The redirect target rides the error only when the\n // redirect_uri was pattern-verified (RFC 6749 §4.1.2.1).\n if (options.client?.accessPolicyId) {\n let allowed = false;\n\n const subject = toIdentityPolicyData(identity);\n if (this.accessPolicyEvaluator && subject) {\n allowed = await this.accessPolicyEvaluator.evaluate(\n options.client.accessPolicyId,\n subject,\n );\n }\n\n if (!allowed) {\n throw OAuth2AccessDeniedError.forClient(\n options.redirectUriVerified && data.redirect_uri ?\n { redirectUri: data.redirect_uri, state: data.state ?? null } :\n undefined,\n );\n }\n }\n\n // The id_token is NOT minted here — the /token exchange mints it after\n // resolving the real backing session, so its `sid` is authoritative\n // (plan 042 item 6). The code carries the authentication instant.\n const codeEntity : OAuth2AuthorizationCode = await this.codeIssuer.issue(\n data,\n identity,\n {\n sessionId: options.sessionId,\n authTime,\n authMethod: session?.authMethod ?? null,\n },\n );\n\n output.authorizationCode = codeEntity.id;\n\n return output;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"service.mjs","names":["isSimpleMatch","isUUID","EventName","EventRefType","EventScope","OAuth2TokenKind","OAuth2EndSessionService","tokenVerifier","sessionManager","clientRepository","realmRepository","eventService","hintGracePeriod","ctx","verify","data","hintVerified","sub","subKind","sessionId","hintRealmId","audiences","id_token_hint","payload","ignoreExpiry","skipActiveCheck","kind","ID_TOKEN","isWithinHintGraceWindow","sub_kind","sid","session_id","undefined","realm_id","aud","Array","isArray","filter","entry","clientId","client_id","length","client","realmKey","realm_name","realm","resolve","findOneByIdOrName","id","expected","includes","clientName","redirectUri","name","post_logout_redirect_uri","isValidPostLogoutRedirect","postLogoutRedirectUri","state","revoke","session","findOneById","record","scope","OAUTH2","LOGOUT","refType","SESSION","refId","actorType","actorId","realmId","revokedSessionId","exp","nowSeconds","Math","floor","Date","now","registered","candidate","parsed","URL","protocol","split"],"sources":["../../../../src/core/oauth2/end-session/service.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isSimpleMatch, isUUID } from '@authup/kit';\nimport type { Client, IdentityType } from '@authup/core-kit';\nimport { EventName, EventRefType, EventScope } from '@authup/core-kit';\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IOAuth2ClientRepository } from '../client/index.ts';\nimport type { IOAuth2TokenVerifier } from '../token/index.ts';\nimport type {\n IOAuth2EndSessionService,\n OAuth2EndSessionRequest,\n OAuth2EndSessionResult,\n OAuth2EndSessionServiceContext,\n} from './types.ts';\n\nexport class OAuth2EndSessionService implements IOAuth2EndSessionService {\n protected tokenVerifier: IOAuth2TokenVerifier;\n\n protected sessionManager: ISessionManager;\n\n protected clientRepository: IOAuth2ClientRepository;\n\n protected realmRepository: IRealmRepository;\n\n protected eventService?: IEventService;\n\n protected hintGracePeriod: number;\n\n constructor(ctx: OAuth2EndSessionServiceContext) {\n this.tokenVerifier = ctx.tokenVerifier;\n this.sessionManager = ctx.sessionManager;\n this.clientRepository = ctx.clientRepository;\n this.realmRepository = ctx.realmRepository;\n this.eventService = ctx.eventService;\n this.hintGracePeriod = ctx.hintGracePeriod ?? 0;\n }\n\n async verify(data: OAuth2EndSessionRequest): Promise<OAuth2EndSessionResult> {\n let hintVerified = false;\n let sub: string | undefined;\n let subKind: string | undefined;\n let sessionId: string | undefined;\n let hintRealmId: string | undefined;\n let audiences: string[] = [];\n\n if (data.id_token_hint) {\n try {\n // Signature + kind are the anchors; exp is skipped (a logout hint\n // is routinely expired) and the cache blocklist is irrelevant.\n const payload = await this.tokenVerifier.verify(data.id_token_hint, {\n ignoreExpiry: true,\n skipActiveCheck: true,\n });\n\n // MUST be an id_token — access/refresh tokens also carry\n // session_id, so accepting them would let a leaked access token\n // act as a logout weapon.\n if (payload.kind === OAuth2TokenKind.ID_TOKEN && this.isWithinHintGraceWindow(payload)) {\n hintVerified = true;\n sub = payload.sub;\n subKind = payload.sub_kind;\n sessionId = payload.sid ?? payload.session_id ?? undefined;\n hintRealmId = payload.realm_id ?? undefined;\n // aud may be a single string or an array (JWT spec).\n const { aud } = payload;\n if (Array.isArray(aud)) {\n audiences = aud.filter((entry): entry is string => typeof entry === 'string');\n } else if (typeof aud === 'string') {\n audiences = [aud];\n }\n }\n } catch {\n // forged / unverifiable hint → stays unverified (no revoke)\n }\n }\n\n // Resolve a single client for the aud cross-check and redirect\n // validation: the request's client_id, else the hint's sole audience\n // (ambiguous for a multi-aud hint).\n const clientId = data.client_id ?? (audiences.length === 1 ? audiences[0] : undefined);\n\n let client: Client | null = null;\n if (clientId) {\n // Scope a name-identified client to the request's realm hint; a\n // VERIFIED hint's own realm claim serves as fallback (claims from an\n // unverified hint are never trusted). No master fallback here\n // (unlike /token): a bogus hint must NOT silently resolve a\n // same-named client in master → null realm.\n const realmKey = data.realm_id ?? data.realm_name ?? (hintVerified ? hintRealmId : undefined);\n const realm = await this.realmRepository.resolve(realmKey, false);\n\n // Fail closed instead of dropping the realm predicate: a supplied\n // realm key that does not resolve never degrades to an unscoped\n // lookup, and a NAME-form client_id without any realm key is\n // ambiguous (every realm has a built-in `web` client — same rule as\n // the /authorize verifier). A UUID is globally unique and needs no\n // scope; the sole-aud-derived clientId is always a UUID.\n if (realm) {\n client = await this.clientRepository.findOneByIdOrName(clientId, realm.id);\n } else if (!realmKey && isUUID(clientId)) {\n client = await this.clientRepository.findOneByIdOrName(clientId);\n }\n }\n\n // aud/client_id cross-check: when the request supplies a client_id on a\n // verified hint, it MUST denote one of the hint's audiences. The aud is\n // the client UUID, so a name-form client_id is compared via its resolved\n // client's id. Fail closed: an aud-less hint, or a name that did not\n // resolve, counts as unverified.\n if (hintVerified && data.client_id) {\n const expected = isUUID(data.client_id) ? data.client_id : client?.id;\n if (audiences.length === 0 || !expected || !audiences.includes(expected)) {\n hintVerified = false;\n sub = undefined;\n subKind = undefined;\n sessionId = undefined;\n }\n }\n\n let clientName: string | undefined;\n let redirectUri: string | undefined;\n\n // Redirect validation is independent of hint verification — the\n // click-gated confirm page (unverified hint / hint-less request) still\n // honors a registered post-logout redirect.\n if (client) {\n clientName = client.name;\n if (\n data.post_logout_redirect_uri &&\n this.isValidPostLogoutRedirect(client.postLogoutRedirectUri, data.post_logout_redirect_uri)\n ) {\n redirectUri = data.post_logout_redirect_uri;\n }\n }\n\n return {\n hintVerified,\n ...(hintVerified ? {\n sub,\n subKind,\n sessionId,\n } : {}),\n ...(clientId ? { clientId } : {}),\n ...(clientName ? { clientName } : {}),\n ...(redirectUri ? { redirectUri, ...(data.state ? { state: data.state } : {}) } : {}),\n };\n }\n\n async revoke(sessionId: string, sub: string, subKind: string): Promise<boolean> {\n const session = await this.sessionManager.findOneById(sessionId);\n if (!session) {\n return false;\n }\n\n // Never revoke a session that does not belong to the hint's subject.\n if (session.sub !== sub || session.subKind !== subKind) {\n return false;\n }\n\n await this.sessionManager.revoke(sessionId);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.LOGOUT,\n refType: EventRefType.SESSION,\n refId: sessionId,\n actorType: session.subKind as `${IdentityType}`,\n actorId: session.sub,\n realmId: session.realmId,\n data: { revokedSessionId: sessionId },\n });\n\n return true;\n }\n\n /**\n * With a positive `hintGracePeriod`, an expired hint is honored only within\n * the window past `exp` — beyond it a leaked id_token stops being a\n * replayable remote logout (the hint counts as unverified; the click-gated\n * confirm page still works). 0 keeps spec/Keycloak parity: any expired hint.\n */\n protected isWithinHintGraceWindow(payload: OAuth2TokenPayload): boolean {\n if (this.hintGracePeriod <= 0) {\n return true;\n }\n\n // a bounded window without an exp claim to judge against fails closed\n if (typeof payload.exp !== 'number') {\n return false;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n return nowSeconds - payload.exp <= this.hintGracePeriod;\n }\n\n /**\n * A post-logout redirect is honored only when it is an absolute http(s) URL\n * that matches a registered client redirect pattern (defense against open\n * redirects to attacker-controlled URLs).\n */\n protected isValidPostLogoutRedirect(\n registered: string | null | undefined,\n candidate: string,\n ): boolean {\n let parsed: URL;\n try {\n parsed = new URL(candidate);\n } catch {\n return false;\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return false;\n }\n\n if (!registered) {\n return false;\n }\n\n return isSimpleMatch(candidate, registered.split(','));\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"service.mjs","names":["isSimpleMatch","isUUID","EventName","EventRefType","EventScope","OAuth2TokenKind","OAuth2EndSessionService","tokenVerifier","sessionManager","clientRepository","realmRepository","eventService","hintGracePeriod","ctx","verify","data","hintVerified","sub","subKind","sessionId","hintRealmId","audiences","id_token_hint","payload","ignoreExpiry","skipActiveCheck","kind","ID_TOKEN","isWithinHintGraceWindow","sub_kind","sid","session_id","undefined","realm_id","aud","Array","isArray","filter","entry","clientId","client_id","length","client","realmKey","realm_name","realm","resolve","findOneByIdOrName","id","expected","includes","clientName","redirectUri","name","post_logout_redirect_uri","isValidPostLogoutRedirect","postLogoutRedirectUri","state","revoke","session","findOneById","record","scope","OAUTH2","LOGOUT","refType","SESSION","refId","actorType","actorId","realmId","revokedSessionId","exp","nowSeconds","Math","floor","Date","now","registered","candidate","parsed","URL","protocol","split"],"sources":["../../../../src/core/oauth2/end-session/service.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { isSimpleMatch, isUUID } from '@authup/kit';\nimport type { Client, IdentityType } from '@authup/core-kit';\nimport { EventName, EventRefType, EventScope } from '@authup/core-kit';\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IEventService, IRealmRepository } from '../../entities/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport type { IOAuth2ClientRepository } from '../client/index.ts';\nimport type { IOAuth2TokenVerifier } from '../token/index.ts';\nimport type {\n IOAuth2EndSessionService,\n OAuth2EndSessionRequest,\n OAuth2EndSessionResult,\n OAuth2EndSessionServiceContext,\n} from './types.ts';\n\nexport class OAuth2EndSessionService implements IOAuth2EndSessionService {\n protected tokenVerifier: IOAuth2TokenVerifier;\n\n protected sessionManager: ISessionManager;\n\n protected clientRepository: IOAuth2ClientRepository;\n\n protected realmRepository: IRealmRepository;\n\n protected eventService?: IEventService;\n\n protected hintGracePeriod: number;\n\n constructor(ctx: OAuth2EndSessionServiceContext) {\n this.tokenVerifier = ctx.tokenVerifier;\n this.sessionManager = ctx.sessionManager;\n this.clientRepository = ctx.clientRepository;\n this.realmRepository = ctx.realmRepository;\n this.eventService = ctx.eventService;\n this.hintGracePeriod = ctx.hintGracePeriod ?? 0;\n }\n\n async verify(data: OAuth2EndSessionRequest): Promise<OAuth2EndSessionResult> {\n let hintVerified = false;\n let sub: string | undefined;\n let subKind: string | undefined;\n let sessionId: string | undefined;\n let hintRealmId: string | undefined;\n let audiences: string[] = [];\n\n if (data.id_token_hint) {\n try {\n // Signature + kind are the anchors; exp is skipped (a logout hint\n // is routinely expired) and the cache blocklist is irrelevant.\n const payload = await this.tokenVerifier.verify(data.id_token_hint, {\n ignoreExpiry: true,\n skipActiveCheck: true,\n });\n\n // MUST be an id_token — access/refresh tokens also carry\n // session_id, so accepting them would let a leaked access token\n // act as a logout weapon.\n if (payload.kind === OAuth2TokenKind.ID_TOKEN && this.isWithinHintGraceWindow(payload)) {\n hintVerified = true;\n sub = payload.sub;\n subKind = payload.sub_kind;\n sessionId = payload.sid ?? payload.session_id ?? undefined;\n hintRealmId = payload.realm_id ?? undefined;\n // aud may be a single string or an array (JWT spec).\n const { aud } = payload;\n if (Array.isArray(aud)) {\n audiences = aud.filter((entry): entry is string => typeof entry === 'string');\n } else if (typeof aud === 'string') {\n audiences = [aud];\n }\n }\n } catch {\n // forged / unverifiable hint → stays unverified (no revoke)\n }\n }\n\n // Resolve a single client for the aud cross-check and redirect\n // validation: the request's client_id, else the hint's sole audience\n // (ambiguous for a multi-aud hint).\n const clientId = data.client_id ?? (audiences.length === 1 ? audiences[0] : undefined);\n\n let client: Client | null = null;\n if (clientId) {\n // Scope a name-identified client to the request's realm hint; a\n // VERIFIED hint's own realm claim serves as fallback (claims from an\n // unverified hint are never trusted). No master fallback here\n // (unlike /token): a bogus hint must NOT silently resolve a\n // same-named client in master → null realm.\n const realmKey = data.realm_id ?? data.realm_name ?? (hintVerified ? hintRealmId : undefined);\n const realm = await this.realmRepository.resolve(realmKey, false);\n\n // Fail closed instead of dropping the realm predicate: a supplied\n // realm key that does not resolve never degrades to an unscoped\n // lookup, and a NAME-form client_id without any realm key is\n // ambiguous (every realm has a built-in `web` client — same rule as\n // the /authorize verifier). A UUID is globally unique and needs no\n // scope; the sole-aud-derived clientId is always a UUID.\n if (realm) {\n client = await this.clientRepository.findOneByIdOrName(clientId, realm.id);\n } else if (!realmKey && isUUID(clientId)) {\n client = await this.clientRepository.findOneByIdOrName(clientId);\n }\n }\n\n // aud/client_id cross-check: when the request supplies a client_id on a\n // verified hint, it MUST denote one of the hint's audiences. The aud is\n // the client UUID, so a name-form client_id is compared via its resolved\n // client's id. Fail closed: an aud-less hint, or a name that did not\n // resolve, counts as unverified.\n if (hintVerified && data.client_id) {\n const expected = isUUID(data.client_id) ? data.client_id : client?.id;\n if (audiences.length === 0 || !expected || !audiences.includes(expected)) {\n hintVerified = false;\n sub = undefined;\n subKind = undefined;\n sessionId = undefined;\n }\n }\n\n let clientName: string | undefined;\n let redirectUri: string | undefined;\n\n // Redirect validation is independent of hint verification — the\n // click-gated confirm page (unverified hint / hint-less request) still\n // honors a registered post-logout redirect.\n if (client) {\n clientName = client.name;\n if (\n data.post_logout_redirect_uri &&\n this.isValidPostLogoutRedirect(client.postLogoutRedirectUri, data.post_logout_redirect_uri)\n ) {\n redirectUri = data.post_logout_redirect_uri;\n }\n }\n\n return {\n hintVerified,\n ...(hintVerified ? {\n sub,\n subKind,\n sessionId,\n } : {}),\n ...(clientId ? { clientId } : {}),\n ...(clientName ? { clientName } : {}),\n ...(redirectUri ? { redirectUri, ...(data.state ? { state: data.state } : {}) } : {}),\n };\n }\n\n async revoke(sessionId: string, sub: string, subKind: string): Promise<boolean> {\n const session = await this.sessionManager.findOneById(sessionId);\n if (!session) {\n return false;\n }\n\n // Never revoke a session that does not belong to the hint's subject.\n if (session.sub !== sub || session.subKind !== subKind) {\n return false;\n }\n\n await this.sessionManager.revoke(sessionId);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.LOGOUT,\n refType: EventRefType.SESSION,\n refId: sessionId,\n actorType: session.subKind as `${IdentityType}`,\n actorId: session.sub,\n realmId: session.realmId,\n data: { revokedSessionId: sessionId },\n });\n\n return true;\n }\n\n /**\n * With a positive `hintGracePeriod`, an expired hint is honored only within\n * the window past `exp` — beyond it a leaked id_token stops being a\n * replayable remote logout (the hint counts as unverified; the click-gated\n * confirm page still works). 0 keeps spec/Keycloak parity: any expired hint.\n */\n protected isWithinHintGraceWindow(payload: OAuth2TokenPayload): boolean {\n if (this.hintGracePeriod <= 0) {\n return true;\n }\n\n // a bounded window without an exp claim to judge against fails closed\n if (typeof payload.exp !== 'number') {\n return false;\n }\n\n const nowSeconds = Math.floor(Date.now() / 1000);\n return nowSeconds - payload.exp <= this.hintGracePeriod;\n }\n\n /**\n * A post-logout redirect is honored only when it is an absolute http(s) URL\n * that matches a registered client redirect pattern (defense against open\n * redirects to attacker-controlled URLs).\n */\n protected isValidPostLogoutRedirect(\n registered: string | null | undefined,\n candidate: string,\n ): boolean {\n let parsed: URL;\n try {\n parsed = new URL(candidate);\n } catch {\n return false;\n }\n\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return false;\n }\n\n if (!registered) {\n return false;\n }\n\n return isSimpleMatch(candidate, registered.split(','));\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"refresh-token.mjs","names":["JWTError","OAuth2GrantError","OAuth2TokenKind","EventName","EventRefType","EventScope","buildOAuth2BearerTokenResponse","OAuth2BaseGrant","OAuth2RefreshTokenGrant","refreshTokenIssuer","tokenVerifier","tokenRepository","sessionTokenRepository","eventService","metrics","logger","gracePeriod","ctx","accessTokenIssuer","sessionManager","options","runWith","input","payload","verify","skipActiveCheck","jti","payloadPropertyInvalid","session_id","kind","REFRESH","invalid","row","findOneById","revokedAt","sessionId","now","Date","nowISO","toISOString","consumed","markRefreshConsumed","withinGrace","isWithinGraceWindow","revokeFamily","setInactive","exp","session","userAgent","ipAddress","refresh","refreshToken","refreshTokenPayload","issue","confirmation","cnf","user_agent","remote_address","buildExp","parent_id","accessToken","accessTokenPayload","refresh_token_id","current","consumedAt","getTime","chainAdvanced","hasConsumedChild","at","warn","record","scope","OAUTH2","name","REFRESH_REPLAY_DETECTED","refType","SESSION","refId","data","recordRefreshReplay","rows","revokeBySessionId","Promise","allSettled","map","id","Math","floor","expiresAt","revoke"],"sources":["../../../../src/core/oauth2/grant-types/refresh-token.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenGrantResponse, OAuth2TokenPayload } from '@authup/specs';\nimport { JWTError, OAuth2GrantError, OAuth2TokenKind } from '@authup/specs';\nimport { EventName, EventRefType, EventScope } from '@authup/core-kit';\nimport type { Logger } from '@authup/server-kit';\nimport type { IEventService } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport { buildOAuth2BearerTokenResponse } from '../response/index.ts';\nimport type { ISessionTokenRepository } from '../session-token/index.ts';\nimport type { IOAuth2TokenIssuer, IOAuth2TokenRepository, IOAuth2TokenVerifier } from '../token/index.ts';\nimport { OAuth2BaseGrant } from './base.ts';\nimport type { IOAuth2Grant, OAuth2GrantRunWIthOptions, OAuth2RefreshTokenGrantContext } from './types.ts';\n\nexport class OAuth2RefreshTokenGrant extends OAuth2BaseGrant<string | OAuth2TokenPayload> implements IOAuth2Grant {\n protected refreshTokenIssuer : IOAuth2TokenIssuer;\n\n protected tokenVerifier : IOAuth2TokenVerifier;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n protected sessionTokenRepository : ISessionTokenRepository;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected logger? : Logger;\n\n protected gracePeriod : number;\n\n constructor(ctx: OAuth2RefreshTokenGrantContext) {\n super({\n accessTokenIssuer: ctx.accessTokenIssuer,\n sessionManager: ctx.sessionManager,\n });\n\n this.refreshTokenIssuer = ctx.refreshTokenIssuer;\n this.tokenVerifier = ctx.tokenVerifier;\n this.tokenRepository = ctx.tokenRepository;\n this.sessionTokenRepository = ctx.sessionTokenRepository;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.logger = ctx.logger;\n this.gracePeriod = ctx.options?.gracePeriod ?? 0;\n }\n\n async runWith(\n input: string | OAuth2TokenPayload,\n options: OAuth2GrantRunWIthOptions = {},\n ) : Promise<OAuth2TokenGrantResponse> {\n let payload : OAuth2TokenPayload;\n if (typeof input === 'string') {\n // The auth_session_tokens row — not the cache blocklist — is the\n // authority for refresh-token validity, so skip the active check.\n payload = await this.tokenVerifier.verify(input, { skipActiveCheck: true });\n } else {\n payload = input;\n }\n\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n if (!payload.session_id) {\n throw JWTError.payloadPropertyInvalid('session_id');\n }\n\n // Explicit kind guard (defense in depth). Authup signs access tokens\n // and the MFA-pending login ticket with the same keys; only a refresh\n // token may be exchanged here. The ticket is also rejected implicitly\n // (it writes no auth_session_tokens row), but do not rely on that\n // second-order fact alone.\n if (payload.kind && payload.kind !== OAuth2TokenKind.REFRESH) {\n throw OAuth2GrantError.invalid('unexpected token kind');\n }\n\n const row = await this.sessionTokenRepository.findOneById(payload.jti);\n if (!row || row.kind !== 'refresh') {\n // Row missing = expired-and-swept or a pre-rotation legacy token\n // (hard cutover). Either way the client must re-authenticate.\n throw OAuth2GrantError.invalid('refresh token is unknown');\n }\n if (row.revokedAt) {\n throw OAuth2GrantError.invalid('refresh token has been revoked');\n }\n if (row.sessionId !== payload.session_id) {\n // The row is written with the token's own session_id and jti is\n // globally unique, so a mismatch means corruption / jti reuse — fail\n // closed rather than refresh or revoke against the wrong session.\n throw OAuth2GrantError.invalid('refresh token session mismatch');\n }\n\n const now = new Date();\n const nowISO = now.toISOString();\n\n const consumed = await this.sessionTokenRepository.markRefreshConsumed(payload.jti, nowISO);\n if (!consumed) {\n // Lost the atomic consume — either a replay of an already-rotated\n // token, or a benign concurrent-refresh race the grace window\n // absorbs.\n const withinGrace = await this.isWithinGraceWindow(payload.jti, now);\n if (!withinGrace) {\n await this.revokeFamily(row.sessionId, payload.jti, nowISO);\n throw OAuth2GrantError.invalid('refresh token replay detected; session revoked');\n }\n }\n\n // Blocklist the presented refresh token in the cache so introspection\n // reports it inactive. Deliberately NOT a DB revoke — that would set\n // revoked_at and break the grace window.\n await this.tokenRepository.setInactive(payload.jti, payload.exp);\n\n const session = await this.sessionManager.findOneById(payload.session_id);\n if (!session) {\n throw JWTError.payloadPropertyInvalid('session_id');\n }\n\n await this.sessionManager.verify(session);\n\n if (options.userAgent) {\n session.userAgent = options.userAgent;\n }\n if (options.ipAddress) {\n session.ipAddress = options.ipAddress;\n }\n await this.sessionManager.refresh(session);\n\n const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue({\n ...payload,\n ...(options.confirmation ? { cnf: options.confirmation } : {}),\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n exp: this.refreshTokenIssuer.buildExp(),\n parent_id: payload.jti,\n });\n\n const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue({\n ...payload,\n ...(options.confirmation ? { cnf: options.confirmation } : {}),\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n exp: this.accessTokenIssuer.buildExp(),\n refresh_token_id: refreshTokenPayload.jti,\n });\n\n return buildOAuth2BearerTokenResponse({\n accessToken,\n accessTokenPayload,\n refreshToken,\n refreshTokenPayload,\n });\n }\n\n /**\n * The grace window (opt-in via `tokenRefreshGracePeriod`) absorbs benign\n * concurrent / multi-tab refreshes of the SAME still-current token. It is\n * scoped to the chain tip: a token whose descendant has already been\n * consumed is a stale ancestor (a real replay), never a grace re-use.\n */\n protected async isWithinGraceWindow(jti: string, now: Date) : Promise<boolean> {\n if (this.gracePeriod <= 0) {\n return false;\n }\n\n const current = await this.sessionTokenRepository.findOneById(jti);\n if (\n !current ||\n current.revokedAt !== null ||\n current.consumedAt === null ||\n (now.getTime() - new Date(current.consumedAt).getTime()) > this.gracePeriod * 1_000\n ) {\n return false;\n }\n\n // Grace applies ONLY to the most-recent consumed token: a consumed\n // descendant proves the rotation advanced past this one (stale replay).\n const chainAdvanced = await this.sessionTokenRepository.hasConsumedChild(jti);\n return !chainAdvanced;\n }\n\n /**\n * RFC 6819 §5.2.2.3 reaction strategy — revoke the whole token family\n * (the auth_sessions row) on replay: soft-revoke every session-token row,\n * blocklist their jtis in the cache, and delete the session so its access\n * tokens stop verifying on authup's own API.\n */\n protected async revokeFamily(sessionId: string, jti: string, at: string) : Promise<void> {\n this.logger?.warn('OAuth2 refresh token replay detected; revoking session', {\n sessionId,\n jti,\n });\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.REFRESH_REPLAY_DETECTED,\n refType: EventRefType.SESSION,\n refId: sessionId,\n data: { jti },\n });\n this.metrics?.recordRefreshReplay();\n\n // Blocklisting is best-effort (cache) — settle all of them so one cache\n // failure cannot skip the rest or, worse, abort the session delete\n // below (the load-bearing part of the revocation). Pin each TTL to the\n // token's real expiry (unix seconds) so a long-lived refresh token\n // cannot resurface as active via introspection after the fallback 1h.\n const rows = await this.sessionTokenRepository.revokeBySessionId(sessionId, at);\n await Promise.allSettled(rows.map((row) => this.tokenRepository.setInactive(\n row.id,\n Math.floor(new Date(row.expiresAt).getTime() / 1_000),\n )));\n\n await this.sessionManager.revoke(sessionId);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"refresh-token.mjs","names":["JWTError","OAuth2GrantError","OAuth2TokenKind","EventName","EventRefType","EventScope","buildOAuth2BearerTokenResponse","OAuth2BaseGrant","OAuth2RefreshTokenGrant","refreshTokenIssuer","tokenVerifier","tokenRepository","sessionTokenRepository","eventService","metrics","logger","gracePeriod","ctx","accessTokenIssuer","sessionManager","options","runWith","input","payload","verify","skipActiveCheck","jti","payloadPropertyInvalid","session_id","kind","REFRESH","invalid","row","findOneById","revokedAt","sessionId","now","Date","nowISO","toISOString","consumed","markRefreshConsumed","withinGrace","isWithinGraceWindow","revokeFamily","setInactive","exp","session","userAgent","ipAddress","refresh","refreshToken","refreshTokenPayload","issue","confirmation","cnf","user_agent","remote_address","buildExp","parent_id","accessToken","accessTokenPayload","refresh_token_id","current","consumedAt","getTime","chainAdvanced","hasConsumedChild","at","warn","record","scope","OAUTH2","name","REFRESH_REPLAY_DETECTED","refType","SESSION","refId","data","recordRefreshReplay","rows","revokeBySessionId","Promise","allSettled","map","id","Math","floor","expiresAt","revoke"],"sources":["../../../../src/core/oauth2/grant-types/refresh-token.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenGrantResponse, OAuth2TokenPayload } from '@authup/specs';\nimport { JWTError, OAuth2GrantError, OAuth2TokenKind } from '@authup/specs';\nimport { EventName, EventRefType, EventScope } from '@authup/core-kit';\nimport type { Logger } from '@authup/server-kit';\nimport type { IEventService } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport { buildOAuth2BearerTokenResponse } from '../response/index.ts';\nimport type { ISessionTokenRepository } from '../session-token/index.ts';\nimport type { IOAuth2TokenIssuer, IOAuth2TokenRepository, IOAuth2TokenVerifier } from '../token/index.ts';\nimport { OAuth2BaseGrant } from './base.ts';\nimport type { IOAuth2Grant, OAuth2GrantRunWIthOptions, OAuth2RefreshTokenGrantContext } from './types.ts';\n\nexport class OAuth2RefreshTokenGrant extends OAuth2BaseGrant<string | OAuth2TokenPayload> implements IOAuth2Grant {\n protected refreshTokenIssuer : IOAuth2TokenIssuer;\n\n protected tokenVerifier : IOAuth2TokenVerifier;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n protected sessionTokenRepository : ISessionTokenRepository;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n protected logger? : Logger;\n\n protected gracePeriod : number;\n\n constructor(ctx: OAuth2RefreshTokenGrantContext) {\n super({\n accessTokenIssuer: ctx.accessTokenIssuer,\n sessionManager: ctx.sessionManager,\n });\n\n this.refreshTokenIssuer = ctx.refreshTokenIssuer;\n this.tokenVerifier = ctx.tokenVerifier;\n this.tokenRepository = ctx.tokenRepository;\n this.sessionTokenRepository = ctx.sessionTokenRepository;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n this.logger = ctx.logger;\n this.gracePeriod = ctx.options?.gracePeriod ?? 0;\n }\n\n async runWith(\n input: string | OAuth2TokenPayload,\n options: OAuth2GrantRunWIthOptions = {},\n ) : Promise<OAuth2TokenGrantResponse> {\n let payload : OAuth2TokenPayload;\n if (typeof input === 'string') {\n // The auth_session_tokens row — not the cache blocklist — is the\n // authority for refresh-token validity, so skip the active check.\n payload = await this.tokenVerifier.verify(input, { skipActiveCheck: true });\n } else {\n payload = input;\n }\n\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n if (!payload.session_id) {\n throw JWTError.payloadPropertyInvalid('session_id');\n }\n\n // Explicit kind guard (defense in depth). Authup signs access tokens\n // and the MFA-pending login ticket with the same keys; only a refresh\n // token may be exchanged here. The ticket is also rejected implicitly\n // (it writes no auth_session_tokens row), but do not rely on that\n // second-order fact alone.\n if (payload.kind && payload.kind !== OAuth2TokenKind.REFRESH) {\n throw OAuth2GrantError.invalid('unexpected token kind');\n }\n\n const row = await this.sessionTokenRepository.findOneById(payload.jti);\n if (!row || row.kind !== 'refresh') {\n // Row missing = expired-and-swept or a pre-rotation legacy token\n // (hard cutover). Either way the client must re-authenticate.\n throw OAuth2GrantError.invalid('refresh token is unknown');\n }\n if (row.revokedAt) {\n throw OAuth2GrantError.invalid('refresh token has been revoked');\n }\n if (row.sessionId !== payload.session_id) {\n // The row is written with the token's own session_id and jti is\n // globally unique, so a mismatch means corruption / jti reuse — fail\n // closed rather than refresh or revoke against the wrong session.\n throw OAuth2GrantError.invalid('refresh token session mismatch');\n }\n\n const now = new Date();\n const nowISO = now.toISOString();\n\n const consumed = await this.sessionTokenRepository.markRefreshConsumed(payload.jti, nowISO);\n if (!consumed) {\n // Lost the atomic consume — either a replay of an already-rotated\n // token, or a benign concurrent-refresh race the grace window\n // absorbs.\n const withinGrace = await this.isWithinGraceWindow(payload.jti, now);\n if (!withinGrace) {\n await this.revokeFamily(row.sessionId, payload.jti, nowISO);\n throw OAuth2GrantError.invalid('refresh token replay detected; session revoked');\n }\n }\n\n // Blocklist the presented refresh token in the cache so introspection\n // reports it inactive. Deliberately NOT a DB revoke — that would set\n // revoked_at and break the grace window.\n await this.tokenRepository.setInactive(payload.jti, payload.exp);\n\n const session = await this.sessionManager.findOneById(payload.session_id);\n if (!session) {\n throw JWTError.payloadPropertyInvalid('session_id');\n }\n\n await this.sessionManager.verify(session);\n\n if (options.userAgent) {\n session.userAgent = options.userAgent;\n }\n if (options.ipAddress) {\n session.ipAddress = options.ipAddress;\n }\n await this.sessionManager.refresh(session);\n\n const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue({\n ...payload,\n ...(options.confirmation ? { cnf: options.confirmation } : {}),\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n exp: this.refreshTokenIssuer.buildExp(),\n parent_id: payload.jti,\n });\n\n const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue({\n ...payload,\n ...(options.confirmation ? { cnf: options.confirmation } : {}),\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n exp: this.accessTokenIssuer.buildExp(),\n refresh_token_id: refreshTokenPayload.jti,\n });\n\n return buildOAuth2BearerTokenResponse({\n accessToken,\n accessTokenPayload,\n refreshToken,\n refreshTokenPayload,\n });\n }\n\n /**\n * The grace window (opt-in via `tokenRefreshGracePeriod`) absorbs benign\n * concurrent / multi-tab refreshes of the SAME still-current token. It is\n * scoped to the chain tip: a token whose descendant has already been\n * consumed is a stale ancestor (a real replay), never a grace re-use.\n */\n protected async isWithinGraceWindow(jti: string, now: Date) : Promise<boolean> {\n if (this.gracePeriod <= 0) {\n return false;\n }\n\n const current = await this.sessionTokenRepository.findOneById(jti);\n if (\n !current ||\n current.revokedAt !== null ||\n current.consumedAt === null ||\n (now.getTime() - new Date(current.consumedAt).getTime()) > this.gracePeriod * 1_000\n ) {\n return false;\n }\n\n // Grace applies ONLY to the most-recent consumed token: a consumed\n // descendant proves the rotation advanced past this one (stale replay).\n const chainAdvanced = await this.sessionTokenRepository.hasConsumedChild(jti);\n return !chainAdvanced;\n }\n\n /**\n * RFC 6819 §5.2.2.3 reaction strategy — revoke the whole token family\n * (the auth_sessions row) on replay: soft-revoke every session-token row,\n * blocklist their jtis in the cache, and delete the session so its access\n * tokens stop verifying on authup's own API.\n */\n protected async revokeFamily(sessionId: string, jti: string, at: string) : Promise<void> {\n this.logger?.warn('OAuth2 refresh token replay detected; revoking session', {\n sessionId,\n jti,\n });\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.REFRESH_REPLAY_DETECTED,\n refType: EventRefType.SESSION,\n refId: sessionId,\n data: { jti },\n });\n this.metrics?.recordRefreshReplay();\n\n // Blocklisting is best-effort (cache) — settle all of them so one cache\n // failure cannot skip the rest or, worse, abort the session delete\n // below (the load-bearing part of the revocation). Pin each TTL to the\n // token's real expiry (unix seconds) so a long-lived refresh token\n // cannot resurface as active via introspection after the fallback 1h.\n const rows = await this.sessionTokenRepository.revokeBySessionId(sessionId, at);\n await Promise.allSettled(rows.map((row) => this.tokenRepository.setInactive(\n row.id,\n Math.floor(new Date(row.expiresAt).getTime() / 1_000),\n )));\n\n await this.sessionManager.revoke(sessionId);\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["OAuth2GrantError","OAuth2SubKind","OAuth2TokenGrant","EventName","EventRefType","EventScope","IdentityType","ScopeName","SessionAuthMethod","deriveAmrAcr","buildOAuth2BearerTokenResponse","OAuth2MfaLoginService","sessionManager","ticketIssuer","accessTokenIssuer","refreshTokenIssuer","tokenRepository","eventService","metrics","ctx","issueTicket","input","options","user","clientId","undefined","expiresAt","buildExp","session","create","userAgent","ipAddress","realmId","sub","id","subKind","USER","mfaAt","authMethod","PASSWORD","Date","toISOString","token","payload","issue","client_id","session_id","user_agent","remote_address","sub_kind","realm_id","realm_name","realm","name","confirmation","cnf","exp","expiresIn","Math","floor","now","complete","ticket","refresh","amrAcr","jti","claimed","claimInactive","invalid","issuePayload","scope","GLOBAL","accessToken","accessTokenPayload","refreshToken","refreshTokenPayload","record","OAUTH2","LOGIN","refType","SESSION","refId","actorType","actorId","actorName","userName","requestIpAddress","requestUserAgent","data","grantType","sessionId","recordLogin"],"sources":["../../../../src/core/oauth2/mfa-login/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenGrantResponse, OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2GrantError, OAuth2SubKind, OAuth2TokenGrant } from '@authup/specs';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityType,\n ScopeName,\n SessionAuthMethod,\n} from '@authup/core-kit';\nimport type { IEventService } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport { deriveAmrAcr } from '../authorization/helpers.ts';\nimport { buildOAuth2BearerTokenResponse } from '../response/index.ts';\nimport type { IOAuth2TokenIssuer, IOAuth2TokenRepository } from '../token/index.ts';\nimport type {\n IOAuth2MfaLoginService,\n OAuth2MfaLoginCompleteInput,\n OAuth2MfaLoginServiceContext,\n OAuth2MfaLoginTicket,\n OAuth2MfaLoginTicketIssueInput,\n OAuth2MfaLoginTicketIssueOptions,\n} from './types.ts';\n\nexport class OAuth2MfaLoginService implements IOAuth2MfaLoginService {\n protected sessionManager : ISessionManager;\n\n protected ticketIssuer : IOAuth2TokenIssuer;\n\n protected accessTokenIssuer : IOAuth2TokenIssuer;\n\n protected refreshTokenIssuer : IOAuth2TokenIssuer;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n constructor(ctx: OAuth2MfaLoginServiceContext) {\n this.sessionManager = ctx.sessionManager;\n this.ticketIssuer = ctx.ticketIssuer;\n this.accessTokenIssuer = ctx.accessTokenIssuer;\n this.refreshTokenIssuer = ctx.refreshTokenIssuer;\n this.tokenRepository = ctx.tokenRepository;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n }\n\n async issueTicket(\n input: OAuth2MfaLoginTicketIssueInput,\n options: OAuth2MfaLoginTicketIssueOptions = {},\n ) : Promise<OAuth2MfaLoginTicket> {\n const { user } = input;\n const clientId = input.clientId ?? undefined;\n\n // One expiration instant for BOTH artifacts — the pending session\n // lives exactly as long as the ticket (an abandoned login\n // self-expires and is swept with the regular session sweep;\n // completion extends it to the full lifetime). Computed once so\n // the two cannot drift.\n const expiresAt = this.ticketIssuer.buildExp();\n\n const session = await this.sessionManager.create({\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n realmId: user.realmId,\n clientId,\n sub: user.id,\n subKind: IdentityType.USER,\n mfaAt: null,\n authMethod: SessionAuthMethod.PASSWORD,\n expiresAt: new Date(expiresAt * 1000).toISOString(),\n });\n\n // No scope, no role claims — the ticket is not a bearer; it only\n // unlocks the challenge routes (which opt in by kind).\n const [token, payload] = await this.ticketIssuer.issue({\n client_id: clientId,\n session_id: session.id,\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n sub: user.id,\n sub_kind: OAuth2SubKind.USER,\n realm_id: user.realmId,\n realm_name: user.realm?.name,\n ...(input.confirmation ? { cnf: input.confirmation } : {}),\n exp: expiresAt,\n });\n\n return {\n token,\n expiresIn: payload.exp ?\n payload.exp - Math.floor(Date.now() / 1000) :\n 0,\n };\n }\n\n async complete(input: OAuth2MfaLoginCompleteInput) : Promise<OAuth2TokenGrantResponse> {\n const { ticket } = input;\n\n // Extend the ticket-scoped pending session to the regular session\n // lifetime — the login is complete now.\n const session = await this.sessionManager.refresh(input.session);\n\n // mfaAt was stamped inside the verify unit of work, so the claims\n // advertise the completed factor (amr +otp, acr urn:authup:mfa).\n const amrAcr = deriveAmrAcr(session);\n\n // Single use: atomically claim the ticket BEFORE minting so two\n // concurrent completions (a mixed-kind user presenting two distinct\n // fresh factors) cannot both issue a token pair — `claimInactive` is a\n // set-if-absent write, so exactly one racer wins. The trade-off vs.\n // claiming after minting is that a mint failure burns the ticket — the\n // safer bias for a single-use login credential (the user re-authenticates).\n if (ticket.jti) {\n const claimed = await this.tokenRepository.claimInactive(ticket.jti, ticket.exp);\n if (!claimed) {\n throw OAuth2GrantError.invalid('mfa login ticket has already been used');\n }\n }\n\n const issuePayload : Partial<OAuth2TokenPayload> = {\n client_id: session.clientId ?? undefined,\n session_id: session.id,\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n scope: ScopeName.GLOBAL,\n sub: session.sub,\n sub_kind: OAuth2SubKind.USER,\n realm_id: session.realmId,\n realm_name: ticket.realm_name,\n ...(ticket.cnf ? { cnf: ticket.cnf } : {}),\n ...amrAcr,\n };\n\n const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue(issuePayload);\n const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue(issuePayload);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.LOGIN,\n refType: EventRefType.SESSION,\n refId: session.id,\n clientId: session.clientId ?? null,\n actorType: IdentityType.USER,\n actorId: session.sub,\n actorName: input.userName ?? null,\n realmId: session.realmId,\n requestIpAddress: session.ipAddress ?? null,\n requestUserAgent: session.userAgent ?? null,\n data: {\n grantType: OAuth2TokenGrant.PASSWORD,\n sessionId: session.id,\n },\n });\n this.metrics?.recordLogin('success');\n\n return buildOAuth2BearerTokenResponse({\n accessToken,\n accessTokenPayload,\n refreshToken,\n refreshTokenPayload,\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"module.mjs","names":["OAuth2GrantError","OAuth2SubKind","OAuth2TokenGrant","EventName","EventRefType","EventScope","IdentityType","ScopeName","SessionAuthMethod","deriveAmrAcr","buildOAuth2BearerTokenResponse","OAuth2MfaLoginService","sessionManager","ticketIssuer","accessTokenIssuer","refreshTokenIssuer","tokenRepository","eventService","metrics","ctx","issueTicket","input","options","user","clientId","undefined","expiresAt","buildExp","session","create","userAgent","ipAddress","realmId","sub","id","subKind","USER","mfaAt","authMethod","PASSWORD","Date","toISOString","token","payload","issue","client_id","session_id","user_agent","remote_address","sub_kind","realm_id","realm_name","realm","name","confirmation","cnf","exp","expiresIn","Math","floor","now","complete","ticket","refresh","amrAcr","jti","claimed","claimInactive","invalid","issuePayload","scope","GLOBAL","accessToken","accessTokenPayload","refreshToken","refreshTokenPayload","record","OAUTH2","LOGIN","refType","SESSION","refId","actorType","actorId","actorName","userName","requestIpAddress","requestUserAgent","data","grantType","sessionId","recordLogin"],"sources":["../../../../src/core/oauth2/mfa-login/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenGrantResponse, OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2GrantError, OAuth2SubKind, OAuth2TokenGrant } from '@authup/specs';\nimport {\n EventName,\n EventRefType,\n EventScope,\n IdentityType,\n ScopeName,\n SessionAuthMethod,\n} from '@authup/core-kit';\nimport type { IEventService } from '../../entities/index.ts';\nimport type { IAuthFlowMetrics } from '../../metrics/index.ts';\nimport type { ISessionManager } from '../../authentication/index.ts';\nimport { deriveAmrAcr } from '../authorization/helpers.ts';\nimport { buildOAuth2BearerTokenResponse } from '../response/index.ts';\nimport type { IOAuth2TokenIssuer, IOAuth2TokenRepository } from '../token/index.ts';\nimport type {\n IOAuth2MfaLoginService,\n OAuth2MfaLoginCompleteInput,\n OAuth2MfaLoginServiceContext,\n OAuth2MfaLoginTicket,\n OAuth2MfaLoginTicketIssueInput,\n OAuth2MfaLoginTicketIssueOptions,\n} from './types.ts';\n\nexport class OAuth2MfaLoginService implements IOAuth2MfaLoginService {\n protected sessionManager : ISessionManager;\n\n protected ticketIssuer : IOAuth2TokenIssuer;\n\n protected accessTokenIssuer : IOAuth2TokenIssuer;\n\n protected refreshTokenIssuer : IOAuth2TokenIssuer;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n protected eventService? : IEventService;\n\n protected metrics? : IAuthFlowMetrics;\n\n constructor(ctx: OAuth2MfaLoginServiceContext) {\n this.sessionManager = ctx.sessionManager;\n this.ticketIssuer = ctx.ticketIssuer;\n this.accessTokenIssuer = ctx.accessTokenIssuer;\n this.refreshTokenIssuer = ctx.refreshTokenIssuer;\n this.tokenRepository = ctx.tokenRepository;\n this.eventService = ctx.eventService;\n this.metrics = ctx.metrics;\n }\n\n async issueTicket(\n input: OAuth2MfaLoginTicketIssueInput,\n options: OAuth2MfaLoginTicketIssueOptions = {},\n ) : Promise<OAuth2MfaLoginTicket> {\n const { user } = input;\n const clientId = input.clientId ?? undefined;\n\n // One expiration instant for BOTH artifacts — the pending session\n // lives exactly as long as the ticket (an abandoned login\n // self-expires and is swept with the regular session sweep;\n // completion extends it to the full lifetime). Computed once so\n // the two cannot drift.\n const expiresAt = this.ticketIssuer.buildExp();\n\n const session = await this.sessionManager.create({\n userAgent: options.userAgent,\n ipAddress: options.ipAddress,\n realmId: user.realmId,\n clientId,\n sub: user.id,\n subKind: IdentityType.USER,\n mfaAt: null,\n authMethod: SessionAuthMethod.PASSWORD,\n expiresAt: new Date(expiresAt * 1000).toISOString(),\n });\n\n // No scope, no role claims — the ticket is not a bearer; it only\n // unlocks the challenge routes (which opt in by kind).\n const [token, payload] = await this.ticketIssuer.issue({\n client_id: clientId,\n session_id: session.id,\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n sub: user.id,\n sub_kind: OAuth2SubKind.USER,\n realm_id: user.realmId,\n realm_name: user.realm?.name,\n ...(input.confirmation ? { cnf: input.confirmation } : {}),\n exp: expiresAt,\n });\n\n return {\n token,\n expiresIn: payload.exp ?\n payload.exp - Math.floor(Date.now() / 1000) :\n 0,\n };\n }\n\n async complete(input: OAuth2MfaLoginCompleteInput) : Promise<OAuth2TokenGrantResponse> {\n const { ticket } = input;\n\n // Extend the ticket-scoped pending session to the regular session\n // lifetime — the login is complete now.\n const session = await this.sessionManager.refresh(input.session);\n\n // mfaAt was stamped inside the verify unit of work, so the claims\n // advertise the completed factor (amr +otp, acr urn:authup:mfa).\n const amrAcr = deriveAmrAcr(session);\n\n // Single use: atomically claim the ticket BEFORE minting so two\n // concurrent completions (a mixed-kind user presenting two distinct\n // fresh factors) cannot both issue a token pair — `claimInactive` is a\n // set-if-absent write, so exactly one racer wins. The trade-off vs.\n // claiming after minting is that a mint failure burns the ticket — the\n // safer bias for a single-use login credential (the user re-authenticates).\n if (ticket.jti) {\n const claimed = await this.tokenRepository.claimInactive(ticket.jti, ticket.exp);\n if (!claimed) {\n throw OAuth2GrantError.invalid('mfa login ticket has already been used');\n }\n }\n\n const issuePayload : Partial<OAuth2TokenPayload> = {\n client_id: session.clientId ?? undefined,\n session_id: session.id,\n user_agent: session.userAgent,\n remote_address: session.ipAddress,\n scope: ScopeName.GLOBAL,\n sub: session.sub,\n sub_kind: OAuth2SubKind.USER,\n realm_id: session.realmId,\n realm_name: ticket.realm_name,\n ...(ticket.cnf ? { cnf: ticket.cnf } : {}),\n ...amrAcr,\n };\n\n const [accessToken, accessTokenPayload] = await this.accessTokenIssuer.issue(issuePayload);\n const [refreshToken, refreshTokenPayload] = await this.refreshTokenIssuer.issue(issuePayload);\n\n await this.eventService?.record({\n scope: EventScope.OAUTH2,\n name: EventName.LOGIN,\n refType: EventRefType.SESSION,\n refId: session.id,\n clientId: session.clientId ?? null,\n actorType: IdentityType.USER,\n actorId: session.sub,\n actorName: input.userName ?? null,\n realmId: session.realmId,\n requestIpAddress: session.ipAddress ?? null,\n requestUserAgent: session.userAgent ?? null,\n data: {\n grantType: OAuth2TokenGrant.PASSWORD,\n sessionId: session.id,\n },\n });\n this.metrics?.recordLogin('success');\n\n return buildOAuth2BearerTokenResponse({\n accessToken,\n accessTokenPayload,\n refreshToken,\n refreshTokenPayload,\n });\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"claims.mjs","names":["hasOwnProperty","OAuth2SubKind","OAuth2OpenIDClaimsBuilder","clientMap","name","nickname","preferred_username","updated_at","value","Math","floor","Date","getTime","userMap","family_name","given_name","email","email_verified","fromIdentity","identity","type","CLIENT","fromClient","data","fromUser","input","extract","attributeMap","attributes","result","keys","Object","key_","attribute","key","transformer"],"sources":["../../../../src/core/oauth2/openid/claims.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Client, \n Identity, \n User,\n} from '@authup/core-kit';\nimport { hasOwnProperty } from '@authup/kit';\nimport type { OpenIDTokenPayload } from '@authup/specs';\nimport { OAuth2SubKind } from '@authup/specs';\nimport type { ObjectLiteral } from 'validup';\n\ntype AttributeMapTuple<T> = {\n [K in keyof T]: [K, (value: unknown) => any]\n}[keyof T];\n\ntype AttributeMap<T extends Record<string, any>> = Record<\n keyof OpenIDTokenPayload,\nkeyof T | AttributeMapTuple<T>\n>;\n\nexport class OAuth2OpenIDClaimsBuilder {\n protected clientMap : AttributeMap<Client> = {\n name: 'name',\n nickname: 'name',\n preferred_username: 'name',\n updated_at: [\n 'updatedAt',\n (value: unknown) => {\n if (typeof value === 'string') {\n return Math.floor(new Date(value).getTime() / 1000);\n }\n\n return value;\n },\n ],\n };\n\n protected userMap : AttributeMap<User> = {\n name: 'name',\n family_name: 'lastName',\n given_name: 'firstName',\n nickname: 'displayName',\n preferred_username: 'displayName',\n updated_at: [\n 'updatedAt',\n (value: unknown) => {\n if (typeof value === 'string') {\n return Math.floor(new Date(value).getTime() / 1000);\n }\n\n return value;\n },\n ],\n\n email: 'email',\n email_verified: 'active',\n };\n\n /**\n * Build OpenID Claims form Oauth2 Identity.\n *\n * @param identity\n */\n fromIdentity(identity: Identity) : OpenIDTokenPayload {\n if (identity.type === OAuth2SubKind.CLIENT) {\n return this.fromClient(identity.data);\n }\n\n return this.fromUser(identity.data);\n }\n\n fromClient(input: Client) : OpenIDTokenPayload {\n return this.extract(this.clientMap, input);\n }\n\n fromUser(input: User) : OpenIDTokenPayload {\n return this.extract(this.userMap, input);\n }\n\n protected extract<T extends ObjectLiteral = ObjectLiteral>(\n attributeMap: AttributeMap<T>,\n attributes: T,\n ) : OpenIDTokenPayload {\n const result = {} as OpenIDTokenPayload;\n\n const keys = Object.keys(attributeMap);\n for (const key_ of keys) {\n const attribute = attributeMap[key_];\n if (typeof attribute === 'string') {\n if (hasOwnProperty(attributes, attribute)) {\n result[key_] = attributes[attribute];\n }\n } else {\n const [key, transformer] = attribute as AttributeMapTuple<T>;\n\n if (hasOwnProperty(attributes, key)) {\n result[key_] = transformer(attributes[key]);\n }\n }\n }\n\n return result;\n }\n}\n"],"mappings":";;;;;;AA0BA,IAAaE,4BAAb,MAAaA;CACCC,YAAmC;EACzCC,MAAM;EACNC,UAAU;EACVC,oBAAoB;EACpBC,YAAY,CACR,cACCC,UAAAA;GACG,IAAI,OAAOA,UAAU,UACjB,OAAOC,KAAKC,MAAM,IAAIC,KAAKH,KAAAA,CAAAA,CAAOI,QAAO,IAAK,GAAA;GAGlD,OAAOJ;EACX,CAAA;CAER;CAEUK,UAA+B;EACrCT,MAAM;EACNU,aAAa;EACbC,YAAY;EACZV,UAAU;EACVC,oBAAoB;EACpBC,YAAY,CACR,cACCC,UAAAA;GACG,IAAI,OAAOA,UAAU,UACjB,OAAOC,KAAKC,MAAM,IAAIC,KAAKH,KAAAA,CAAAA,CAAOI,QAAO,IAAK,GAAA;GAGlD,OAAOJ;EACX,CAAA;EAGJQ,OAAO;EACPC,gBAAgB;CACpB;;;;;IAOAC,aAAaC,UAAyC;EAClD,IAAIA,SAASC,SAASnB,cAAcoB,QAChC,OAAO,KAAKC,WAAWH,SAASI,IAAI;EAGxC,OAAO,KAAKC,SAASL,SAASI,IAAI;CACtC;CAEAD,WAAWG,OAAoC;EAC3C,OAAO,KAAKC,QAAQ,KAAKvB,WAAWsB,KAAAA;CACxC;CAEAD,SAASC,OAAkC;EACvC,OAAO,KAAKC,QAAQ,KAAKb,SAASY,KAAAA;CACtC;CAEUC,QACNC,cACAC,YACmB;EACnB,MAAMC,SAAS,CAAC;EAEhB,MAAMC,OAAOC,OAAOD,KAAKH,YAAAA;EACzB,KAAK,MAAMK,QAAQF,MAAM;GACrB,MAAMG,YAAYN,aAAaK;GAC/B,IAAI,OAAOC,cAAc;QACjBjC,eAAe4B,YAAYK,SAAAA,GAC3BJ,OAAOG,QAAQJ,WAAWK;GAAAA,OAE3B;IACH,MAAM,CAACC,KAAKC,eAAeF;IAE3B,IAAIjC,eAAe4B,YAAYM,GAAAA,GAC3BL,OAAOG,QAAQG,YAAYP,WAAWM,IAAI;GAElD;EACJ;EAEA,OAAOL;CACX;AACJ"}
1
+ {"version":3,"file":"claims.mjs","names":["hasOwnProperty","OAuth2SubKind","OAuth2OpenIDClaimsBuilder","clientMap","name","nickname","preferred_username","updated_at","value","Math","floor","Date","getTime","userMap","family_name","given_name","email","email_verified","fromIdentity","identity","type","CLIENT","fromClient","data","fromUser","input","extract","attributeMap","attributes","result","keys","Object","key_","attribute","key","transformer"],"sources":["../../../../src/core/oauth2/openid/claims.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type {\n Client, \n Identity, \n User,\n} from '@authup/core-kit';\nimport { hasOwnProperty } from '@authup/kit';\nimport type { OpenIDTokenPayload } from '@authup/specs';\nimport { OAuth2SubKind } from '@authup/specs';\nimport type { ObjectLiteral } from 'validup';\n\ntype AttributeMapTuple<T> = {\n [K in keyof T]: [K, (value: unknown) => any]\n}[keyof T];\n\ntype AttributeMap<T extends Record<string, any>> = Record<\n keyof OpenIDTokenPayload,\nkeyof T | AttributeMapTuple<T>\n>;\n\nexport class OAuth2OpenIDClaimsBuilder {\n protected clientMap : AttributeMap<Client> = {\n name: 'name',\n nickname: 'name',\n preferred_username: 'name',\n updated_at: [\n 'updatedAt',\n (value: unknown) => {\n if (typeof value === 'string') {\n return Math.floor(new Date(value).getTime() / 1000);\n }\n\n return value;\n },\n ],\n };\n\n protected userMap : AttributeMap<User> = {\n name: 'name',\n family_name: 'lastName',\n given_name: 'firstName',\n nickname: 'displayName',\n preferred_username: 'displayName',\n updated_at: [\n 'updatedAt',\n (value: unknown) => {\n if (typeof value === 'string') {\n return Math.floor(new Date(value).getTime() / 1000);\n }\n\n return value;\n },\n ],\n\n email: 'email',\n email_verified: 'active',\n };\n\n /**\n * Build OpenID Claims form Oauth2 Identity.\n *\n * @param identity\n */\n fromIdentity(identity: Identity) : OpenIDTokenPayload {\n if (identity.type === OAuth2SubKind.CLIENT) {\n return this.fromClient(identity.data);\n }\n\n return this.fromUser(identity.data);\n }\n\n fromClient(input: Client) : OpenIDTokenPayload {\n return this.extract(this.clientMap, input);\n }\n\n fromUser(input: User) : OpenIDTokenPayload {\n return this.extract(this.userMap, input);\n }\n\n protected extract<T extends ObjectLiteral = ObjectLiteral>(\n attributeMap: AttributeMap<T>,\n attributes: T,\n ) : OpenIDTokenPayload {\n const result = {} as OpenIDTokenPayload;\n\n const keys = Object.keys(attributeMap);\n for (const key_ of keys) {\n const attribute = attributeMap[key_];\n if (typeof attribute === 'string') {\n if (hasOwnProperty(attributes, attribute)) {\n result[key_] = attributes[attribute];\n }\n } else {\n const [key, transformer] = attribute as AttributeMapTuple<T>;\n\n if (hasOwnProperty(attributes, key)) {\n result[key_] = transformer(attributes[key]);\n }\n }\n }\n\n return result;\n }\n}\n"],"mappings":";;;;;;AA0BA,IAAaE,4BAAb,MAAaA;CACCC,YAAmC;EACzCC,MAAM;EACNC,UAAU;EACVC,oBAAoB;EACpBC,YAAY,CACR,cACCC,UAAAA;GACG,IAAI,OAAOA,UAAU,UACjB,OAAOC,KAAKC,MAAM,IAAIC,KAAKH,KAAAA,CAAAA,CAAOI,QAAO,IAAK,GAAA;GAGlD,OAAOJ;EACX,CACH;CACL;CAEUK,UAA+B;EACrCT,MAAM;EACNU,aAAa;EACbC,YAAY;EACZV,UAAU;EACVC,oBAAoB;EACpBC,YAAY,CACR,cACCC,UAAAA;GACG,IAAI,OAAOA,UAAU,UACjB,OAAOC,KAAKC,MAAM,IAAIC,KAAKH,KAAAA,CAAAA,CAAOI,QAAO,IAAK,GAAA;GAGlD,OAAOJ;EACX,CACH;EAEDQ,OAAO;EACPC,gBAAgB;CACpB;;;;;IAOAC,aAAaC,UAAyC;EAClD,IAAIA,SAASC,SAASnB,cAAcoB,QAChC,OAAO,KAAKC,WAAWH,SAASI,IAAI;EAGxC,OAAO,KAAKC,SAASL,SAASI,IAAI;CACtC;CAEAD,WAAWG,OAAoC;EAC3C,OAAO,KAAKC,QAAQ,KAAKvB,WAAWsB,KAAAA;CACxC;CAEAD,SAASC,OAAkC;EACvC,OAAO,KAAKC,QAAQ,KAAKb,SAASY,KAAAA;CACtC;CAEUC,QACNC,cACAC,YACmB;EACnB,MAAMC,SAAS,CAAC;EAEhB,MAAMC,OAAOC,OAAOD,KAAKH,YAAAA;EACzB,KAAK,MAAMK,QAAQF,MAAM;GACrB,MAAMG,YAAYN,aAAaK;GAC/B,IAAI,OAAOC,cAAc,UACjBjC;QAAAA,eAAe4B,YAAYK,SAAAA,GAC3BJ,OAAOG,QAAQJ,WAAWK;GAAAA,OAE3B;IACH,MAAM,CAACC,KAAKC,eAAeF;IAE3B,IAAIjC,eAAe4B,YAAYM,GAAAA,GAC3BL,OAAOG,QAAQG,YAAYP,WAAWM,IAAI;GAElD;EACJ;EAEA,OAAOL;CACX;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"resolver.mjs","names":["ScopeName","hasOwnProperty","OAuth2SubKind","unwrapOAuth2Scope","distinctArray","OAuth2ScopeAttributesResolver","clientAttributes","IDENTITY","OPEN_ID","userAttributes","EMAIL","resolveFor","type","scope","CLIENT","resolveForClient","resolveForUser","attributes","scopes","push","GLOBAL","Object","values","flat"],"sources":["../../../../src/core/oauth2/scope/resolver.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Client, User } from '@authup/core-kit';\nimport { ScopeName } from '@authup/core-kit';\nimport { hasOwnProperty } from '@authup/kit';\nimport { OAuth2SubKind, unwrapOAuth2Scope } from '@authup/specs';\nimport { distinctArray } from 'smob';\nimport type { ObjectLiteral } from 'validup';\n\ntype ScopeFields<T extends ObjectLiteral = ObjectLiteral> = {\n [key: string]: (keyof T & string)[]\n};\n\nexport class OAuth2ScopeAttributesResolver {\n protected clientAttributes : ScopeFields<Client> = {\n [ScopeName.IDENTITY]: ['name'],\n [ScopeName.OPEN_ID]: ['name', 'displayName', 'updatedAt', 'active'],\n };\n\n protected userAttributes : ScopeFields<User> = {\n [ScopeName.IDENTITY]: ['name', 'displayName', 'lastName', 'firstName'],\n [ScopeName.EMAIL]: ['email'],\n [ScopeName.OPEN_ID]: ['name', 'updatedAt', 'firstName', 'lastName', 'displayName', 'active', 'email'],\n };\n\n /**\n * Resolve attributes for sub kind.\n *\n * @param type\n * @param scope\n */\n resolveFor(type: OAuth2SubKind, scope: string | string[]) {\n if (type === OAuth2SubKind.CLIENT) {\n return this.resolveForClient(scope);\n }\n\n return this.resolveForUser(scope);\n }\n\n resolveForClient(scope: string | string[]) : string[] {\n const attributes : string[] = [];\n const scopes = unwrapOAuth2Scope(scope);\n\n for (const scope of scopes) {\n if (hasOwnProperty(this.clientAttributes, scope)) {\n attributes.push(...this.clientAttributes[scope]);\n }\n\n if (scope === ScopeName.GLOBAL) {\n attributes.push(...Object.values(this.clientAttributes).flat());\n break;\n }\n }\n\n return distinctArray(attributes);\n }\n\n resolveForUser(scope: string | string[]) : string[] {\n const attributes : string[] = [];\n const scopes = unwrapOAuth2Scope(scope);\n\n for (const scope of scopes) {\n if (hasOwnProperty(this.userAttributes, scope)) {\n attributes.push(...this.userAttributes[scope]);\n }\n\n if (scope === ScopeName.GLOBAL) {\n attributes.push(...Object.values(this.userAttributes).flat());\n break;\n }\n }\n\n return distinctArray(attributes);\n }\n}\n"],"mappings":";;;;;;;;AAkBA,IAAaK,gCAAb,MAAaA;CACCC,mBAAyC;GAC9CN,UAAUO,WAAW,CAAC,MAAA;GACtBP,UAAUQ,UAAU;GAAC;GAAQ;GAAe;GAAa;;CAC9D;CAEUC,iBAAqC;GAC1CT,UAAUO,WAAW;GAAC;GAAQ;GAAe;GAAY;;GACzDP,UAAUU,QAAQ,CAAC,OAAA;GACnBV,UAAUQ,UAAU;GAAC;GAAQ;GAAa;GAAa;GAAY;GAAe;GAAU;;CACjG;;;;;;IAQAG,WAAWC,MAAqBC,OAA0B;EACtD,IAAID,SAASV,cAAcY,QACvB,OAAO,KAAKC,iBAAiBF,KAAAA;EAGjC,OAAO,KAAKG,eAAeH,KAAAA;CAC/B;CAEAE,iBAAiBF,OAAqC;EAClD,MAAMI,aAAwB,CAAA;EAC9B,MAAMC,SAASf,kBAAkBU,KAAAA;EAEjC,KAAK,MAAMA,SAASK,QAAQ;GACxB,IAAIjB,eAAe,KAAKK,kBAAkBO,KAAAA,GACtCI,WAAWE,KAAI,GAAI,KAAKb,iBAAiBO,MAAM;GAGnD,IAAIA,UAAUb,UAAUoB,QAAQ;IAC5BH,WAAWE,KAAI,GAAIE,OAAOC,OAAO,KAAKhB,gBAAgB,CAAA,CAAEiB,KAAI,CAAA;IAC5D;GACJ;EACJ;EAEA,OAAOnB,cAAca,UAAAA;CACzB;CAEAD,eAAeH,OAAqC;EAChD,MAAMI,aAAwB,CAAA;EAC9B,MAAMC,SAASf,kBAAkBU,KAAAA;EAEjC,KAAK,MAAMA,SAASK,QAAQ;GACxB,IAAIjB,eAAe,KAAKQ,gBAAgBI,KAAAA,GACpCI,WAAWE,KAAI,GAAI,KAAKV,eAAeI,MAAM;GAGjD,IAAIA,UAAUb,UAAUoB,QAAQ;IAC5BH,WAAWE,KAAI,GAAIE,OAAOC,OAAO,KAAKb,cAAc,CAAA,CAAEc,KAAI,CAAA;IAC1D;GACJ;EACJ;EAEA,OAAOnB,cAAca,UAAAA;CACzB;AACJ"}
1
+ {"version":3,"file":"resolver.mjs","names":["ScopeName","hasOwnProperty","OAuth2SubKind","unwrapOAuth2Scope","distinctArray","OAuth2ScopeAttributesResolver","clientAttributes","IDENTITY","OPEN_ID","userAttributes","EMAIL","resolveFor","type","scope","CLIENT","resolveForClient","resolveForUser","attributes","scopes","push","GLOBAL","Object","values","flat"],"sources":["../../../../src/core/oauth2/scope/resolver.ts"],"sourcesContent":["/*\n * Copyright (c) 2022.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { Client, User } from '@authup/core-kit';\nimport { ScopeName } from '@authup/core-kit';\nimport { hasOwnProperty } from '@authup/kit';\nimport { OAuth2SubKind, unwrapOAuth2Scope } from '@authup/specs';\nimport { distinctArray } from 'smob';\nimport type { ObjectLiteral } from 'validup';\n\ntype ScopeFields<T extends ObjectLiteral = ObjectLiteral> = {\n [key: string]: (keyof T & string)[]\n};\n\nexport class OAuth2ScopeAttributesResolver {\n protected clientAttributes : ScopeFields<Client> = {\n [ScopeName.IDENTITY]: ['name'],\n [ScopeName.OPEN_ID]: ['name', 'displayName', 'updatedAt', 'active'],\n };\n\n protected userAttributes : ScopeFields<User> = {\n [ScopeName.IDENTITY]: ['name', 'displayName', 'lastName', 'firstName'],\n [ScopeName.EMAIL]: ['email'],\n [ScopeName.OPEN_ID]: ['name', 'updatedAt', 'firstName', 'lastName', 'displayName', 'active', 'email'],\n };\n\n /**\n * Resolve attributes for sub kind.\n *\n * @param type\n * @param scope\n */\n resolveFor(type: OAuth2SubKind, scope: string | string[]) {\n if (type === OAuth2SubKind.CLIENT) {\n return this.resolveForClient(scope);\n }\n\n return this.resolveForUser(scope);\n }\n\n resolveForClient(scope: string | string[]) : string[] {\n const attributes : string[] = [];\n const scopes = unwrapOAuth2Scope(scope);\n\n for (const scope of scopes) {\n if (hasOwnProperty(this.clientAttributes, scope)) {\n attributes.push(...this.clientAttributes[scope]);\n }\n\n if (scope === ScopeName.GLOBAL) {\n attributes.push(...Object.values(this.clientAttributes).flat());\n break;\n }\n }\n\n return distinctArray(attributes);\n }\n\n resolveForUser(scope: string | string[]) : string[] {\n const attributes : string[] = [];\n const scopes = unwrapOAuth2Scope(scope);\n\n for (const scope of scopes) {\n if (hasOwnProperty(this.userAttributes, scope)) {\n attributes.push(...this.userAttributes[scope]);\n }\n\n if (scope === ScopeName.GLOBAL) {\n attributes.push(...Object.values(this.userAttributes).flat());\n break;\n }\n }\n\n return distinctArray(attributes);\n }\n}\n"],"mappings":";;;;;;;;AAkBA,IAAaK,gCAAb,MAAaA;CACCC,mBAAyC;GAC9CN,UAAUO,WAAW,CAAC,MAAO;GAC7BP,UAAUQ,UAAU;GAAC;GAAQ;GAAe;GAAa;EAAS;CACvE;CAEUC,iBAAqC;GAC1CT,UAAUO,WAAW;GAAC;GAAQ;GAAe;GAAY;EAAY;GACrEP,UAAUU,QAAQ,CAAC,OAAQ;GAC3BV,UAAUQ,UAAU;GAAC;GAAQ;GAAa;GAAa;GAAY;GAAe;GAAU;EAAQ;CACzG;;;;;;IAQAG,WAAWC,MAAqBC,OAA0B;EACtD,IAAID,SAASV,cAAcY,QACvB,OAAO,KAAKC,iBAAiBF,KAAAA;EAGjC,OAAO,KAAKG,eAAeH,KAAAA;CAC/B;CAEAE,iBAAiBF,OAAqC;EAClD,MAAMI,aAAwB,CAAA;EAC9B,MAAMC,SAASf,kBAAkBU,KAAAA;EAEjC,KAAK,MAAMA,SAASK,QAAQ;GACxB,IAAIjB,eAAe,KAAKK,kBAAkBO,KAAAA,GACtCI,WAAWE,KAAI,GAAI,KAAKb,iBAAiBO,MAAM;GAGnD,IAAIA,UAAUb,UAAUoB,QAAQ;IAC5BH,WAAWE,KAAI,GAAIE,OAAOC,OAAO,KAAKhB,gBAAgB,CAAA,CAAEiB,KAAI,CAAA;IAC5D;GACJ;EACJ;EAEA,OAAOnB,cAAca,UAAAA;CACzB;CAEAD,eAAeH,OAAqC;EAChD,MAAMI,aAAwB,CAAA;EAC9B,MAAMC,SAASf,kBAAkBU,KAAAA;EAEjC,KAAK,MAAMA,SAASK,QAAQ;GACxB,IAAIjB,eAAe,KAAKQ,gBAAgBI,KAAAA,GACpCI,WAAWE,KAAI,GAAI,KAAKV,eAAeI,MAAM;GAGjD,IAAIA,UAAUb,UAAUoB,QAAQ;IAC5BH,WAAWE,KAAI,GAAIE,OAAOC,OAAO,KAAKb,cAAc,CAAA,CAAEc,KAAI,CAAA;IAC1D;GACJ;EACJ;EAEA,OAAOnB,cAAca,UAAAA;CACzB;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2AccessTokenIssuer","repository","signer","identityRoleProvider","sessionTokenRepository","options","issue","input","iss","buildIss","accessClaims","buildAccessClaims","data","insert","kind","ACCESS","exp","buildExp","token","sign","saveWithSignature","session_id","jti","create","id","sessionId","refreshTokenId","refresh_token_id","ipAddress","remote_address","userAgent","user_agent","expiresAt","Date","toISOString","sub","sub_kind","roles","getRolesFor","type","clientId","client_id","realmId","realm_id","realmName","realm_name","realmRoles","globalRoles","role","push","name","realm_access","global_access"],"sources":["../../../../../../src/core/oauth2/token/issuer/access/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport type { ISessionTokenRepository } from '../../../session-token/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\nimport type { IIdentityRoleProvider } from '../../../../identity/index.ts';\n\nexport class OAuth2AccessTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected identityRoleProvider?: IIdentityRoleProvider;\n\n protected sessionTokenRepository?: ISessionTokenRepository;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n identityRoleProvider?: IIdentityRoleProvider,\n sessionTokenRepository?: ISessionTokenRepository,\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n this.identityRoleProvider = identityRoleProvider;\n this.sessionTokenRepository = sessionTokenRepository;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const accessClaims = await this.buildAccessClaims(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.ACCESS,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n ...accessClaims,\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n if (this.sessionTokenRepository && data.session_id && data.jti && typeof data.exp === 'number') {\n await this.sessionTokenRepository.create({\n id: data.jti,\n sessionId: data.session_id,\n kind: 'access',\n refreshTokenId: input.refresh_token_id ?? null,\n ipAddress: data.remote_address ?? '',\n userAgent: data.user_agent ?? '',\n expiresAt: new Date(data.exp * 1000).toISOString(),\n });\n }\n\n return [token, data];\n }\n\n protected async buildAccessClaims(input: OAuth2TokenPayload) : Promise<Pick<OAuth2TokenPayload, 'realm_access' | 'global_access'>> {\n if (!this.identityRoleProvider || !input.sub || !input.sub_kind) {\n return {};\n }\n\n const roles = await this.identityRoleProvider.getRolesFor({\n type: input.sub_kind,\n id: input.sub,\n clientId: input.client_id,\n realmId: input.realm_id,\n realmName: input.realm_name,\n });\n\n const realmRoles: string[] = [];\n const globalRoles: string[] = [];\n\n for (const role of roles) {\n if (!role.realmId) {\n globalRoles.push(role.name);\n } else if (input.realm_id && role.realmId === input.realm_id) {\n realmRoles.push(role.name);\n }\n }\n\n return {\n realm_access: { roles: realmRoles },\n global_access: { roles: globalRoles },\n };\n }\n}\n"],"mappings":";;;;;;AAgBA,IAAaE,0BAAb,cAA6CD,sBAAAA;CAC/BE;CAEAC;CAEAC;CAEAC;CAEV,YACIH,YACAC,QACAG,UAAoC,CAAC,GACrCF,sBACAC,wBACF;EACE,MAAMC,OAAAA;EAEN,KAAKJ,aAAaA;EAClB,KAAKC,SAASA;EACd,KAAKC,uBAAuBA;EAC5B,KAAKC,yBAAyBA;CAClC;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,eAAe,MAAM,KAAKC,kBAAkBJ,KAAAA;EAElD,MAAMK,OAAO,MAAM,KAAKX,WAAWY,OAAO;GACtC,GAAGN;GACHO,MAAMhB,gBAAgBiB;GACtBC,KAAK,KAAKC,SAASV,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;GACrB,GAAGE;EACP,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKhB,OAAOiB,KAAKP,IAAAA;EAErC,MAAM,KAAKX,WAAWmB,kBAAkBR,MAAMM,KAAAA;EAE9C,IAAI,KAAKd,0BAA0BQ,KAAKS,cAAcT,KAAKU,OAAO,OAAOV,KAAKI,QAAQ,UAClF,MAAM,KAAKZ,uBAAuBmB,OAAO;GACrCC,IAAIZ,KAAKU;GACTG,WAAWb,KAAKS;GAChBP,MAAM;GACNY,gBAAgBnB,MAAMoB,oBAAoB;GAC1CC,WAAWhB,KAAKiB,kBAAkB;GAClCC,WAAWlB,KAAKmB,cAAc;GAC9BC,4BAAW,IAAIC,KAAKrB,KAAKI,MAAM,GAAA,EAAA,CAAMkB,YAAW;EACpD,CAAA;EAGJ,OAAO,CAAChB,OAAON,IAAAA;CACnB;CAEA,MAAgBD,kBAAkBJ,OAAiG;EAC/H,IAAI,CAAC,KAAKJ,wBAAwB,CAACI,MAAM4B,OAAO,CAAC5B,MAAM6B,UACnD,OAAO,CAAC;EAGZ,MAAMC,QAAQ,MAAM,KAAKlC,qBAAqBmC,YAAY;GACtDC,MAAMhC,MAAM6B;GACZZ,IAAIjB,MAAM4B;GACVK,UAAUjC,MAAMkC;GAChBC,SAASnC,MAAMoC;GACfC,WAAWrC,MAAMsC;EACrB,CAAA;EAEA,MAAMC,aAAuB,CAAA;EAC7B,MAAMC,cAAwB,CAAA;EAE9B,KAAK,MAAMC,QAAQX,OACf,IAAI,CAACW,KAAKN,SACNK,YAAYE,KAAKD,KAAKE,IAAI;OACvB,IAAI3C,MAAMoC,YAAYK,KAAKN,YAAYnC,MAAMoC,UAChDG,WAAWG,KAAKD,KAAKE,IAAI;EAIjC,OAAO;GACHC,cAAc,EAAEd,OAAOS,WAAW;GAClCM,eAAe,EAAEf,OAAOU,YAAY;EACxC;CACJ;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2AccessTokenIssuer","repository","signer","identityRoleProvider","sessionTokenRepository","options","issue","input","iss","buildIss","accessClaims","buildAccessClaims","data","insert","kind","ACCESS","exp","buildExp","token","sign","saveWithSignature","session_id","jti","create","id","sessionId","refreshTokenId","refresh_token_id","ipAddress","remote_address","userAgent","user_agent","expiresAt","Date","toISOString","sub","sub_kind","roles","getRolesFor","type","clientId","client_id","realmId","realm_id","realmName","realm_name","realmRoles","globalRoles","role","push","name","realm_access","global_access"],"sources":["../../../../../../src/core/oauth2/token/issuer/access/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport type { ISessionTokenRepository } from '../../../session-token/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\nimport type { IIdentityRoleProvider } from '../../../../identity/index.ts';\n\nexport class OAuth2AccessTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected identityRoleProvider?: IIdentityRoleProvider;\n\n protected sessionTokenRepository?: ISessionTokenRepository;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n identityRoleProvider?: IIdentityRoleProvider,\n sessionTokenRepository?: ISessionTokenRepository,\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n this.identityRoleProvider = identityRoleProvider;\n this.sessionTokenRepository = sessionTokenRepository;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const accessClaims = await this.buildAccessClaims(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.ACCESS,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n ...accessClaims,\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n if (this.sessionTokenRepository && data.session_id && data.jti && typeof data.exp === 'number') {\n await this.sessionTokenRepository.create({\n id: data.jti,\n sessionId: data.session_id,\n kind: 'access',\n refreshTokenId: input.refresh_token_id ?? null,\n ipAddress: data.remote_address ?? '',\n userAgent: data.user_agent ?? '',\n expiresAt: new Date(data.exp * 1000).toISOString(),\n });\n }\n\n return [token, data];\n }\n\n protected async buildAccessClaims(input: OAuth2TokenPayload) : Promise<Pick<OAuth2TokenPayload, 'realm_access' | 'global_access'>> {\n if (!this.identityRoleProvider || !input.sub || !input.sub_kind) {\n return {};\n }\n\n const roles = await this.identityRoleProvider.getRolesFor({\n type: input.sub_kind,\n id: input.sub,\n clientId: input.client_id,\n realmId: input.realm_id,\n realmName: input.realm_name,\n });\n\n const realmRoles: string[] = [];\n const globalRoles: string[] = [];\n\n for (const role of roles) {\n if (!role.realmId) {\n globalRoles.push(role.name);\n } else if (input.realm_id && role.realmId === input.realm_id) {\n realmRoles.push(role.name);\n }\n }\n\n return {\n realm_access: { roles: realmRoles },\n global_access: { roles: globalRoles },\n };\n }\n}\n"],"mappings":";;;;;;AAgBA,IAAaE,0BAAb,cAA6CD,sBAAAA;CAC/BE;CAEAC;CAEAC;CAEAC;CAEV,YACIH,YACAC,QACAG,UAAoC,CAAC,GACrCF,sBACAC,wBACF;EACE,MAAMC,OAAAA;EAEN,KAAKJ,aAAaA;EAClB,KAAKC,SAASA;EACd,KAAKC,uBAAuBA;EAC5B,KAAKC,yBAAyBA;CAClC;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,eAAe,MAAM,KAAKC,kBAAkBJ,KAAAA;EAElD,MAAMK,OAAO,MAAM,KAAKX,WAAWY,OAAO;GACtC,GAAGN;GACHO,MAAMhB,gBAAgBiB;GACtBC,KAAK,KAAKC,SAASV,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;GACrB,GAAGE;EACP,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKhB,OAAOiB,KAAKP,IAAAA;EAErC,MAAM,KAAKX,WAAWmB,kBAAkBR,MAAMM,KAAAA;EAE9C,IAAI,KAAKd,0BAA0BQ,KAAKS,cAAcT,KAAKU,OAAO,OAAOV,KAAKI,QAAQ,UAClF,MAAM,KAAKZ,uBAAuBmB,OAAO;GACrCC,IAAIZ,KAAKU;GACTG,WAAWb,KAAKS;GAChBP,MAAM;GACNY,gBAAgBnB,MAAMoB,oBAAoB;GAC1CC,WAAWhB,KAAKiB,kBAAkB;GAClCC,WAAWlB,KAAKmB,cAAc;GAC9BC,4BAAW,IAAIC,KAAKrB,KAAKI,MAAM,GAAA,EAAA,CAAMkB,YAAW;EACpD,CAAA;EAGJ,OAAO,CAAChB,OAAON,IAAK;CACxB;CAEA,MAAgBD,kBAAkBJ,OAAiG;EAC/H,IAAI,CAAC,KAAKJ,wBAAwB,CAACI,MAAM4B,OAAO,CAAC5B,MAAM6B,UACnD,OAAO,CAAC;EAGZ,MAAMC,QAAQ,MAAM,KAAKlC,qBAAqBmC,YAAY;GACtDC,MAAMhC,MAAM6B;GACZZ,IAAIjB,MAAM4B;GACVK,UAAUjC,MAAMkC;GAChBC,SAASnC,MAAMoC;GACfC,WAAWrC,MAAMsC;EACrB,CAAA;EAEA,MAAMC,aAAuB,CAAA;EAC7B,MAAMC,cAAwB,CAAA;EAE9B,KAAK,MAAMC,QAAQX,OACf,IAAI,CAACW,KAAKN,SACNK,YAAYE,KAAKD,KAAKE,IAAI;OACvB,IAAI3C,MAAMoC,YAAYK,KAAKN,YAAYnC,MAAMoC,UAChDG,WAAWG,KAAKD,KAAKE,IAAI;EAIjC,OAAO;GACHC,cAAc,EAAEd,OAAOS,WAAW;GAClCM,eAAe,EAAEf,OAAOU,YAAY;EACxC;CACJ;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2MfaTokenIssuer","repository","signer","options","issue","input","iss","buildIss","data","insert","kind","MFA","exp","buildExp","token","sign","saveWithSignature"],"sources":["../../../../../../src/core/oauth2/token/issuer/mfa/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\n\n/**\n * Issues the short-lived \"MFA-pending\" login ticket (kind: mfa_token).\n * Deliberately minimal: no role claims, no scope, no session-token\n * inventory row — the ticket is not a bearer, it only unlocks the\n * challenge routes (which opt in by kind).\n */\nexport class OAuth2MfaTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.MFA,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;;;;;;GAoBA,IAAaE,uBAAb,cAA0CD,sBAAAA;CAC5BE;CAEAC;CAEV,YACID,YACAC,QACAC,UAAoC,CAAC,GACvC;EACE,MAAMA,OAAAA;EAEN,KAAKF,aAAaA;EAClB,KAAKC,SAASA;CAClB;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,OAAO,MAAM,KAAKP,WAAWQ,OAAO;GACtC,GAAGJ;GACHK,MAAMZ,gBAAgBa;GACtBC,KAAK,KAAKC,SAASR,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;EACzB,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKZ,OAAOa,KAAKP,IAAAA;EAErC,MAAM,KAAKP,WAAWe,kBAAkBR,MAAMM,KAAAA;EAE9C,OAAO,CAACA,OAAON,IAAAA;CACnB;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2MfaTokenIssuer","repository","signer","options","issue","input","iss","buildIss","data","insert","kind","MFA","exp","buildExp","token","sign","saveWithSignature"],"sources":["../../../../../../src/core/oauth2/token/issuer/mfa/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\n\n/**\n * Issues the short-lived \"MFA-pending\" login ticket (kind: mfa_token).\n * Deliberately minimal: no role claims, no scope, no session-token\n * inventory row — the ticket is not a bearer, it only unlocks the\n * challenge routes (which opt in by kind).\n */\nexport class OAuth2MfaTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.MFA,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;;;;;;GAoBA,IAAaE,uBAAb,cAA0CD,sBAAAA;CAC5BE;CAEAC;CAEV,YACID,YACAC,QACAC,UAAoC,CAAC,GACvC;EACE,MAAMA,OAAAA;EAEN,KAAKF,aAAaA;EAClB,KAAKC,SAASA;CAClB;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,OAAO,MAAM,KAAKP,WAAWQ,OAAO;GACtC,GAAGJ;GACHK,MAAMZ,gBAAgBa;GACtBC,KAAK,KAAKC,SAASR,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;EACzB,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKZ,OAAOa,KAAKP,IAAAA;EAErC,MAAM,KAAKP,WAAWe,kBAAkBR,MAAMM,KAAAA;EAE9C,OAAO,CAACA,OAAON,IAAK;CACxB;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["JWTError","OAuth2TokenKind","OAuth2OpenIDClaimsBuilder","OAuth2BaseTokenIssuer","OAuth2OpenIDTokenIssuer","repository","signer","identityResolver","claimsBuilder","ctx","options","issue","input","sub_kind","payloadPropertyInvalid","sub","identity","resolve","payloadInvalid","issueWithIdentity","claims","fromIdentity","utc","Math","floor","Date","getTime","iss","buildIss","data","insert","kind","ID_TOKEN","auth_time","exp","buildExp","updated_at","client_id","aud","token","sign","saveWithSignature"],"sources":["../../../../../../src/core/oauth2/token/issuer/open-id/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { JWTError, OAuth2TokenKind } from '@authup/specs';\nimport type { Identity } from '@authup/core-kit';\nimport { OAuth2OpenIDClaimsBuilder } from '../../../openid/index.ts';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { OAuth2TokenIssuerResponse } from '../types.ts';\nimport type { IOAuth2OpenIDTokenIssuer, OAuth2OpenIDTokenIssuerContext } from './types.ts';\nimport type { IIdentityResolver } from '../../../../identity/index.ts';\n\nexport class OAuth2OpenIDTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2OpenIDTokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected identityResolver : IIdentityResolver;\n\n protected claimsBuilder: OAuth2OpenIDClaimsBuilder;\n\n constructor(ctx: OAuth2OpenIDTokenIssuerContext) {\n super(ctx.options);\n\n this.repository = ctx.repository;\n this.signer = ctx.signer;\n\n this.identityResolver = ctx.identityResolver;\n this.claimsBuilder = new OAuth2OpenIDClaimsBuilder();\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n if (!input.sub_kind) {\n throw JWTError.payloadPropertyInvalid('sub_kind');\n }\n\n if (!input.sub) {\n throw JWTError.payloadPropertyInvalid('sub');\n }\n\n const identity = await this.identityResolver.resolve(\n input.sub_kind,\n input.sub,\n );\n\n if (!identity) {\n throw JWTError.payloadInvalid();\n }\n\n return this.issueWithIdentity(input, identity);\n }\n\n async issueWithIdentity(\n input: OAuth2TokenPayload,\n identity: Identity,\n ): Promise<OAuth2TokenIssuerResponse> {\n const claims = this.claimsBuilder.fromIdentity(identity);\n\n const utc = Math.floor(new Date().getTime() / 1000);\n\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n ...claims,\n kind: OAuth2TokenKind.ID_TOKEN,\n // Real authentication time when threaded from the authorize flow\n // (the session's creation instant); fall back to issuance time only\n // for session-less id_token issuance. `sid` rides via ...input.\n auth_time: typeof input.auth_time === 'number' ? input.auth_time : utc,\n exp: this.buildExp(input),\n updated_at: utc,\n ...(iss ? { iss } : {}),\n ...(input.client_id ? { aud: input.client_id } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;;;AAkBA,IAAaI,0BAAb,cAA6CD,sBAAAA;CAC/BE;CAEAC;CAEAC;CAEAC;CAEV,YAAYC,KAAqC;EAC7C,MAAMA,IAAIC,OAAO;EAEjB,KAAKL,aAAaI,IAAIJ;EACtB,KAAKC,SAASG,IAAIH;EAElB,KAAKC,mBAAmBE,IAAIF;EAC5B,KAAKC,gBAAgB,IAAIN,0BAAAA;CAC7B;CAEA,MAAMS,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,IAAI,CAACA,MAAMC,UACP,MAAMb,SAASc,uBAAuB,UAAA;EAG1C,IAAI,CAACF,MAAMG,KACP,MAAMf,SAASc,uBAAuB,KAAA;EAG1C,MAAME,WAAW,MAAM,KAAKT,iBAAiBU,QACzCL,MAAMC,UACND,MAAMG,GAAG;EAGb,IAAI,CAACC,UACD,MAAMhB,SAASkB,eAAc;EAGjC,OAAO,KAAKC,kBAAkBP,OAAOI,QAAAA;CACzC;CAEA,MAAMG,kBACFP,OACAI,UACkC;EAClC,MAAMI,SAAS,KAAKZ,cAAca,aAAaL,QAAAA;EAE/C,MAAMM,MAAMC,KAAKC,uBAAM,IAAIC,KAAAA,EAAAA,CAAOC,QAAO,IAAK,GAAA;EAE9C,MAAMC,MAAM,KAAKC,SAAShB,KAAAA;EAE1B,MAAMiB,OAAO,MAAM,KAAKxB,WAAWyB,OAAO;GACtC,GAAGlB;GACH,GAAGQ;GACHW,MAAM9B,gBAAgB+B;GAItBC,WAAW,OAAOrB,MAAMqB,cAAc,WAAWrB,MAAMqB,YAAYX;GACnEY,KAAK,KAAKC,SAASvB,KAAAA;GACnBwB,YAAYd;GACZ,GAAIK,MAAM,EAAEA,IAAI,IAAI,CAAC;GACrB,GAAIf,MAAMyB,YAAY,EAAEC,KAAK1B,MAAMyB,UAAU,IAAI,CAAC;EACtD,CAAA;EAEA,MAAME,QAAQ,MAAM,KAAKjC,OAAOkC,KAAKX,IAAAA;EAErC,MAAM,KAAKxB,WAAWoC,kBAAkBZ,MAAMU,KAAAA;EAE9C,OAAO,CAACA,OAAOV,IAAAA;CACnB;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["JWTError","OAuth2TokenKind","OAuth2OpenIDClaimsBuilder","OAuth2BaseTokenIssuer","OAuth2OpenIDTokenIssuer","repository","signer","identityResolver","claimsBuilder","ctx","options","issue","input","sub_kind","payloadPropertyInvalid","sub","identity","resolve","payloadInvalid","issueWithIdentity","claims","fromIdentity","utc","Math","floor","Date","getTime","iss","buildIss","data","insert","kind","ID_TOKEN","auth_time","exp","buildExp","updated_at","client_id","aud","token","sign","saveWithSignature"],"sources":["../../../../../../src/core/oauth2/token/issuer/open-id/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { JWTError, OAuth2TokenKind } from '@authup/specs';\nimport type { Identity } from '@authup/core-kit';\nimport { OAuth2OpenIDClaimsBuilder } from '../../../openid/index.ts';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { OAuth2TokenIssuerResponse } from '../types.ts';\nimport type { IOAuth2OpenIDTokenIssuer, OAuth2OpenIDTokenIssuerContext } from './types.ts';\nimport type { IIdentityResolver } from '../../../../identity/index.ts';\n\nexport class OAuth2OpenIDTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2OpenIDTokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected identityResolver : IIdentityResolver;\n\n protected claimsBuilder: OAuth2OpenIDClaimsBuilder;\n\n constructor(ctx: OAuth2OpenIDTokenIssuerContext) {\n super(ctx.options);\n\n this.repository = ctx.repository;\n this.signer = ctx.signer;\n\n this.identityResolver = ctx.identityResolver;\n this.claimsBuilder = new OAuth2OpenIDClaimsBuilder();\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n if (!input.sub_kind) {\n throw JWTError.payloadPropertyInvalid('sub_kind');\n }\n\n if (!input.sub) {\n throw JWTError.payloadPropertyInvalid('sub');\n }\n\n const identity = await this.identityResolver.resolve(\n input.sub_kind,\n input.sub,\n );\n\n if (!identity) {\n throw JWTError.payloadInvalid();\n }\n\n return this.issueWithIdentity(input, identity);\n }\n\n async issueWithIdentity(\n input: OAuth2TokenPayload,\n identity: Identity,\n ): Promise<OAuth2TokenIssuerResponse> {\n const claims = this.claimsBuilder.fromIdentity(identity);\n\n const utc = Math.floor(new Date().getTime() / 1000);\n\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n ...claims,\n kind: OAuth2TokenKind.ID_TOKEN,\n // Real authentication time when threaded from the authorize flow\n // (the session's creation instant); fall back to issuance time only\n // for session-less id_token issuance. `sid` rides via ...input.\n auth_time: typeof input.auth_time === 'number' ? input.auth_time : utc,\n exp: this.buildExp(input),\n updated_at: utc,\n ...(iss ? { iss } : {}),\n ...(input.client_id ? { aud: input.client_id } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;;;AAkBA,IAAaI,0BAAb,cAA6CD,sBAAAA;CAC/BE;CAEAC;CAEAC;CAEAC;CAEV,YAAYC,KAAqC;EAC7C,MAAMA,IAAIC,OAAO;EAEjB,KAAKL,aAAaI,IAAIJ;EACtB,KAAKC,SAASG,IAAIH;EAElB,KAAKC,mBAAmBE,IAAIF;EAC5B,KAAKC,gBAAgB,IAAIN,0BAAAA;CAC7B;CAEA,MAAMS,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,IAAI,CAACA,MAAMC,UACP,MAAMb,SAASc,uBAAuB,UAAA;EAG1C,IAAI,CAACF,MAAMG,KACP,MAAMf,SAASc,uBAAuB,KAAA;EAG1C,MAAME,WAAW,MAAM,KAAKT,iBAAiBU,QACzCL,MAAMC,UACND,MAAMG,GAAG;EAGb,IAAI,CAACC,UACD,MAAMhB,SAASkB,eAAc;EAGjC,OAAO,KAAKC,kBAAkBP,OAAOI,QAAAA;CACzC;CAEA,MAAMG,kBACFP,OACAI,UACkC;EAClC,MAAMI,SAAS,KAAKZ,cAAca,aAAaL,QAAAA;EAE/C,MAAMM,MAAMC,KAAKC,uBAAM,IAAIC,KAAAA,EAAAA,CAAOC,QAAO,IAAK,GAAA;EAE9C,MAAMC,MAAM,KAAKC,SAAShB,KAAAA;EAE1B,MAAMiB,OAAO,MAAM,KAAKxB,WAAWyB,OAAO;GACtC,GAAGlB;GACH,GAAGQ;GACHW,MAAM9B,gBAAgB+B;GAItBC,WAAW,OAAOrB,MAAMqB,cAAc,WAAWrB,MAAMqB,YAAYX;GACnEY,KAAK,KAAKC,SAASvB,KAAAA;GACnBwB,YAAYd;GACZ,GAAIK,MAAM,EAAEA,IAAI,IAAI,CAAC;GACrB,GAAIf,MAAMyB,YAAY,EAAEC,KAAK1B,MAAMyB,UAAU,IAAI,CAAC;EACtD,CAAA;EAEA,MAAME,QAAQ,MAAM,KAAKjC,OAAOkC,KAAKX,IAAAA;EAErC,MAAM,KAAKxB,WAAWoC,kBAAkBZ,MAAMU,KAAAA;EAE9C,OAAO,CAACA,OAAOV,IAAK;CACxB;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2RefreshTokenIssuer","repository","signer","sessionTokenRepository","options","issue","input","iss","buildIss","data","insert","kind","REFRESH","exp","buildExp","token","sign","saveWithSignature","session_id","jti","create","id","sessionId","parentId","parent_id","ipAddress","remote_address","userAgent","user_agent","expiresAt","Date","toISOString"],"sources":["../../../../../../src/core/oauth2/token/issuer/refresh/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport type { ISessionTokenRepository } from '../../../session-token/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\n\nexport class OAuth2RefreshTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected sessionTokenRepository?: ISessionTokenRepository;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n sessionTokenRepository?: ISessionTokenRepository,\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n this.sessionTokenRepository = sessionTokenRepository;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.REFRESH,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n if (this.sessionTokenRepository && data.session_id && data.jti && typeof data.exp === 'number') {\n await this.sessionTokenRepository.create({\n id: data.jti,\n sessionId: data.session_id,\n kind: 'refresh',\n parentId: input.parent_id ?? null,\n ipAddress: data.remote_address ?? '',\n userAgent: data.user_agent ?? '',\n expiresAt: new Date(data.exp * 1000).toISOString(),\n });\n }\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;AAeA,IAAaE,2BAAb,cAA8CD,sBAAAA;CAChCE;CAEAC;CAEAC;CAEV,YACIF,YACAC,QACAE,UAAoC,CAAC,GACrCD,wBACF;EACE,MAAMC,OAAAA;EAEN,KAAKH,aAAaA;EAClB,KAAKC,SAASA;EACd,KAAKC,yBAAyBA;CAClC;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,OAAO,MAAM,KAAKR,WAAWS,OAAO;GACtC,GAAGJ;GACHK,MAAMb,gBAAgBc;GACtBC,KAAK,KAAKC,SAASR,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;EACzB,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKb,OAAOc,KAAKP,IAAAA;EAErC,MAAM,KAAKR,WAAWgB,kBAAkBR,MAAMM,KAAAA;EAE9C,IAAI,KAAKZ,0BAA0BM,KAAKS,cAAcT,KAAKU,OAAO,OAAOV,KAAKI,QAAQ,UAClF,MAAM,KAAKV,uBAAuBiB,OAAO;GACrCC,IAAIZ,KAAKU;GACTG,WAAWb,KAAKS;GAChBP,MAAM;GACNY,UAAUjB,MAAMkB,aAAa;GAC7BC,WAAWhB,KAAKiB,kBAAkB;GAClCC,WAAWlB,KAAKmB,cAAc;GAC9BC,4BAAW,IAAIC,KAAKrB,KAAKI,MAAM,GAAA,EAAA,CAAMkB,YAAW;EACpD,CAAA;EAGJ,OAAO,CAAChB,OAAON,IAAAA;CACnB;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["OAuth2TokenKind","OAuth2BaseTokenIssuer","OAuth2RefreshTokenIssuer","repository","signer","sessionTokenRepository","options","issue","input","iss","buildIss","data","insert","kind","REFRESH","exp","buildExp","token","sign","saveWithSignature","session_id","jti","create","id","sessionId","parentId","parent_id","ipAddress","remote_address","userAgent","user_agent","expiresAt","Date","toISOString"],"sources":["../../../../../../src/core/oauth2/token/issuer/refresh/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport { OAuth2TokenKind } from '@authup/specs';\nimport type { IOAuth2TokenSigner } from '../../signer/index.ts';\nimport type { IOAuth2TokenRepository } from '../../repository/index.ts';\nimport type { ISessionTokenRepository } from '../../../session-token/index.ts';\nimport { OAuth2BaseTokenIssuer } from '../base.ts';\nimport type { IOAuth2TokenIssuer, OAuth2TokenIssuerOptions, OAuth2TokenIssuerResponse } from '../types.ts';\n\nexport class OAuth2RefreshTokenIssuer extends OAuth2BaseTokenIssuer implements IOAuth2TokenIssuer {\n protected repository: IOAuth2TokenRepository;\n\n protected signer : IOAuth2TokenSigner;\n\n protected sessionTokenRepository?: ISessionTokenRepository;\n\n constructor(\n repository: IOAuth2TokenRepository,\n signer: IOAuth2TokenSigner,\n options: OAuth2TokenIssuerOptions = {},\n sessionTokenRepository?: ISessionTokenRepository,\n ) {\n super(options);\n\n this.repository = repository;\n this.signer = signer;\n this.sessionTokenRepository = sessionTokenRepository;\n }\n\n async issue(input: OAuth2TokenPayload = {}) : Promise<OAuth2TokenIssuerResponse> {\n const iss = this.buildIss(input);\n\n const data = await this.repository.insert({\n ...input,\n kind: OAuth2TokenKind.REFRESH,\n exp: this.buildExp(input),\n ...(iss ? { iss } : {}),\n });\n\n const token = await this.signer.sign(data);\n\n await this.repository.saveWithSignature(data, token);\n\n if (this.sessionTokenRepository && data.session_id && data.jti && typeof data.exp === 'number') {\n await this.sessionTokenRepository.create({\n id: data.jti,\n sessionId: data.session_id,\n kind: 'refresh',\n parentId: input.parent_id ?? null,\n ipAddress: data.remote_address ?? '',\n userAgent: data.user_agent ?? '',\n expiresAt: new Date(data.exp * 1000).toISOString(),\n });\n }\n\n return [token, data];\n }\n}\n"],"mappings":";;;;;;AAeA,IAAaE,2BAAb,cAA8CD,sBAAAA;CAChCE;CAEAC;CAEAC;CAEV,YACIF,YACAC,QACAE,UAAoC,CAAC,GACrCD,wBACF;EACE,MAAMC,OAAAA;EAEN,KAAKH,aAAaA;EAClB,KAAKC,SAASA;EACd,KAAKC,yBAAyBA;CAClC;CAEA,MAAME,MAAMC,QAA4B,CAAC,GAAwC;EAC7E,MAAMC,MAAM,KAAKC,SAASF,KAAAA;EAE1B,MAAMG,OAAO,MAAM,KAAKR,WAAWS,OAAO;GACtC,GAAGJ;GACHK,MAAMb,gBAAgBc;GACtBC,KAAK,KAAKC,SAASR,KAAAA;GACnB,GAAIC,MAAM,EAAEA,IAAI,IAAI,CAAC;EACzB,CAAA;EAEA,MAAMQ,QAAQ,MAAM,KAAKb,OAAOc,KAAKP,IAAAA;EAErC,MAAM,KAAKR,WAAWgB,kBAAkBR,MAAMM,KAAAA;EAE9C,IAAI,KAAKZ,0BAA0BM,KAAKS,cAAcT,KAAKU,OAAO,OAAOV,KAAKI,QAAQ,UAClF,MAAM,KAAKV,uBAAuBiB,OAAO;GACrCC,IAAIZ,KAAKU;GACTG,WAAWb,KAAKS;GAChBP,MAAM;GACNY,UAAUjB,MAAMkB,aAAa;GAC7BC,WAAWhB,KAAKiB,kBAAkB;GAClCC,WAAWlB,KAAKmB,cAAc;GAC9BC,4BAAW,IAAIC,KAAKrB,KAAKI,MAAM,GAAA,EAAA,CAAMkB,YAAW;EACpD,CAAA;EAGJ,OAAO,CAAChB,OAAON,IAAK;CACxB;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"module.mjs","names":["extractTokenHeader","verifyToken","KeyStatus","JWKError","JWKType","JWKUse","JWTError","OAuth2TokenVerifier","keyStore","tokenRepository","isInactive","id","verify","token","options","payload","findOneBySignature","jti","payloadPropertyInvalid","skipActiveCheck","notActive","header","kid","headerPropertyInvalid","key","resolveById","notFound","use","SIGNATURE","status","DISABLED","type","OCT","decryptionKey","decryptionKeyMissing","ignoreExpiry","EC","encryptionKey","signatureAlgorithm","algorithms","saveWithSignature"],"sources":["../../../../../src/core/oauth2/token/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n type TokenECAlgorithm,\n type TokenRSAAlgorithm,\n extractTokenHeader,\n verifyToken,\n} from '@authup/server-kit';\nimport { KeyStatus } from '@authup/core-kit';\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport {\n JWKError,\n JWKType,\n JWKUse,\n JWTError,\n} from '@authup/specs';\nimport type { IKeyStore } from '../../../key/index.ts';\nimport type { IOAuth2TokenRepository } from '../repository/types.ts';\nimport type { IOAuth2TokenVerifier, OAuth2TokenVerifyOptions } from './types.ts';\n\nexport class OAuth2TokenVerifier implements IOAuth2TokenVerifier {\n protected keyStore : IKeyStore;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n constructor(\n keyStore : IKeyStore,\n tokenRepository : IOAuth2TokenRepository,\n ) {\n this.keyStore = keyStore;\n this.tokenRepository = tokenRepository;\n }\n\n async isInactive(id: string): Promise<boolean> {\n return this.tokenRepository.isInactive(id);\n }\n\n async verify(token: string, options: OAuth2TokenVerifyOptions = {}) : Promise<OAuth2TokenPayload> {\n let payload = await this.tokenRepository.findOneBySignature(token);\n if (payload) {\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n\n const header = extractTokenHeader(token);\n if (!header.kid) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n const key = await this.keyStore.resolveById(header.kid);\n if (!key) {\n throw JWKError.notFound(header.kid);\n }\n\n // the key store also holds at-rest encryption keys (use: enc) —\n // those must never verify a token.\n if (key.use !== JWKUse.SIGNATURE) {\n throw JWKError.notFound(header.kid);\n }\n\n // disabled = neither signs nor verifies (passive still verifies).\n if (key.status === KeyStatus.DISABLED) {\n throw JWKError.notFound(header.kid);\n }\n\n switch (key.type) {\n case JWKType.OCT: {\n if (!key.decryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: JWKType.OCT,\n key: key.decryptionKey,\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n case JWKType.EC: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenECAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n default: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenRSAAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n }\n }\n\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n // Never populate the shared signature-keyed claims cache on the\n // exp-ignoring path. An expired token verified with `ignoreExpiry`\n // (the RP-initiated-logout id_token_hint) would otherwise be re-cached\n // with buildTTL's 1h fallback (a past exp yields a non-positive ttl),\n // and the cache-first branch above returns it with no exp re-check on\n // every subsequent verify — so `/token/introspect` would report an\n // expired token as active for up to an hour (RFC 7662). Keep the\n // exp-bypass scoped to this single call.\n if (!options.ignoreExpiry) {\n await this.tokenRepository.saveWithSignature(payload, token);\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n}\n"],"mappings":";;;;;;;AAyBA,IAAaO,sBAAb,MAAaA;CACCC;CAEAC;CAEV,YACID,UACAC,iBACF;EACE,KAAKD,WAAWA;EAChB,KAAKC,kBAAkBA;CAC3B;CAEA,MAAMC,WAAWC,IAA8B;EAC3C,OAAO,KAAKF,gBAAgBC,WAAWC,EAAAA;CAC3C;CAEA,MAAMC,OAAOC,OAAeC,UAAoC,CAAC,GAAiC;EAC9F,IAAIC,UAAU,MAAM,KAAKN,gBAAgBO,mBAAmBH,KAAAA;EAC5D,IAAIE,SAAS;GACT,IAAI,CAACA,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;GAG1C,IAAI,CAACJ,QAAQK;QAELT,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;GAAA;GAIhC,OAAOL;EACX;EAEA,MAAMM,SAASrB,mBAAmBa,KAAAA;EAClC,IAAI,CAACQ,OAAOC,KACR,MAAMhB,SAASiB,sBAAsB,KAAA;EAGzC,MAAMC,MAAM,MAAM,KAAKhB,SAASiB,YAAYJ,OAAOC,GAAG;EACtD,IAAI,CAACE,KACD,MAAMrB,SAASuB,SAASL,OAAOC,GAAG;EAKtC,IAAIE,IAAIG,QAAQtB,OAAOuB,WACnB,MAAMzB,SAASuB,SAASL,OAAOC,GAAG;EAItC,IAAIE,IAAIK,WAAW3B,UAAU4B,UACzB,MAAM3B,SAASuB,SAASL,OAAOC,GAAG;EAGtC,QAAQE,IAAIO,MAAZ;GACI,KAAK3B,QAAQ4B;IACT,IAAI,CAACR,IAAIS,eACL,MAAM9B,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAM3B,QAAQ4B;KACdR,KAAKA,IAAIS;IACb,GACA,EAAEE,cAAcrB,QAAQqB,aAAa,CAAA;IAEzC;GAEJ,KAAK/B,QAAQgC;IACT,IAAI,CAACZ,IAAIa,eACL,MAAMlC,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAMP,IAAIO;KACVP,KAAKA,IAAIa;KACT,GACIb,IAAIc,qBACA,EAAEC,YAAY,CAACf,IAAIc,kBAAAA,EAAoB,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcrB,QAAQqB,aAAa,CAAA;IAEzC;GAEJ;IACI,IAAI,CAACX,IAAIa,eACL,MAAMlC,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAMP,IAAIO;KACVP,KAAKA,IAAIa;KACT,GACIb,IAAIc,qBACA,EAAEC,YAAY,CAACf,IAAIc,kBAAAA,EAAoB,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcrB,QAAQqB,aAAa,CAAA;EAGjD;EAEA,IAAI,CAACpB,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;EAW1C,IAAI,CAACJ,QAAQqB,cACT,MAAM,KAAK1B,gBAAgB+B,kBAAkBzB,SAASF,KAAAA;EAG1D,IAAI,CAACC,QAAQK;OAELT,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;EAAA;EAIhC,OAAOL;CACX;AACJ"}
1
+ {"version":3,"file":"module.mjs","names":["extractTokenHeader","verifyToken","KeyStatus","JWKError","JWKType","JWKUse","JWTError","OAuth2TokenVerifier","keyStore","tokenRepository","isInactive","id","verify","token","options","payload","findOneBySignature","jti","payloadPropertyInvalid","skipActiveCheck","notActive","header","kid","headerPropertyInvalid","key","resolveById","notFound","use","SIGNATURE","status","DISABLED","type","OCT","decryptionKey","decryptionKeyMissing","ignoreExpiry","EC","encryptionKey","signatureAlgorithm","algorithms","saveWithSignature"],"sources":["../../../../../src/core/oauth2/token/verifier/module.ts"],"sourcesContent":["/*\n * Copyright (c) 2025.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport {\n type TokenECAlgorithm,\n type TokenRSAAlgorithm,\n extractTokenHeader,\n verifyToken,\n} from '@authup/server-kit';\nimport { KeyStatus } from '@authup/core-kit';\nimport type { OAuth2TokenPayload } from '@authup/specs';\nimport {\n JWKError,\n JWKType,\n JWKUse,\n JWTError,\n} from '@authup/specs';\nimport type { IKeyStore } from '../../../key/index.ts';\nimport type { IOAuth2TokenRepository } from '../repository/types.ts';\nimport type { IOAuth2TokenVerifier, OAuth2TokenVerifyOptions } from './types.ts';\n\nexport class OAuth2TokenVerifier implements IOAuth2TokenVerifier {\n protected keyStore : IKeyStore;\n\n protected tokenRepository : IOAuth2TokenRepository;\n\n constructor(\n keyStore : IKeyStore,\n tokenRepository : IOAuth2TokenRepository,\n ) {\n this.keyStore = keyStore;\n this.tokenRepository = tokenRepository;\n }\n\n async isInactive(id: string): Promise<boolean> {\n return this.tokenRepository.isInactive(id);\n }\n\n async verify(token: string, options: OAuth2TokenVerifyOptions = {}) : Promise<OAuth2TokenPayload> {\n let payload = await this.tokenRepository.findOneBySignature(token);\n if (payload) {\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n\n const header = extractTokenHeader(token);\n if (!header.kid) {\n throw JWTError.headerPropertyInvalid('kid');\n }\n\n const key = await this.keyStore.resolveById(header.kid);\n if (!key) {\n throw JWKError.notFound(header.kid);\n }\n\n // the key store also holds at-rest encryption keys (use: enc) —\n // those must never verify a token.\n if (key.use !== JWKUse.SIGNATURE) {\n throw JWKError.notFound(header.kid);\n }\n\n // disabled = neither signs nor verifies (passive still verifies).\n if (key.status === KeyStatus.DISABLED) {\n throw JWKError.notFound(header.kid);\n }\n\n switch (key.type) {\n case JWKType.OCT: {\n if (!key.decryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: JWKType.OCT,\n key: key.decryptionKey,\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n case JWKType.EC: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenECAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n break;\n }\n default: {\n if (!key.encryptionKey) {\n throw JWKError.decryptionKeyMissing();\n }\n\n payload = await verifyToken(\n token,\n {\n type: key.type,\n key: key.encryptionKey,\n ...(\n key.signatureAlgorithm ?\n { algorithms: [key.signatureAlgorithm] } :\n []\n ) as TokenRSAAlgorithm[],\n },\n { ignoreExpiry: options.ignoreExpiry },\n );\n }\n }\n\n if (!payload.jti) {\n throw JWTError.payloadPropertyInvalid('jti');\n }\n\n // Never populate the shared signature-keyed claims cache on the\n // exp-ignoring path. An expired token verified with `ignoreExpiry`\n // (the RP-initiated-logout id_token_hint) would otherwise be re-cached\n // with buildTTL's 1h fallback (a past exp yields a non-positive ttl),\n // and the cache-first branch above returns it with no exp re-check on\n // every subsequent verify — so `/token/introspect` would report an\n // expired token as active for up to an hour (RFC 7662). Keep the\n // exp-bypass scoped to this single call.\n if (!options.ignoreExpiry) {\n await this.tokenRepository.saveWithSignature(payload, token);\n }\n\n if (!options.skipActiveCheck) {\n const isInactive = await this.isInactive(payload.jti);\n if (isInactive) {\n throw JWTError.notActive();\n }\n }\n\n return payload;\n }\n}\n"],"mappings":";;;;;;;AAyBA,IAAaO,sBAAb,MAAaA;CACCC;CAEAC;CAEV,YACID,UACAC,iBACF;EACE,KAAKD,WAAWA;EAChB,KAAKC,kBAAkBA;CAC3B;CAEA,MAAMC,WAAWC,IAA8B;EAC3C,OAAO,KAAKF,gBAAgBC,WAAWC,EAAAA;CAC3C;CAEA,MAAMC,OAAOC,OAAeC,UAAoC,CAAC,GAAiC;EAC9F,IAAIC,UAAU,MAAM,KAAKN,gBAAgBO,mBAAmBH,KAAAA;EAC5D,IAAIE,SAAS;GACT,IAAI,CAACA,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;GAG1C,IAAI,CAACJ,QAAQK,iBAELT;QAAAA,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;GAAA;GAIhC,OAAOL;EACX;EAEA,MAAMM,SAASrB,mBAAmBa,KAAAA;EAClC,IAAI,CAACQ,OAAOC,KACR,MAAMhB,SAASiB,sBAAsB,KAAA;EAGzC,MAAMC,MAAM,MAAM,KAAKhB,SAASiB,YAAYJ,OAAOC,GAAG;EACtD,IAAI,CAACE,KACD,MAAMrB,SAASuB,SAASL,OAAOC,GAAG;EAKtC,IAAIE,IAAIG,QAAQtB,OAAOuB,WACnB,MAAMzB,SAASuB,SAASL,OAAOC,GAAG;EAItC,IAAIE,IAAIK,WAAW3B,UAAU4B,UACzB,MAAM3B,SAASuB,SAASL,OAAOC,GAAG;EAGtC,QAAQE,IAAIO,MAAZ;GACI,KAAK3B,QAAQ4B;IACT,IAAI,CAACR,IAAIS,eACL,MAAM9B,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAM3B,QAAQ4B;KACdR,KAAKA,IAAIS;IACb,GACA,EAAEE,cAAcrB,QAAQqB,aAAa,CAAA;IAEzC;GAEJ,KAAK/B,QAAQgC;IACT,IAAI,CAACZ,IAAIa,eACL,MAAMlC,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAMP,IAAIO;KACVP,KAAKA,IAAIa;KACT,GACIb,IAAIc,qBACA,EAAEC,YAAY,CAACf,IAAIc,kBAAmB,EAAC,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcrB,QAAQqB,aAAa,CAAA;IAEzC;GAEJ;IACI,IAAI,CAACX,IAAIa,eACL,MAAMlC,SAAS+B,qBAAoB;IAGvCnB,UAAU,MAAMd,YACZY,OACA;KACIkB,MAAMP,IAAIO;KACVP,KAAKA,IAAIa;KACT,GACIb,IAAIc,qBACA,EAAEC,YAAY,CAACf,IAAIc,kBAAmB,EAAC,IACvC,CAAA;IAEZ,GACA,EAAEH,cAAcrB,QAAQqB,aAAa,CAAA;EAGjD;EAEA,IAAI,CAACpB,QAAQE,KACT,MAAMX,SAASY,uBAAuB,KAAA;EAW1C,IAAI,CAACJ,QAAQqB,cACT,MAAM,KAAK1B,gBAAgB+B,kBAAkBzB,SAASF,KAAAA;EAG1D,IAAI,CAACC,QAAQK,iBAELT;OAAAA,MADqB,KAAKA,WAAWK,QAAQE,GAAG,GAEhD,MAAMX,SAASc,UAAS;EAAA;EAIhC,OAAOL;CACX;AACJ"}
@@ -1 +1 @@
1
- {"version":3,"file":"validator.mjs","names":["BuiltInPolicyType","PolicyValidator","createValidator","Container","ValidupError","defineIssueItem","z","ProvisioningStrategyValidator","createProvisioningEntitiesValidator","PolicyProvisioningValidator","run","input","options","output","attributes","type","COMPOSITE","children","length","path","message","initialize","strategyValidator","mount","optional","attributesValidator","record","string","any","array","check"],"sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuiltInPolicyType } from '@authup/access';\nimport { PolicyValidator } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions } from 'validup';\nimport { Container, ValidupError, defineIssueItem } from 'validup';\nimport { z } from 'zod';\nimport { ProvisioningStrategyValidator } from '../../strategy/index.ts';\nimport { createProvisioningEntitiesValidator } from '../utils.ts';\nimport type { PolicyProvisioningEntity } from './types.ts';\n\nexport class PolicyProvisioningValidator extends Container<PolicyProvisioningEntity> {\n override async run(\n input?: ContainerInput<PolicyProvisioningEntity>,\n options?: ContainerRunOptions<PolicyProvisioningEntity>,\n ): Promise<PolicyProvisioningEntity> {\n const output = await super.run(input, options);\n\n // A composite policy with no children can never be satisfied and makes\n // any permission bound to it permanently un-grantable (#3304). Reject\n // it up front so provisioning fails at config load with a clear\n // message, rather than silently provisioning a \"stale\" permission that\n // only fails — with an opaque empty-issue error — once evaluated.\n if (\n output.attributes?.type === BuiltInPolicyType.COMPOSITE &&\n (!output.children || output.children.length === 0)\n ) {\n throw new ValidupError([\n defineIssueItem({\n path: ['children'],\n message: 'A composite policy must define at least one child policy.',\n }),\n ]);\n }\n\n return output;\n }\n\n protected initialize() {\n super.initialize();\n\n const strategyValidator = new ProvisioningStrategyValidator();\n this.mount('strategy', { optional: true }, strategyValidator);\n\n const attributesValidator = new PolicyValidator();\n this.mount('attributes', attributesValidator);\n\n this.mount('extraAttributes', { optional: true }, createValidator(\n z.record(z.string(), z.any()),\n ));\n\n this.mount('children', { optional: true }, createValidator(\n z\n .array(z.any())\n .check(createProvisioningEntitiesValidator(this)),\n ));\n }\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,IAAaS,8BAAb,cAAiDN,UAAAA;CAC7C,MAAeO,IACXC,OACAC,SACiC;EACjC,MAAMC,SAAS,MAAM,MAAMH,IAAIC,OAAOC,OAAAA;EAOtC,IACIC,OAAOC,YAAYC,SAASf,kBAAkBgB,cAC7C,CAACH,OAAOI,YAAYJ,OAAOI,SAASC,WAAW,IAEhD,MAAM,IAAId,aAAa,CACnBC,gBAAgB;GACZc,MAAM,CAAC,UAAA;GACPC,SAAS;EACb,CAAA,CAAA,CACH;EAGL,OAAOP;CACX;CAEUQ,aAAa;EACnB,MAAMA,WAAAA;EAEN,MAAMC,oBAAoB,IAAIf,8BAAAA;EAC9B,KAAKgB,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGF,iBAAAA;EAE3C,MAAMG,sBAAsB,IAAIxB,gBAAAA;EAChC,KAAKsB,MAAM,cAAcE,mBAAAA;EAEzB,KAAKF,MAAM,mBAAmB,EAAEC,UAAU,KAAK,GAAGtB,gBAC9CI,EAAEoB,OAAOpB,EAAEqB,OAAM,GAAIrB,EAAEsB,IAAG,CAAA,CAAA,CAAA;EAG9B,KAAKL,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGtB,gBACvCI,EACKuB,MAAMvB,EAAEsB,IAAG,CAAA,CAAA,CACXE,MAAMtB,oCAAoC,IAAI,CAAA,CAAA,CAAA;CAE3D;AACJ"}
1
+ {"version":3,"file":"validator.mjs","names":["BuiltInPolicyType","PolicyValidator","createValidator","Container","ValidupError","defineIssueItem","z","ProvisioningStrategyValidator","createProvisioningEntitiesValidator","PolicyProvisioningValidator","run","input","options","output","attributes","type","COMPOSITE","children","length","path","message","initialize","strategyValidator","mount","optional","attributesValidator","record","string","any","array","check"],"sources":["../../../../../src/core/provisioning/entities/policy/validator.ts"],"sourcesContent":["/*\n * Copyright (c) 2026.\n * Author Peter Placzek (tada5hi)\n * For the full copyright and license information,\n * view the LICENSE file that was distributed with this source code.\n */\n\nimport { BuiltInPolicyType } from '@authup/access';\nimport { PolicyValidator } from '@authup/core-kit';\nimport { createValidator } from '@validup/zod';\nimport type { ContainerInput, ContainerRunOptions } from 'validup';\nimport { Container, ValidupError, defineIssueItem } from 'validup';\nimport { z } from 'zod';\nimport { ProvisioningStrategyValidator } from '../../strategy/index.ts';\nimport { createProvisioningEntitiesValidator } from '../utils.ts';\nimport type { PolicyProvisioningEntity } from './types.ts';\n\nexport class PolicyProvisioningValidator extends Container<PolicyProvisioningEntity> {\n override async run(\n input?: ContainerInput<PolicyProvisioningEntity>,\n options?: ContainerRunOptions<PolicyProvisioningEntity>,\n ): Promise<PolicyProvisioningEntity> {\n const output = await super.run(input, options);\n\n // A composite policy with no children can never be satisfied and makes\n // any permission bound to it permanently un-grantable (#3304). Reject\n // it up front so provisioning fails at config load with a clear\n // message, rather than silently provisioning a \"stale\" permission that\n // only fails — with an opaque empty-issue error — once evaluated.\n if (\n output.attributes?.type === BuiltInPolicyType.COMPOSITE &&\n (!output.children || output.children.length === 0)\n ) {\n throw new ValidupError([\n defineIssueItem({\n path: ['children'],\n message: 'A composite policy must define at least one child policy.',\n }),\n ]);\n }\n\n return output;\n }\n\n protected initialize() {\n super.initialize();\n\n const strategyValidator = new ProvisioningStrategyValidator();\n this.mount('strategy', { optional: true }, strategyValidator);\n\n const attributesValidator = new PolicyValidator();\n this.mount('attributes', attributesValidator);\n\n this.mount('extraAttributes', { optional: true }, createValidator(\n z.record(z.string(), z.any()),\n ));\n\n this.mount('children', { optional: true }, createValidator(\n z\n .array(z.any())\n .check(createProvisioningEntitiesValidator(this)),\n ));\n }\n}\n"],"mappings":";;;;;;;;;;;;AAiBA,IAAaS,8BAAb,cAAiDN,UAAAA;CAC7C,MAAeO,IACXC,OACAC,SACiC;EACjC,MAAMC,SAAS,MAAM,MAAMH,IAAIC,OAAOC,OAAAA;EAOtC,IACIC,OAAOC,YAAYC,SAASf,kBAAkBgB,cAC7C,CAACH,OAAOI,YAAYJ,OAAOI,SAASC,WAAW,IAEhD,MAAM,IAAId,aAAa,CACnBC,gBAAgB;GACZc,MAAM,CAAC,UAAW;GAClBC,SAAS;EACb,CAAA,CACH,CAAA;EAGL,OAAOP;CACX;CAEUQ,aAAa;EACnB,MAAMA,WAAAA;EAEN,MAAMC,oBAAoB,IAAIf,8BAAAA;EAC9B,KAAKgB,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGF,iBAAAA;EAE3C,MAAMG,sBAAsB,IAAIxB,gBAAAA;EAChC,KAAKsB,MAAM,cAAcE,mBAAAA;EAEzB,KAAKF,MAAM,mBAAmB,EAAEC,UAAU,KAAK,GAAGtB,gBAC9CI,EAAEoB,OAAOpB,EAAEqB,OAAM,GAAIrB,EAAEsB,IAAG,CAAA,CAAA,CAAA;EAG9B,KAAKL,MAAM,YAAY,EAAEC,UAAU,KAAK,GAAGtB,gBACvCI,EACKuB,MAAMvB,EAAEsB,IAAG,CAAA,CAAA,CACXE,MAAMtB,oCAAoC,IAAI,CAAA,CAAA,CAAA;CAE3D;AACJ"}