@bajustone/fortress 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (465) hide show
  1. package/CHANGELOG.md +1011 -0
  2. package/LICENSE +21 -0
  3. package/README.md +760 -0
  4. package/SECURITY.md +197 -0
  5. package/bin/fortress.ts +667 -0
  6. package/dist/auth-service-BcyQ2PuZ.d.cts +307 -0
  7. package/dist/auth-service-BkzZkWfH.d.ts +307 -0
  8. package/dist/config-B2366s_G.d.ts +665 -0
  9. package/dist/config-GtlVt6ZD.d.cts +665 -0
  10. package/dist/convert-routes-BLCQT57f.d.ts +72 -0
  11. package/dist/convert-routes-BdMvP2_X.d.cts +72 -0
  12. package/dist/crypto.cjs +61 -0
  13. package/dist/crypto.d.cts +36 -0
  14. package/dist/crypto.d.ts +36 -0
  15. package/dist/crypto.js +35 -0
  16. package/dist/drift-BT1wtMDJ.d.cts +22 -0
  17. package/dist/drift-k9LiYpRP.d.ts +22 -0
  18. package/dist/drizzle/pg.cjs +481 -0
  19. package/dist/drizzle/pg.d.cts +15 -0
  20. package/dist/drizzle/pg.d.ts +15 -0
  21. package/dist/drizzle/pg.js +454 -0
  22. package/dist/drizzle.cjs +1442 -0
  23. package/dist/drizzle.d.cts +85 -0
  24. package/dist/drizzle.d.ts +85 -0
  25. package/dist/drizzle.js +1411 -0
  26. package/dist/express.cjs +1357 -0
  27. package/dist/express.d.cts +333 -0
  28. package/dist/express.d.ts +333 -0
  29. package/dist/express.js +1314 -0
  30. package/dist/fetcher.cjs +77 -0
  31. package/dist/fetcher.d.cts +7 -0
  32. package/dist/fetcher.d.ts +7 -0
  33. package/dist/fetcher.js +41 -0
  34. package/dist/fortress-BmSvFfqK.d.cts +1089 -0
  35. package/dist/fortress-uA-_cheR.d.ts +1089 -0
  36. package/dist/hono.cjs +1530 -0
  37. package/dist/hono.d.cts +514 -0
  38. package/dist/hono.d.ts +514 -0
  39. package/dist/hono.js +1483 -0
  40. package/dist/index-CxEkfCA0.d.cts +77 -0
  41. package/dist/index-CxEkfCA0.d.ts +77 -0
  42. package/dist/index-D054pcb-.d.cts +146 -0
  43. package/dist/index-jAMbbUAY.d.ts +146 -0
  44. package/dist/index.cjs +9046 -0
  45. package/dist/index.d.cts +717 -0
  46. package/dist/index.d.ts +717 -0
  47. package/dist/index.js +8935 -0
  48. package/dist/jwt.cjs +255 -0
  49. package/dist/jwt.d.cts +90 -0
  50. package/dist/jwt.d.ts +90 -0
  51. package/dist/jwt.js +227 -0
  52. package/dist/otel.cjs +108 -0
  53. package/dist/otel.d.cts +62 -0
  54. package/dist/otel.d.ts +62 -0
  55. package/dist/otel.js +73 -0
  56. package/dist/plugins/account-lockout.cjs +322 -0
  57. package/dist/plugins/account-lockout.d.cts +42 -0
  58. package/dist/plugins/account-lockout.d.ts +42 -0
  59. package/dist/plugins/account-lockout.js +295 -0
  60. package/dist/plugins/admin.cjs +2378 -0
  61. package/dist/plugins/admin.d.cts +72 -0
  62. package/dist/plugins/admin.d.ts +72 -0
  63. package/dist/plugins/admin.js +2351 -0
  64. package/dist/plugins/api-key.cjs +792 -0
  65. package/dist/plugins/api-key.d.cts +121 -0
  66. package/dist/plugins/api-key.d.ts +121 -0
  67. package/dist/plugins/api-key.js +765 -0
  68. package/dist/plugins/audit-log.cjs +493 -0
  69. package/dist/plugins/audit-log.d.cts +86 -0
  70. package/dist/plugins/audit-log.d.ts +86 -0
  71. package/dist/plugins/audit-log.js +468 -0
  72. package/dist/plugins/data-isolation.cjs +211 -0
  73. package/dist/plugins/data-isolation.d.cts +49 -0
  74. package/dist/plugins/data-isolation.d.ts +49 -0
  75. package/dist/plugins/data-isolation.js +186 -0
  76. package/dist/plugins/email-verification.cjs +273 -0
  77. package/dist/plugins/email-verification.d.cts +44 -0
  78. package/dist/plugins/email-verification.d.ts +44 -0
  79. package/dist/plugins/email-verification.js +246 -0
  80. package/dist/plugins/magic-link.cjs +231 -0
  81. package/dist/plugins/magic-link.d.cts +39 -0
  82. package/dist/plugins/magic-link.d.ts +39 -0
  83. package/dist/plugins/magic-link.js +204 -0
  84. package/dist/plugins/oauth.cjs +1696 -0
  85. package/dist/plugins/oauth.d.cts +406 -0
  86. package/dist/plugins/oauth.d.ts +406 -0
  87. package/dist/plugins/oauth.js +1669 -0
  88. package/dist/plugins/openapi.cjs +1185 -0
  89. package/dist/plugins/openapi.d.cts +6 -0
  90. package/dist/plugins/openapi.d.ts +6 -0
  91. package/dist/plugins/openapi.js +1158 -0
  92. package/dist/plugins/rate-limit/express.cjs +55 -0
  93. package/dist/plugins/rate-limit/express.d.cts +64 -0
  94. package/dist/plugins/rate-limit/express.d.ts +64 -0
  95. package/dist/plugins/rate-limit/express.js +30 -0
  96. package/dist/plugins/rate-limit/hono.cjs +51 -0
  97. package/dist/plugins/rate-limit/hono.d.cts +59 -0
  98. package/dist/plugins/rate-limit/hono.d.ts +59 -0
  99. package/dist/plugins/rate-limit/hono.js +26 -0
  100. package/dist/plugins/rate-limit/sveltekit.cjs +49 -0
  101. package/dist/plugins/rate-limit/sveltekit.d.cts +59 -0
  102. package/dist/plugins/rate-limit/sveltekit.d.ts +59 -0
  103. package/dist/plugins/rate-limit/sveltekit.js +24 -0
  104. package/dist/plugins/rate-limit.cjs +354 -0
  105. package/dist/plugins/rate-limit.d.cts +140 -0
  106. package/dist/plugins/rate-limit.d.ts +140 -0
  107. package/dist/plugins/rate-limit.js +325 -0
  108. package/dist/plugins/social-login.cjs +905 -0
  109. package/dist/plugins/social-login.d.cts +114 -0
  110. package/dist/plugins/social-login.d.ts +114 -0
  111. package/dist/plugins/social-login.js +880 -0
  112. package/dist/plugins/tenancy.cjs +780 -0
  113. package/dist/plugins/tenancy.d.cts +108 -0
  114. package/dist/plugins/tenancy.d.ts +108 -0
  115. package/dist/plugins/tenancy.js +753 -0
  116. package/dist/plugins/two-factor.cjs +555 -0
  117. package/dist/plugins/two-factor.d.cts +67 -0
  118. package/dist/plugins/two-factor.d.ts +67 -0
  119. package/dist/plugins/two-factor.js +526 -0
  120. package/dist/plugins/webauthn.cjs +786 -0
  121. package/dist/plugins/webauthn.d.cts +72 -0
  122. package/dist/plugins/webauthn.d.ts +72 -0
  123. package/dist/plugins/webauthn.js +766 -0
  124. package/dist/plugins/webhook.cjs +819 -0
  125. package/dist/plugins/webhook.d.cts +254 -0
  126. package/dist/plugins/webhook.d.ts +254 -0
  127. package/dist/plugins/webhook.js +789 -0
  128. package/dist/protect-D1v_0upK.d.cts +123 -0
  129. package/dist/protect-DsxV_-dJ.d.ts +123 -0
  130. package/dist/sveltekit.cjs +1258 -0
  131. package/dist/sveltekit.d.cts +420 -0
  132. package/dist/sveltekit.d.ts +420 -0
  133. package/dist/sveltekit.js +1207 -0
  134. package/dist/testing.cjs +4294 -0
  135. package/dist/testing.d.cts +197 -0
  136. package/dist/testing.d.ts +197 -0
  137. package/dist/testing.js +4264 -0
  138. package/dist/types-et0R8tsC.d.cts +217 -0
  139. package/dist/types-et0R8tsC.d.ts +217 -0
  140. package/docs/adapter-typed-helpers.md +192 -0
  141. package/docs/admin-recipes.md +227 -0
  142. package/docs/architecture.md +434 -0
  143. package/docs/ci/github-actions.yml +59 -0
  144. package/docs/ci.md +109 -0
  145. package/docs/compatibility.md +115 -0
  146. package/docs/deployment.md +305 -0
  147. package/docs/hardening.md +118 -0
  148. package/docs/host-owned-routes.md +154 -0
  149. package/docs/migrations/0001-schema-version.md +54 -0
  150. package/docs/migrations/0002-initial-schema.md +84 -0
  151. package/docs/migrations/upgrade-guide.md +160 -0
  152. package/docs/observability.md +394 -0
  153. package/docs/plugins/account-lockout.md +131 -0
  154. package/docs/plugins/admin.md +402 -0
  155. package/docs/plugins/api-key.md +327 -0
  156. package/docs/plugins/audit-log.md +261 -0
  157. package/docs/plugins/data-isolation.md +186 -0
  158. package/docs/plugins/email-verification.md +121 -0
  159. package/docs/plugins/magic-link.md +123 -0
  160. package/docs/plugins/oauth.md +544 -0
  161. package/docs/plugins/openapi.md +250 -0
  162. package/docs/plugins/rate-limit.md +223 -0
  163. package/docs/plugins/social-login.md +337 -0
  164. package/docs/plugins/tenancy.md +122 -0
  165. package/docs/plugins/two-factor.md +191 -0
  166. package/docs/plugins/webauthn.md +188 -0
  167. package/docs/plugins/webhook.md +242 -0
  168. package/docs/policy-as-code.md +156 -0
  169. package/docs/route-manifest.md +46 -0
  170. package/docs/security.md +261 -0
  171. package/docs/threat-model.md +161 -0
  172. package/examples/README.md +119 -0
  173. package/examples/express-app/index.ts +747 -0
  174. package/examples/hono-app/docker-compose.yml +14 -0
  175. package/examples/hono-app/index.ts +1009 -0
  176. package/examples/policy/fortress.policy.json +47 -0
  177. package/examples/sveltekit-app/README.md +125 -0
  178. package/examples/sveltekit-app/src/app.d.ts +16 -0
  179. package/examples/sveltekit-app/src/hooks.server.ts +23 -0
  180. package/examples/sveltekit-app/src/lib/server/fortress.ts +81 -0
  181. package/examples/sveltekit-app/src/routes/api/echo/[id]/+server.ts +32 -0
  182. package/examples/sveltekit-app/src/routes/api/fortress/[...path]/+server.ts +15 -0
  183. package/examples/sveltekit-app/src/routes/dashboard/+page.server.ts +20 -0
  184. package/examples/sveltekit-app/src/routes/login/+page.server.ts +48 -0
  185. package/examples/sveltekit-app/src/routes/oauth/consent/+page.server.ts +69 -0
  186. package/examples/sveltekit-app/src/routes/oauth/consent/+page.svelte +83 -0
  187. package/migrations/pg/0001_schema_version.down.sql +2 -0
  188. package/migrations/pg/0001_schema_version.sql +8 -0
  189. package/migrations/pg/0002_initial_schema.down.sql +36 -0
  190. package/migrations/pg/0002_initial_schema.sql +376 -0
  191. package/migrations/pg/0003_auth_continuation.down.sql +6 -0
  192. package/migrations/pg/0003_auth_continuation.sql +30 -0
  193. package/migrations/pg/0004_tenant_default_unique.down.sql +1 -0
  194. package/migrations/pg/0004_tenant_default_unique.sql +14 -0
  195. package/migrations/pg/0005_hot_indexes_timestamptz.down.sql +71 -0
  196. package/migrations/pg/0005_hot_indexes_timestamptz.sql +71 -0
  197. package/migrations/pg/0006_canonical_email.down.sql +3 -0
  198. package/migrations/pg/0006_canonical_email.sql +8 -0
  199. package/migrations/pg/0007_audit_chain_anchor.down.sql +1 -0
  200. package/migrations/pg/0007_audit_chain_anchor.sql +8 -0
  201. package/migrations/pg/0008_two_factor_hardening.down.sql +8 -0
  202. package/migrations/pg/0008_two_factor_hardening.sql +8 -0
  203. package/migrations/pg/0009_encrypt_totp_secrets.down.sql +2 -0
  204. package/migrations/pg/0009_encrypt_totp_secrets.sql +5 -0
  205. package/migrations/pg/0010_bigint_append_only_ids.down.sql +2 -0
  206. package/migrations/pg/0010_bigint_append_only_ids.sql +23 -0
  207. package/migrations/sqlite/0001_schema_version.down.sql +2 -0
  208. package/migrations/sqlite/0001_schema_version.sql +8 -0
  209. package/migrations/sqlite/0002_initial_schema.down.sql +36 -0
  210. package/migrations/sqlite/0002_initial_schema.sql +376 -0
  211. package/migrations/sqlite/0003_auth_continuation.down.sql +27 -0
  212. package/migrations/sqlite/0003_auth_continuation.sql +45 -0
  213. package/migrations/sqlite/0004_tenant_default_unique.down.sql +1 -0
  214. package/migrations/sqlite/0004_tenant_default_unique.sql +13 -0
  215. package/migrations/sqlite/0005_hot_indexes_timestamptz.down.sql +10 -0
  216. package/migrations/sqlite/0005_hot_indexes_timestamptz.sql +10 -0
  217. package/migrations/sqlite/0006_canonical_email.down.sql +3 -0
  218. package/migrations/sqlite/0006_canonical_email.sql +8 -0
  219. package/migrations/sqlite/0007_audit_chain_anchor.down.sql +1 -0
  220. package/migrations/sqlite/0007_audit_chain_anchor.sql +8 -0
  221. package/migrations/sqlite/0008_two_factor_hardening.down.sql +14 -0
  222. package/migrations/sqlite/0008_two_factor_hardening.sql +7 -0
  223. package/migrations/sqlite/0009_encrypt_totp_secrets.down.sql +2 -0
  224. package/migrations/sqlite/0009_encrypt_totp_secrets.sql +5 -0
  225. package/migrations/sqlite/0010_bigint_append_only_ids.down.sql +1 -0
  226. package/migrations/sqlite/0010_bigint_append_only_ids.sql +2 -0
  227. package/package.json +398 -0
  228. package/src/adapters/database/index.ts +63 -0
  229. package/src/adapters/database/types.ts +22 -0
  230. package/src/changelog-script.test.ts +21 -0
  231. package/src/cli.test.ts +79 -0
  232. package/src/core/auth/auth-admin.test.ts +247 -0
  233. package/src/core/auth/auth-endpoints.ts +558 -0
  234. package/src/core/auth/auth-observer.test.ts +191 -0
  235. package/src/core/auth/auth-service.ts +1464 -0
  236. package/src/core/auth/email.test.ts +17 -0
  237. package/src/core/auth/email.ts +11 -0
  238. package/src/core/auth/hooks.test.ts +251 -0
  239. package/src/core/auth/impersonation.test.ts +179 -0
  240. package/src/core/auth/jwt.test.ts +184 -0
  241. package/src/core/auth/jwt.ts +239 -0
  242. package/src/core/auth/login-timing.test.ts +77 -0
  243. package/src/core/auth/password-policy.test.ts +253 -0
  244. package/src/core/auth/password-policy.ts +220 -0
  245. package/src/core/auth/password.test.ts +42 -0
  246. package/src/core/auth/password.ts +62 -0
  247. package/src/core/auth/post-auth-gate.test.ts +258 -0
  248. package/src/core/auth/post-auth-gate.ts +228 -0
  249. package/src/core/auth/refresh-token.test.ts +86 -0
  250. package/src/core/auth/refresh-token.ts +105 -0
  251. package/src/core/auth/timing-safe.ts +23 -0
  252. package/src/core/config.ts +212 -0
  253. package/src/core/endpoint.ts +149 -0
  254. package/src/core/errors.ts +199 -0
  255. package/src/core/fetcher-interop.test.ts +106 -0
  256. package/src/core/fortress.test.ts +354 -0
  257. package/src/core/fortress.ts +550 -0
  258. package/src/core/http/call.test.ts +148 -0
  259. package/src/core/http/call.ts +149 -0
  260. package/src/core/http/cookie-serialize.test.ts +198 -0
  261. package/src/core/http/cookie-serialize.ts +107 -0
  262. package/src/core/http/csrf.test.ts +140 -0
  263. package/src/core/http/csrf.ts +173 -0
  264. package/src/core/http/dispatch.ts +652 -0
  265. package/src/core/http/error-response.test.ts +69 -0
  266. package/src/core/http/error-response.ts +93 -0
  267. package/src/core/http/fortress-rbac.test.ts +43 -0
  268. package/src/core/http/fortress-rbac.ts +127 -0
  269. package/src/core/http/handle-request.test.ts +441 -0
  270. package/src/core/http/handle-request.ts +354 -0
  271. package/src/core/http/match.test.ts +122 -0
  272. package/src/core/http/match.ts +126 -0
  273. package/src/core/http/outbound.test.ts +34 -0
  274. package/src/core/http/outbound.ts +105 -0
  275. package/src/core/http/plugin-middleware.ts +67 -0
  276. package/src/core/http/principal.test.ts +345 -0
  277. package/src/core/http/principal.ts +86 -0
  278. package/src/core/http/protect.test.ts +220 -0
  279. package/src/core/http/protect.ts +394 -0
  280. package/src/core/http/token-extraction.test.ts +59 -0
  281. package/src/core/http/token-extraction.ts +53 -0
  282. package/src/core/iam/explain.test.ts +177 -0
  283. package/src/core/iam/explain.ts +302 -0
  284. package/src/core/iam/iam-endpoints.ts +779 -0
  285. package/src/core/iam/iam-service.test.ts +829 -0
  286. package/src/core/iam/iam-service.ts +1137 -0
  287. package/src/core/iam/permission-cache.test.ts +115 -0
  288. package/src/core/iam/permission-cache.ts +71 -0
  289. package/src/core/iam/permission-check-observer.test.ts +90 -0
  290. package/src/core/iam/permission-evaluator.test.ts +291 -0
  291. package/src/core/iam/permission-evaluator.ts +198 -0
  292. package/src/core/iam/permission-sync.test.ts +166 -0
  293. package/src/core/iam/permission-sync.ts +192 -0
  294. package/src/core/iam/resource-sync.test.ts +70 -0
  295. package/src/core/iam/resource-sync.ts +182 -0
  296. package/src/core/iam/service-account-http.test.ts +529 -0
  297. package/src/core/iam/service-account.test.ts +282 -0
  298. package/src/core/internal-adapter.test.ts +389 -0
  299. package/src/core/internal-adapter.ts +435 -0
  300. package/src/core/json-schema.ts +50 -0
  301. package/src/core/manifest/__snapshots__/route-manifest.test.ts.snap +192 -0
  302. package/src/core/manifest/drift.ts +103 -0
  303. package/src/core/manifest/route-manifest.test.ts +142 -0
  304. package/src/core/manifest/route-manifest.ts +154 -0
  305. package/src/core/migrate.test.ts +72 -0
  306. package/src/core/migrations/engine.test.ts +308 -0
  307. package/src/core/migrations/engine.ts +548 -0
  308. package/src/core/migrations/migrations.ts +1771 -0
  309. package/src/core/migrations/pg-migration.integration-test.ts +353 -0
  310. package/src/core/migrations/upgrade-fixture.test.ts +378 -0
  311. package/src/core/observability/db-instrumentation.ts +205 -0
  312. package/src/core/observability/listener-list.test.ts +124 -0
  313. package/src/core/observability/listener-list.ts +73 -0
  314. package/src/core/observability/logger.test.ts +43 -0
  315. package/src/core/observability/logger.ts +49 -0
  316. package/src/core/observability/spans.test.ts +193 -0
  317. package/src/core/observability/types.ts +80 -0
  318. package/src/core/openapi-drift.test.ts +41 -0
  319. package/src/core/openapi.ts +47 -0
  320. package/src/core/plugin-methods-map.ts +75 -0
  321. package/src/core/plugin-runner.test.ts +233 -0
  322. package/src/core/plugin-runner.ts +276 -0
  323. package/src/core/plugin.ts +210 -0
  324. package/src/core/policy/apply.ts +272 -0
  325. package/src/core/policy/diff.ts +288 -0
  326. package/src/core/policy/loader.test.ts +63 -0
  327. package/src/core/policy/loader.ts +214 -0
  328. package/src/core/policy/policy.test.ts +232 -0
  329. package/src/core/policy/types.ts +105 -0
  330. package/src/core/schema-builder.test.ts +660 -0
  331. package/src/core/schema-builder.ts +881 -0
  332. package/src/core/standard-schema.ts +56 -0
  333. package/src/core/types.ts +258 -0
  334. package/src/core/validation.test.ts +195 -0
  335. package/src/core/validation.ts +192 -0
  336. package/src/drizzle/adapter.test.ts +425 -0
  337. package/src/drizzle/adapter.ts +474 -0
  338. package/src/drizzle/index.ts +31 -0
  339. package/src/drizzle/pg/adapter.integration-test.ts +456 -0
  340. package/src/drizzle/pg/index.ts +13 -0
  341. package/src/drizzle/pg/pg.integration-test.ts +1539 -0
  342. package/src/drizzle/pg/schema.ts +539 -0
  343. package/src/drizzle/pg-error-map.test.ts +218 -0
  344. package/src/drizzle/pg-error-map.ts +151 -0
  345. package/src/drizzle/schema.ts +544 -0
  346. package/src/drizzle/sqlite-serialization.test.ts +106 -0
  347. package/src/express/express-api-key-user-routes.test.ts +241 -0
  348. package/src/express/express.test.ts +442 -0
  349. package/src/express/handle.ts +191 -0
  350. package/src/express/index.ts +62 -0
  351. package/src/express/middleware.ts +551 -0
  352. package/src/express/protect.ts +154 -0
  353. package/src/express/validated.test.ts +86 -0
  354. package/src/express/validated.ts +99 -0
  355. package/src/fetcher/index.ts +81 -0
  356. package/src/hono/convert-routes.test.ts +220 -0
  357. package/src/hono/convert-routes.ts +196 -0
  358. package/src/hono/converters.test.ts +50 -0
  359. package/src/hono/converters.ts +43 -0
  360. package/src/hono/handle.ts +79 -0
  361. package/src/hono/helpers.ts +2 -0
  362. package/src/hono/hono-api-key-user-routes.test.ts +225 -0
  363. package/src/hono/hono-handlers.test.ts +861 -0
  364. package/src/hono/hono.test.ts +454 -0
  365. package/src/hono/index.ts +101 -0
  366. package/src/hono/middleware/auth.ts +236 -0
  367. package/src/hono/middleware/auth.types.test.ts +94 -0
  368. package/src/hono/middleware/csrf.test.ts +127 -0
  369. package/src/hono/middleware/csrf.ts +68 -0
  370. package/src/hono/middleware/error-handler.ts +12 -0
  371. package/src/hono/middleware/plugin-middleware.ts +35 -0
  372. package/src/hono/middleware/rbac.ts +131 -0
  373. package/src/hono/middleware/security-headers.test.ts +88 -0
  374. package/src/hono/middleware/security-headers.ts +68 -0
  375. package/src/hono/openapi.ts +237 -0
  376. package/src/hono/protect.ts +87 -0
  377. package/src/hono/validated.test.ts +123 -0
  378. package/src/hono/validated.ts +62 -0
  379. package/src/index.ts +309 -0
  380. package/src/integration.test.ts +718 -0
  381. package/src/internal/changelog.ts +56 -0
  382. package/src/otel/index.ts +158 -0
  383. package/src/otel/otel-types.test.ts +35 -0
  384. package/src/otel/otel.test.ts +235 -0
  385. package/src/plugins/account-lockout/account-lockout.test.ts +367 -0
  386. package/src/plugins/account-lockout/index.ts +267 -0
  387. package/src/plugins/admin/admin-api-key.test.ts +438 -0
  388. package/src/plugins/admin/index.ts +1612 -0
  389. package/src/plugins/api-key/api-key.test.ts +684 -0
  390. package/src/plugins/api-key/core.ts +336 -0
  391. package/src/plugins/api-key/index.ts +315 -0
  392. package/src/plugins/audit-log/audit-log.test.ts +749 -0
  393. package/src/plugins/audit-log/index.ts +680 -0
  394. package/src/plugins/data-isolation/data-isolation.test.ts +197 -0
  395. package/src/plugins/data-isolation/index.ts +157 -0
  396. package/src/plugins/email-verification/email-verification.test.ts +199 -0
  397. package/src/plugins/email-verification/index.ts +190 -0
  398. package/src/plugins/magic-link/index.ts +129 -0
  399. package/src/plugins/magic-link/magic-link.test.ts +156 -0
  400. package/src/plugins/oauth/id-token.ts +86 -0
  401. package/src/plugins/oauth/index.ts +2145 -0
  402. package/src/plugins/oauth/jwks.test.ts +32 -0
  403. package/src/plugins/oauth/jwks.ts +182 -0
  404. package/src/plugins/oauth/oauth.test.ts +2220 -0
  405. package/src/plugins/oauth/pkce.ts +58 -0
  406. package/src/plugins/openapi/index.ts +298 -0
  407. package/src/plugins/openapi/openapi.test.ts +425 -0
  408. package/src/plugins/openapi/spec-builder.ts +287 -0
  409. package/src/plugins/rate-limit/express.test.ts +89 -0
  410. package/src/plugins/rate-limit/express.ts +86 -0
  411. package/src/plugins/rate-limit/hono.test.ts +60 -0
  412. package/src/plugins/rate-limit/hono.ts +74 -0
  413. package/src/plugins/rate-limit/index.ts +383 -0
  414. package/src/plugins/rate-limit/memory-store.ts +61 -0
  415. package/src/plugins/rate-limit/rate-limit.test.ts +756 -0
  416. package/src/plugins/rate-limit/sveltekit.test.ts +58 -0
  417. package/src/plugins/rate-limit/sveltekit.ts +66 -0
  418. package/src/plugins/social-login/index.ts +715 -0
  419. package/src/plugins/social-login/providers/apple.ts +34 -0
  420. package/src/plugins/social-login/providers/discord.ts +26 -0
  421. package/src/plugins/social-login/providers/github.ts +54 -0
  422. package/src/plugins/social-login/providers/google.ts +23 -0
  423. package/src/plugins/social-login/providers/index.ts +22 -0
  424. package/src/plugins/social-login/providers/microsoft.ts +35 -0
  425. package/src/plugins/social-login/providers/oidc.ts +37 -0
  426. package/src/plugins/social-login/providers/providers.test.ts +211 -0
  427. package/src/plugins/social-login/social-login.test.ts +675 -0
  428. package/src/plugins/social-login/types.ts +80 -0
  429. package/src/plugins/tenancy/index.ts +544 -0
  430. package/src/plugins/tenancy/tenancy.test.ts +323 -0
  431. package/src/plugins/two-factor/index.ts +569 -0
  432. package/src/plugins/two-factor/two-factor.test.ts +235 -0
  433. package/src/plugins/webauthn/index.ts +554 -0
  434. package/src/plugins/webauthn/webauthn.test.ts +472 -0
  435. package/src/plugins/webhook/builtin-events.ts +29 -0
  436. package/src/plugins/webhook/delivery.test.ts +51 -0
  437. package/src/plugins/webhook/delivery.ts +154 -0
  438. package/src/plugins/webhook/hooks.ts +89 -0
  439. package/src/plugins/webhook/index.ts +445 -0
  440. package/src/plugins/webhook/queue/database.ts +67 -0
  441. package/src/plugins/webhook/queue/in-memory.test.ts +48 -0
  442. package/src/plugins/webhook/queue/in-memory.ts +71 -0
  443. package/src/plugins/webhook/queue/types.ts +51 -0
  444. package/src/plugins/webhook/registry.test.ts +48 -0
  445. package/src/plugins/webhook/registry.ts +64 -0
  446. package/src/plugins/webhook/signing.ts +45 -0
  447. package/src/plugins/webhook/ssrf.ts +198 -0
  448. package/src/plugins/webhook/types.ts +138 -0
  449. package/src/plugins/webhook/webhook.test.ts +324 -0
  450. package/src/sveltekit/actions.ts +233 -0
  451. package/src/sveltekit/catch-all.ts +43 -0
  452. package/src/sveltekit/cookies.ts +136 -0
  453. package/src/sveltekit/handle.ts +356 -0
  454. package/src/sveltekit/helpers.ts +69 -0
  455. package/src/sveltekit/index.ts +74 -0
  456. package/src/sveltekit/protect.ts +80 -0
  457. package/src/sveltekit/sveltekit-types.test.ts +31 -0
  458. package/src/sveltekit/sveltekit.test.ts +799 -0
  459. package/src/sveltekit/types.ts +134 -0
  460. package/src/sveltekit/validated.test.ts +117 -0
  461. package/src/sveltekit/validated.ts +78 -0
  462. package/src/testing/adapter-conformance.test.ts +408 -0
  463. package/src/testing/checks.test.ts +124 -0
  464. package/src/testing/checks.ts +304 -0
  465. package/src/testing/index.ts +107 -0
@@ -0,0 +1,435 @@
1
+ import type { DatabaseAdapter } from '../adapters/database';
2
+ import type {
3
+ FortressUser,
4
+ LoginIdentifier,
5
+ PendingReason,
6
+ Permission,
7
+ PermissionInput,
8
+ ServiceAccount,
9
+ Subject,
10
+ } from './types';
11
+ import { normalizeEmail } from './auth/email';
12
+
13
+ // --- Stored types for typed queries ---
14
+
15
+ export interface StoredRefreshToken {
16
+ id: string;
17
+ userId: string;
18
+ tokenFamily: string;
19
+ familyCreatedAt: Date;
20
+ successorTokenHash: string | null;
21
+ rotatedAt: Date | null;
22
+ isRevoked: boolean;
23
+ expiresAt: Date;
24
+ ipAddress: string | null;
25
+ userAgent: string | null;
26
+ deviceName: string | null;
27
+ lastActiveAt: Date | null;
28
+ fingerprintHash: string | null;
29
+ createdAt: Date;
30
+ }
31
+
32
+ /** Hashed, short-lived state for completing a pending authentication flow. */
33
+ export interface StoredContinuation {
34
+ id: string;
35
+ userId: string;
36
+ tokenHash: string;
37
+ reason: PendingReason;
38
+ expiresAt: Date;
39
+ consumedAt: Date | null;
40
+ failedAttempts: number;
41
+ lastFailedAt: Date | null;
42
+ invalidatedAt: Date | null;
43
+ maxAttempts: number;
44
+ cooldownSeconds: number;
45
+ createdAt: Date;
46
+ }
47
+
48
+ // --- Interface ---
49
+
50
+ export interface InternalAdapter {
51
+ /** Resolve user via login_identifier lookup, falling back to direct email match */
52
+ findUserByIdentifier: (identifier: string) => Promise<(FortressUser & { passwordHash: string | null }) | null>;
53
+ /** Get group names for a user via group_user → group resolution */
54
+ getUserGroups: (userId: string) => Promise<string[]>;
55
+ /** Find a refresh token by its SHA-256 hash */
56
+ findRefreshTokenByHash: (tokenHash: string) => Promise<StoredRefreshToken | null>;
57
+ /**
58
+ * Resolve every permission that applies to a subject. For `USER` subjects,
59
+ * walks group memberships and unions their bindings. For `SERVICE_ACCOUNT`
60
+ * and other non-user subjects, only direct bindings on that subject are
61
+ * considered. Missing or inactive users and service accounts return an empty
62
+ * list (the existence/isActive gate is a second layer of defense alongside
63
+ * credential resolution).
64
+ */
65
+ getSubjectPermissions: (subject: Subject, tenantId?: string) => Promise<Permission[]>;
66
+ /** Find an existing permission without creating it. */
67
+ findPermission: (input: PermissionInput) => Promise<Permission | null>;
68
+ /** Find an existing permission or create it if missing */
69
+ findOrCreatePermission: (input: PermissionInput) => Promise<Permission>;
70
+ /** Internal mutation variant that reports whether this call inserted the row. */
71
+ findOrCreatePermissionWithStatus: (input: PermissionInput) => Promise<{ permission: Permission; created: boolean }>;
72
+ /** Ensure a resource exists (no-op if already present) */
73
+ ensureResource: (name: string) => Promise<void>;
74
+ }
75
+
76
+ // --- Factory ---
77
+
78
+ function normalizePermission(permission: Permission): Permission {
79
+ return {
80
+ ...permission,
81
+ conditions: typeof permission.conditions === 'string'
82
+ ? JSON.parse(permission.conditions)
83
+ : permission.conditions,
84
+ };
85
+ }
86
+
87
+ function stableJson(value: unknown): unknown {
88
+ if (Array.isArray(value))
89
+ return value.map(stableJson);
90
+ if (value && typeof value === 'object') {
91
+ const out: Record<string, unknown> = {};
92
+ for (const key of Object.keys(value as Record<string, unknown>).sort())
93
+ out[key] = stableJson((value as Record<string, unknown>)[key]);
94
+ return out;
95
+ }
96
+ return value;
97
+ }
98
+
99
+ function serializePermissionConditions(
100
+ db: DatabaseAdapter,
101
+ conditions: PermissionInput['conditions'],
102
+ ): string | PermissionInput['conditions'] | null {
103
+ if (!conditions)
104
+ return null;
105
+ // M8: normalize JSON key order before storage / lookup. SQLite compares
106
+ // text byte-for-byte; PostgreSQL JSONB normalizes object key order on the
107
+ // server, but returning a stable object here keeps adapter behavior
108
+ // consistent and makes tests deterministic.
109
+ const normalized = stableJson(conditions) as PermissionInput['conditions'];
110
+ // PostgreSQL schema stores JSONB; SQLite stores text.
111
+ return db.dialect === 'pg' ? normalized : JSON.stringify(normalized);
112
+ }
113
+
114
+ async function findOrCreatePermissionWithStatus(
115
+ db: DatabaseAdapter,
116
+ input: PermissionInput,
117
+ ): Promise<{ permission: Permission; created: boolean }> {
118
+ const conditions = serializePermissionConditions(db, input.conditions);
119
+ const where = [
120
+ { field: 'resource', operator: '=' as const, value: input.resource },
121
+ { field: 'action', operator: '=' as const, value: input.action },
122
+ { field: 'effect', operator: '=' as const, value: input.effect ?? 'ALLOW' },
123
+ conditions == null
124
+ ? { field: 'conditions', operator: 'isNull' as const, value: null }
125
+ : { field: 'conditions', operator: '=' as const, value: conditions },
126
+ ];
127
+
128
+ const existing = await db.findOne<Permission>({ model: 'permission', where });
129
+ if (existing)
130
+ return { permission: normalizePermission(existing), created: false };
131
+
132
+ try {
133
+ const created = await db.create<Permission>({
134
+ model: 'permission',
135
+ data: {
136
+ resource: input.resource,
137
+ action: input.action,
138
+ effect: input.effect ?? 'ALLOW',
139
+ conditions,
140
+ description: `${input.action} ${input.resource}`,
141
+ },
142
+ });
143
+ return { permission: normalizePermission(created), created: true };
144
+ }
145
+ catch (err) {
146
+ // A concurrent caller may win the unique-index race. Re-read its row;
147
+ // only the winner reports a creation event.
148
+ const winner = await db.findOne<Permission>({ model: 'permission', where });
149
+ if (winner)
150
+ return { permission: normalizePermission(winner), created: false };
151
+ throw err;
152
+ }
153
+ }
154
+
155
+ export function createInternalAdapter(db: DatabaseAdapter): InternalAdapter {
156
+ return {
157
+ async findUserByIdentifier(identifier: string): Promise<(FortressUser & { passwordHash: string | null }) | null> {
158
+ // Preserve case-sensitive username/phone semantics by trying the exact
159
+ // identifier first. Email gets a second canonical lookup so mixed-case
160
+ // and canonically-equivalent Unicode input resolves the stored identity.
161
+ let loginId = await db.findOne<LoginIdentifier>({
162
+ model: 'login_identifier',
163
+ where: [{ field: 'value', operator: '=', value: identifier }],
164
+ });
165
+
166
+ const canonicalEmail = normalizeEmail(identifier);
167
+ if (!loginId && canonicalEmail !== identifier) {
168
+ loginId = await db.findOne<LoginIdentifier>({
169
+ model: 'login_identifier',
170
+ where: [
171
+ { field: 'type', operator: '=', value: 'email' },
172
+ { field: 'value', operator: '=', value: canonicalEmail },
173
+ ],
174
+ });
175
+ }
176
+
177
+ if (loginId) {
178
+ return db.findOne<FortressUser & { passwordHash: string | null }>({
179
+ model: 'user',
180
+ where: [{ field: 'id', operator: '=', value: loginId.userId }],
181
+ });
182
+ }
183
+
184
+ // Fallback for social-only users without a login_identifier row.
185
+ return db.findOne<FortressUser & { passwordHash: string | null }>({
186
+ model: 'user',
187
+ where: [{ field: 'email', operator: '=', value: canonicalEmail }],
188
+ });
189
+ },
190
+
191
+ async getUserGroups(userId: string): Promise<string[]> {
192
+ const memberships = await db.findMany<{ groupId: string }>({
193
+ model: 'group_user',
194
+ where: [{ field: 'userId', operator: '=', value: userId }],
195
+ });
196
+
197
+ if (memberships.length === 0)
198
+ return [];
199
+
200
+ const groupIds = memberships.map(m => m.groupId);
201
+ const groups = await db.findMany<{ name: string }>({
202
+ model: 'group',
203
+ where: [{ field: 'id', operator: 'in', value: groupIds }],
204
+ });
205
+
206
+ return groups.map(g => g.name);
207
+ },
208
+
209
+ async findRefreshTokenByHash(tokenHash: string): Promise<StoredRefreshToken | null> {
210
+ return db.findOne<StoredRefreshToken>({
211
+ model: 'refresh_token',
212
+ where: [{ field: 'tokenHash', operator: '=', value: tokenHash }],
213
+ });
214
+ },
215
+
216
+ async getSubjectPermissions(subject: Subject, tenantId?: string): Promise<Permission[]> {
217
+ const tenantFilter = tenantId != null;
218
+ const tenantlessFilter = tenantId == null;
219
+ const isUser = subject.type === 'USER';
220
+
221
+ // Missing or inactive principals never resolve permissions. This is a
222
+ // second line of defense behind credential resolution and also protects
223
+ // callers that invoke IAM APIs directly.
224
+ if (subject.type === 'USER') {
225
+ const user = await db.findOne<FortressUser>({
226
+ model: 'user',
227
+ where: [{ field: 'id', operator: '=', value: subject.id }],
228
+ });
229
+ if (!user || !user.isActive)
230
+ return [];
231
+ }
232
+ else if (subject.type === 'SERVICE_ACCOUNT') {
233
+ const sa = await db.findOne<ServiceAccount>({
234
+ model: 'service_account',
235
+ where: [{ field: 'id', operator: '=', value: subject.id }],
236
+ });
237
+ if (!sa || !sa.isActive)
238
+ return [];
239
+ }
240
+
241
+ // Optimized path: single JOIN query when rawQuery is available
242
+ if (db.rawQuery) {
243
+ const rbTenant = tenantFilter
244
+ ? ' AND (rb.tenant_id = ? OR rb.tenant_id IS NULL)'
245
+ : ' AND rb.tenant_id IS NULL';
246
+ const dpbTenant = tenantFilter
247
+ ? ' AND (dpb.tenant_id = ? OR dpb.tenant_id IS NULL)'
248
+ : ' AND dpb.tenant_id IS NULL';
249
+
250
+ // Role-binding predicate: always match the bare subject.
251
+ // For USER subjects, also union group memberships.
252
+ const rbPredicate = isUser
253
+ ? `((rb.subject_type = 'USER' AND rb.subject_id = ?)
254
+ OR (rb.subject_type = 'GROUP' AND rb.subject_id IN (
255
+ SELECT gu.group_id FROM fortress_group_user gu WHERE gu.user_id = ?
256
+ )))`
257
+ : `(rb.subject_type = ? AND rb.subject_id = ?)`;
258
+
259
+ const dpbPredicate = isUser
260
+ ? `((dpb.subject_type = 'USER' AND dpb.subject_id = ?)
261
+ OR (dpb.subject_type = 'GROUP' AND dpb.subject_id IN (
262
+ SELECT gu.group_id FROM fortress_group_user gu WHERE gu.user_id = ?
263
+ )))`
264
+ : `(dpb.subject_type = ? AND dpb.subject_id = ?)`;
265
+
266
+ const rbParams = isUser
267
+ ? [subject.id, subject.id]
268
+ : [subject.type, subject.id];
269
+ const dpbParams = isUser
270
+ ? [subject.id, subject.id]
271
+ : [subject.type, subject.id];
272
+
273
+ const params: unknown[] = [];
274
+ params.push(...rbParams);
275
+ if (tenantFilter)
276
+ params.push(tenantId);
277
+ params.push(...dpbParams);
278
+ if (tenantFilter)
279
+ params.push(tenantId);
280
+
281
+ const rows = await db.rawQuery<Permission>(
282
+ `SELECT DISTINCT p.id, p.resource, p.action, p.effect, p.conditions, p.description
283
+ FROM fortress_permission p
284
+ WHERE p.id IN (
285
+ -- Role-based permissions
286
+ SELECT rp.permission_id FROM fortress_role_permission rp
287
+ JOIN fortress_role_binding rb ON rb.role_id = rp.role_id
288
+ WHERE ${rbPredicate}${rbTenant}
289
+ UNION
290
+ -- Direct permission bindings
291
+ SELECT dpb.permission_id FROM fortress_direct_permission_binding dpb
292
+ WHERE ${dpbPredicate}${dpbTenant}
293
+ )
294
+ ORDER BY p.id`,
295
+ params,
296
+ );
297
+ return rows.map(normalizePermission);
298
+ }
299
+
300
+ // Fallback: sequential findMany queries
301
+ // Helper to filter bindings by tenant (global bindings always included)
302
+ function matchesTenant<T extends { tenantId?: string | null }>(bindings: T[]): T[] {
303
+ if (tenantlessFilter)
304
+ return bindings.filter(b => b.tenantId == null);
305
+ return bindings.filter(b => b.tenantId == null || b.tenantId === tenantId);
306
+ }
307
+
308
+ // 1. Group memberships — USER subjects only. Non-user subjects aren't
309
+ // members of groups, so skip this lookup entirely.
310
+ let groupIds: string[] = [];
311
+ if (isUser) {
312
+ const groupMemberships = await db.findMany<{ groupId: string }>({
313
+ model: 'group_user',
314
+ where: [{ field: 'userId', operator: '=', value: subject.id }],
315
+ });
316
+ groupIds = groupMemberships.map(m => m.groupId);
317
+ }
318
+
319
+ // 2. Role-based permission IDs — direct bindings on the subject.
320
+ const directRoleBindings = matchesTenant(await db.findMany<{ roleId: string; tenantId?: string | null }>({
321
+ model: 'role_binding',
322
+ where: [
323
+ { field: 'subjectType', operator: '=', value: subject.type },
324
+ { field: 'subjectId', operator: '=', value: subject.id },
325
+ ],
326
+ }));
327
+
328
+ let groupRoleBindings: { roleId: string; tenantId?: string | null }[] = [];
329
+ if (groupIds.length > 0) {
330
+ groupRoleBindings = matchesTenant(await db.findMany<{ roleId: string; tenantId?: string | null }>({
331
+ model: 'role_binding',
332
+ where: [
333
+ { field: 'subjectType', operator: '=', value: 'GROUP' },
334
+ { field: 'subjectId', operator: 'in', value: groupIds },
335
+ ],
336
+ }));
337
+ }
338
+
339
+ const roleIds = [...new Set([
340
+ ...directRoleBindings.map(b => b.roleId),
341
+ ...groupRoleBindings.map(b => b.roleId),
342
+ ])];
343
+
344
+ let rolePermissionIds: string[] = [];
345
+ if (roleIds.length > 0) {
346
+ const rolePerms = await db.findMany<{ permissionId: string }>({
347
+ model: 'role_permission',
348
+ where: [{ field: 'roleId', operator: 'in', value: roleIds }],
349
+ });
350
+ rolePermissionIds = rolePerms.map(rp => rp.permissionId);
351
+ }
352
+
353
+ // 3. Direct permission binding IDs — on the subject directly.
354
+ const directSubjectBindings = matchesTenant(await db.findMany<{ permissionId: string; tenantId?: string | null }>({
355
+ model: 'direct_permission_binding',
356
+ where: [
357
+ { field: 'subjectType', operator: '=', value: subject.type },
358
+ { field: 'subjectId', operator: '=', value: subject.id },
359
+ ],
360
+ }));
361
+
362
+ let directGroupBindings: { permissionId: string; tenantId?: string | null }[] = [];
363
+ if (groupIds.length > 0) {
364
+ directGroupBindings = matchesTenant(await db.findMany<{ permissionId: string; tenantId?: string | null }>({
365
+ model: 'direct_permission_binding',
366
+ where: [
367
+ { field: 'subjectType', operator: '=', value: 'GROUP' },
368
+ { field: 'subjectId', operator: 'in', value: groupIds },
369
+ ],
370
+ }));
371
+ }
372
+
373
+ // 4. Merge and deduplicate permission IDs
374
+ const allPermissionIds = [...new Set([
375
+ ...rolePermissionIds,
376
+ ...directSubjectBindings.map(b => b.permissionId),
377
+ ...directGroupBindings.map(b => b.permissionId),
378
+ ])];
379
+
380
+ if (allPermissionIds.length === 0)
381
+ return [];
382
+
383
+ // 5. Fetch actual permissions
384
+ const permissions = await db.findMany<Permission>({
385
+ model: 'permission',
386
+ where: [{ field: 'id', operator: 'in', value: allPermissionIds }],
387
+ });
388
+ return permissions.map(normalizePermission);
389
+ },
390
+
391
+ async findPermission(input: PermissionInput): Promise<Permission | null> {
392
+ const conditions = serializePermissionConditions(db, input.conditions);
393
+ const permission = await db.findOne<Permission>({
394
+ model: 'permission',
395
+ where: [
396
+ { field: 'resource', operator: '=', value: input.resource },
397
+ { field: 'action', operator: '=', value: input.action },
398
+ { field: 'effect', operator: '=', value: input.effect ?? 'ALLOW' },
399
+ conditions == null
400
+ ? { field: 'conditions', operator: 'isNull', value: null }
401
+ : { field: 'conditions', operator: '=', value: conditions },
402
+ ],
403
+ });
404
+ return permission ? normalizePermission(permission) : null;
405
+ },
406
+
407
+ async findOrCreatePermission(input: PermissionInput): Promise<Permission> {
408
+ return (await findOrCreatePermissionWithStatus(db, input)).permission;
409
+ },
410
+
411
+ findOrCreatePermissionWithStatus(input: PermissionInput) {
412
+ return findOrCreatePermissionWithStatus(db, input);
413
+ },
414
+
415
+ async ensureResource(name: string): Promise<void> {
416
+ const where = [{ field: 'name', operator: '=' as const, value: name }];
417
+ const existing = await db.findOne<{ name: string }>({ model: 'resource', where });
418
+ if (existing)
419
+ return;
420
+
421
+ try {
422
+ await db.create({ model: 'resource', data: { name } });
423
+ }
424
+ catch (err) {
425
+ // find-then-create is not atomic — a concurrent caller may have
426
+ // inserted the same resource name (unique). That's the desired end
427
+ // state, so swallow the conflict if the row now exists; re-throw
428
+ // otherwise.
429
+ const winner = await db.findOne<{ name: string }>({ model: 'resource', where });
430
+ if (!winner)
431
+ throw err;
432
+ }
433
+ },
434
+ };
435
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * JSON Schema types (draft 2020-12 subset) + FortressSchema.
3
+ *
4
+ * FortressSchema<T> extends JSONSchema with Standard Schema V1 support,
5
+ * giving each schema: JSON Schema fields (OpenAPI), runtime validation,
6
+ * and TypeScript type inference — all from one object.
7
+ */
8
+
9
+ import type { StandardSchemaV1 } from './standard-schema';
10
+
11
+ /** A subset of JSON Schema (draft 2020-12) sufficient for fortress endpoint definitions and OpenAPI generation. */
12
+ export interface JSONSchema {
13
+ type?: 'string' | 'number' | 'integer' | 'boolean' | 'array' | 'object' | 'null';
14
+ properties?: Record<string, JSONSchema>;
15
+ required?: string[];
16
+ items?: JSONSchema;
17
+ enum?: (string | number | boolean | null)[];
18
+ const?: string | number | boolean | null;
19
+ format?: string;
20
+ description?: string;
21
+ default?: unknown;
22
+ nullable?: boolean;
23
+ oneOf?: JSONSchema[];
24
+ anyOf?: JSONSchema[];
25
+ allOf?: JSONSchema[];
26
+ $ref?: string;
27
+ discriminator?: { propertyName: string; mapping?: Record<string, string> };
28
+ additionalProperties?: boolean | JSONSchema;
29
+ minimum?: number;
30
+ maximum?: number;
31
+ minLength?: number;
32
+ maxLength?: number;
33
+ pattern?: string;
34
+ title?: string;
35
+ }
36
+
37
+ /**
38
+ * A JSON Schema object that also implements Standard Schema V1.
39
+ *
40
+ * - JSON Schema fields → OpenAPI 3.1 spec generation
41
+ * - `~standard.validate()` → runtime validation
42
+ * - `StandardSchemaV1.InferOutput<typeof schema>` → TypeScript type inference
43
+ */
44
+ export type FortressSchema<T = unknown> = JSONSchema & StandardSchemaV1<T, T>;
45
+
46
+ /** Extract the TypeScript type from a FortressSchema or StandardSchemaV1. */
47
+ export type Infer<T> = T extends StandardSchemaV1<any, infer O> ? O : unknown;
48
+
49
+ /** Flatten intersection types for clean IDE tooltips. */
50
+ export type Simplify<T> = { [K in keyof T]: T[K] } & {};
@@ -0,0 +1,192 @@
1
+ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
+
3
+ exports[`route manifest > snapshots the core auth manifest subset 1`] = `
4
+ [
5
+ {
6
+ "classification": "authenticated",
7
+ "csrfApplicable": true,
8
+ "handler": "removeLoginIdentifier",
9
+ "method": "DELETE",
10
+ "mounted": true,
11
+ "path": "/auth/identifiers",
12
+ "plugin": "auth",
13
+ "rateLimited": false,
14
+ "security": [
15
+ "bearer",
16
+ ],
17
+ },
18
+ {
19
+ "classification": "authenticated",
20
+ "csrfApplicable": true,
21
+ "handler": "revokeAllOtherSessions",
22
+ "method": "DELETE",
23
+ "mounted": true,
24
+ "path": "/auth/sessions",
25
+ "plugin": "auth",
26
+ "rateLimited": false,
27
+ "security": [
28
+ "bearer",
29
+ ],
30
+ },
31
+ {
32
+ "classification": "authenticated",
33
+ "csrfApplicable": true,
34
+ "handler": "revokeSession",
35
+ "method": "DELETE",
36
+ "mounted": true,
37
+ "path": "/auth/sessions/:id",
38
+ "plugin": "auth",
39
+ "rateLimited": false,
40
+ "security": [
41
+ "bearer",
42
+ ],
43
+ },
44
+ {
45
+ "classification": "authenticated",
46
+ "csrfApplicable": false,
47
+ "handler": "getLoginIdentifiers",
48
+ "method": "GET",
49
+ "mounted": true,
50
+ "path": "/auth/identifiers",
51
+ "plugin": "auth",
52
+ "rateLimited": false,
53
+ "security": [
54
+ "bearer",
55
+ ],
56
+ },
57
+ {
58
+ "classification": "authenticated",
59
+ "csrfApplicable": false,
60
+ "handler": "me",
61
+ "method": "GET",
62
+ "mounted": true,
63
+ "path": "/auth/me",
64
+ "plugin": "auth",
65
+ "rateLimited": false,
66
+ "security": [
67
+ "bearer",
68
+ ],
69
+ },
70
+ {
71
+ "classification": "authenticated",
72
+ "csrfApplicable": false,
73
+ "handler": "listSessions",
74
+ "method": "GET",
75
+ "mounted": true,
76
+ "path": "/auth/sessions",
77
+ "plugin": "auth",
78
+ "rateLimited": false,
79
+ "security": [
80
+ "bearer",
81
+ ],
82
+ },
83
+ {
84
+ "classification": "public",
85
+ "csrfApplicable": true,
86
+ "handler": "verifyTwoFactor",
87
+ "method": "POST",
88
+ "mounted": true,
89
+ "path": "/auth/2fa/verify",
90
+ "plugin": "auth",
91
+ "rateLimited": false,
92
+ "security": [
93
+ "none",
94
+ ],
95
+ },
96
+ {
97
+ "classification": "authenticated",
98
+ "csrfApplicable": true,
99
+ "handler": "addLoginIdentifier",
100
+ "method": "POST",
101
+ "mounted": true,
102
+ "path": "/auth/identifiers",
103
+ "plugin": "auth",
104
+ "rateLimited": false,
105
+ "security": [
106
+ "bearer",
107
+ ],
108
+ },
109
+ {
110
+ "classification": "rbac",
111
+ "csrfApplicable": true,
112
+ "handler": "impersonate",
113
+ "method": "POST",
114
+ "mounted": true,
115
+ "path": "/auth/impersonate",
116
+ "permission": {
117
+ "action": "impersonate",
118
+ "resource": "fortress",
119
+ },
120
+ "plugin": "auth",
121
+ "rateLimited": false,
122
+ "security": [
123
+ "bearer",
124
+ ],
125
+ },
126
+ {
127
+ "classification": "public",
128
+ "csrfApplicable": true,
129
+ "handler": "login",
130
+ "method": "POST",
131
+ "mounted": true,
132
+ "path": "/auth/login",
133
+ "plugin": "auth",
134
+ "rateLimited": false,
135
+ "security": [
136
+ "none",
137
+ ],
138
+ },
139
+ {
140
+ "classification": "public",
141
+ "csrfApplicable": true,
142
+ "handler": "logout",
143
+ "method": "POST",
144
+ "mounted": true,
145
+ "path": "/auth/logout",
146
+ "plugin": "auth",
147
+ "rateLimited": false,
148
+ "security": [
149
+ "none",
150
+ ],
151
+ },
152
+ {
153
+ "classification": "public",
154
+ "csrfApplicable": true,
155
+ "handler": "verifyMagicLink",
156
+ "method": "POST",
157
+ "mounted": true,
158
+ "path": "/auth/magic-link/verify",
159
+ "plugin": "auth",
160
+ "rateLimited": false,
161
+ "security": [
162
+ "none",
163
+ ],
164
+ },
165
+ {
166
+ "classification": "public",
167
+ "csrfApplicable": true,
168
+ "handler": "refresh",
169
+ "method": "POST",
170
+ "mounted": true,
171
+ "path": "/auth/refresh",
172
+ "plugin": "auth",
173
+ "rateLimited": false,
174
+ "security": [
175
+ "none",
176
+ ],
177
+ },
178
+ {
179
+ "classification": "public",
180
+ "csrfApplicable": true,
181
+ "handler": "createUser",
182
+ "method": "POST",
183
+ "mounted": true,
184
+ "path": "/auth/register",
185
+ "plugin": "auth",
186
+ "rateLimited": false,
187
+ "security": [
188
+ "none",
189
+ ],
190
+ },
191
+ ]
192
+ `;