@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,50 @@
1
+ import type { EndpointDefinition } from '../core/endpoint';
2
+ import { describe, expect, it } from 'vitest';
3
+ import { obj, str } from '../core/schema-builder';
4
+ import { convertRoutes } from './convert-routes';
5
+ import { fetcherSchemaConverter, identitySchemaConverter, toJSONSchemaConverter } from './converters';
6
+ import { buildRouteDefinition } from './openapi';
7
+
8
+ describe('hono OpenAPI converters (Zod-free defaults)', () => {
9
+ it('identitySchemaConverter passes JSON Schema through unchanged', () => {
10
+ const js = { type: 'object' as const, properties: { a: { type: 'string' as const } } };
11
+ expect(identitySchemaConverter(js)).toBe(js);
12
+ });
13
+
14
+ it('fetcherSchemaConverter compiles JSON Schema into a validating Standard Schema', () => {
15
+ const schema = fetcherSchemaConverter({ type: 'object', properties: { a: { type: 'string' } }, required: ['a'] });
16
+ expect(typeof schema['~standard'].validate).toBe('function');
17
+ expect(schema['~standard'].validate({ a: 'x' })).toEqual({ value: { a: 'x' } });
18
+ const bad = schema['~standard'].validate({}) as { issues?: unknown };
19
+ expect(bad.issues).toBeTruthy(); // missing required `a`
20
+ });
21
+
22
+ it('toJSONSchemaConverter extracts JSON Schema from a fortress/fetcher builder schema', () => {
23
+ const js = toJSONSchemaConverter(obj({ a: str() }, 'a'));
24
+ expect(js.type).toBe('object');
25
+ expect(js.properties?.a?.type).toBe('string');
26
+ });
27
+
28
+ it('mounting works with identitySchemaConverter (no Zod)', () => {
29
+ const ep: EndpointDefinition = {
30
+ method: 'POST',
31
+ path: '/x',
32
+ handler: 'x',
33
+ input: { body: { type: 'object', properties: { a: { type: 'string' } }, required: ['a'] } },
34
+ };
35
+ const def = buildRouteDefinition(ep, identitySchemaConverter);
36
+ expect((def.request as any).body.content['application/json'].schema).toEqual(ep.input!.body);
37
+ });
38
+
39
+ it('convertRoutes imports a fetcher/fortress-authored route via toJSONSchemaConverter', () => {
40
+ const route = {
41
+ method: 'POST',
42
+ path: '/users',
43
+ responses: { 200: { description: 'ok' } },
44
+ request: { body: { content: { 'application/json': { schema: obj({ name: str() }, 'name') } } } },
45
+ };
46
+ const [ep] = convertRoutes([route as any], { schemaConverter: toJSONSchemaConverter });
47
+ expect(ep.input?.body?.type).toBe('object');
48
+ expect(ep.input?.body?.properties?.name?.type).toBe('string');
49
+ });
50
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Default schema converters for the Hono OpenAPI integration — so you can mount
3
+ * fortress endpoints (or import external routes) **without pulling in Zod**.
4
+ *
5
+ * fortress's builder schemas (and `@bajustone/fetcher`'s builder schemas) ARE
6
+ * JSON Schema, so the converters are trivial: identity for the JSON-Schema
7
+ * direction, and `fromJSONSchema` when the target wants a validating Standard
8
+ * Schema V1. Existing Zod callers are unaffected — these are opt-in defaults.
9
+ *
10
+ * @module
11
+ */
12
+
13
+ import type { JSONSchema } from '../core/json-schema';
14
+ import type { StandardSchemaV1 } from '../core/standard-schema';
15
+ import type { ToJSONSchemaConverter } from './convert-routes';
16
+ import type { SchemaConverter } from './openapi';
17
+ import { fromJSONSchema } from '@bajustone/fetcher/openapi';
18
+ import { extractJsonSchema } from '../core/schema-builder';
19
+
20
+ /**
21
+ * A {@link SchemaConverter} that passes JSON Schema through unchanged. Use with
22
+ * OpenAPI tooling that consumes JSON Schema directly, or for raw spec
23
+ * generation. Requires no schema library.
24
+ */
25
+ export const identitySchemaConverter: SchemaConverter<JSONSchema> = jsonSchema => jsonSchema;
26
+
27
+ /**
28
+ * A {@link SchemaConverter} that compiles JSON Schema into a validating
29
+ * `@bajustone/fetcher` Standard Schema V1. Use with Hono OpenAPI tooling that
30
+ * accepts Standard Schema (e.g. `hono-openapi` / `@hono/standard-validator`) —
31
+ * no Zod required.
32
+ */
33
+ export const fetcherSchemaConverter: SchemaConverter<StandardSchemaV1> = jsonSchema =>
34
+ fromJSONSchema(jsonSchema) as unknown as StandardSchemaV1;
35
+
36
+ /**
37
+ * A {@link ToJSONSchemaConverter} for importing external routes authored with
38
+ * fortress's or fetcher's builder (both ARE JSON Schema). Extracts clean JSON
39
+ * Schema (runtime-only `~`-prefixed props are dropped at OpenAPI emission).
40
+ * Pass to {@link convertRoutes} so such routes import without Zod.
41
+ */
42
+ export const toJSONSchemaConverter: ToJSONSchemaConverter = schema =>
43
+ extractJsonSchema(schema as Parameters<typeof extractJsonSchema>[0]);
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Primary entry point for the Hono adapter: register a single fortress
3
+ * middleware that detects Fortress-managed paths and delegates to
4
+ * `fortress.handleRequest`. Custom user routes registered *before*
5
+ * `mountFortress` keep working — Hono's first-match routing means user
6
+ * handlers win over the catch-all.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { Hono } from 'hono';
11
+ * import { mountFortress } from '@bajustone/fortress/hono';
12
+ *
13
+ * const app = new Hono();
14
+ * mountFortress(app, fortress);
15
+ * // POST /auth/login, POST /auth/refresh, GET /auth/me, /iam/*, /oauth/* …
16
+ * // are all handled automatically. Cookies for login/refresh are set on
17
+ * // the response without any extra wiring.
18
+ * ```
19
+ */
20
+
21
+ import type { Env, Hono } from 'hono';
22
+ import type { Fortress } from '../core/fortress';
23
+ import { buildRouteTable, matchRoute } from '../core/http/match';
24
+
25
+ /** Options for {@link mountFortress}. */
26
+ export interface MountFortressOptions {
27
+ /**
28
+ * Optional path prefix to mount Fortress under (e.g. `/api`). When set,
29
+ * a request to `/api/auth/login` is internally rewritten to `/auth/login`
30
+ * before being passed to `fortress.handleRequest`.
31
+ */
32
+ prefix?: string;
33
+ }
34
+
35
+ /**
36
+ * Mount a single Hono middleware that delegates Fortress-managed paths to
37
+ * `fortress.handleRequest` and otherwise calls `next()` so user-route
38
+ * handlers run normally.
39
+ *
40
+ * Cookies emitted by login/refresh/impersonate are honored automatically:
41
+ * the `Response` returned by `fortress.handleRequest` already carries the
42
+ * `Set-Cookie` headers built by core, and Hono returns it verbatim.
43
+ */
44
+ export function mountFortress<E extends Env = Env>(
45
+ app: Hono<E>,
46
+ fortress: Fortress,
47
+ options: MountFortressOptions = {},
48
+ ): void {
49
+ const prefix = options.prefix ?? '';
50
+ // Pre-build the route table once at startup. The middleware uses this to
51
+ // detect whether a request belongs to Fortress before delegating to core.
52
+ // Matching against the table catches every endpoint (auth, iam, plugin
53
+ // routes including OpenAPI's flat `/openapi.json` and `/openapi`) without
54
+ // needing per-prefix heuristics.
55
+ const routeTable = buildRouteTable(fortress.manifest.filter(route => route.mounted));
56
+
57
+ app.use('*', async (c, next) => {
58
+ const url = new URL(c.req.url);
59
+ let pathname = url.pathname;
60
+ if (prefix && pathname.startsWith(prefix)) {
61
+ pathname = pathname.slice(prefix.length) || '/';
62
+ }
63
+ else if (prefix) {
64
+ // Path doesn't live under our prefix — leave it for user routes.
65
+ await next();
66
+ return;
67
+ }
68
+
69
+ if (!matchRoute(routeTable, c.req.method, pathname)) {
70
+ await next();
71
+ return;
72
+ }
73
+
74
+ // Build a fresh Request with the (possibly rewritten) path so core
75
+ // matches the canonical /auth/* / /iam/* paths regardless of mount prefix.
76
+ const rewritten = new Request(`${url.origin}${pathname}${url.search}`, c.req.raw);
77
+ return fortress.handleRequest(rewritten);
78
+ });
79
+ }
@@ -0,0 +1,2 @@
1
+ export { getClaims, getDb, getScopedDb, getSubject, getUserId } from './middleware/auth';
2
+ export type { FortressContext, FortressEnv, FortressVariables } from './middleware/auth';
@@ -0,0 +1,225 @@
1
+ /**
2
+ * Integration tests proving that api-key credentials authenticate user-owned
3
+ * Hono routes — the gap this branch closed. The auth middleware must delegate
4
+ * to `fortress.resolvePrincipal`, populate `fortressSubject` for every
5
+ * principal kind, and the RBAC middleware must check permissions by subject
6
+ * (not by hardcoded USER subject).
7
+ */
8
+
9
+ import type { Fortress } from '../core/fortress';
10
+ import type { Subject } from '../core/types';
11
+ import type { FortressEnv } from './middleware/auth';
12
+ import { Hono } from 'hono';
13
+ import { beforeEach, describe, expect, it } from 'vitest';
14
+ import { createFortress } from '../core/fortress';
15
+ import { apiKey } from '../plugins/api-key';
16
+ import { createTestAdapter } from '../testing';
17
+ import { getSubject, getUserId } from './helpers';
18
+ import { createHonoMiddleware } from './index';
19
+
20
+ const SECRET = 'hono-api-key-user-routes-secret-32char!';
21
+
22
+ interface Ctx {
23
+ fortress: Fortress<any>;
24
+ app: Hono<FortressEnv>;
25
+ userId: string;
26
+ userKey: string;
27
+ saId: string;
28
+ saKey: string;
29
+ saKeyNoPerm: string;
30
+ saNoPermId: string;
31
+ userAccessToken: string;
32
+ }
33
+
34
+ async function setup(): Promise<Ctx> {
35
+ const fortress: Fortress<any> = createFortress({
36
+ jwt: { key: SECRET },
37
+ database: createTestAdapter(),
38
+ plugins: [apiKey({ prefix: 'test' })],
39
+ });
40
+
41
+ // Seed: a user, a service account with 'deploy:run' permission, and a
42
+ // second service account with *no* permissions (for the deny case).
43
+ const user = await fortress.auth.createUser({
44
+ email: 'human@example.com',
45
+ name: 'Human',
46
+ password: 'password-123456',
47
+ });
48
+ const userLogin = await fortress.auth.login('human@example.com', 'password-123456');
49
+ if (userLogin.status !== 'success')
50
+ throw new Error('login should succeed');
51
+
52
+ // Give the user the same 'deploy:run' permission so shared user routes work.
53
+ const deployRole = await fortress.iam.createRole('deployer', [
54
+ { resource: 'deploy', action: 'run' },
55
+ ]);
56
+ await fortress.iam.bindRoleToUser(user.id, deployRole.id);
57
+
58
+ const sa = await fortress.iam.createServiceAccount({ name: 'ci-deploy-bot' });
59
+ await fortress.iam.bindRoleToServiceAccount(sa.id, deployRole.id);
60
+
61
+ const saNoPerm = await fortress.iam.createServiceAccount({ name: 'observer-bot' });
62
+
63
+ const { key: userKey } = await fortress.plugins['api-key'].createKey({
64
+ subject: { type: 'USER', id: user.id },
65
+ name: 'user-key',
66
+ });
67
+ const { key: saKey } = await fortress.plugins['api-key'].createKey({
68
+ subject: { type: 'SERVICE_ACCOUNT', id: sa.id },
69
+ name: 'sa-key',
70
+ });
71
+ const { key: saKeyNoPerm } = await fortress.plugins['api-key'].createKey({
72
+ subject: { type: 'SERVICE_ACCOUNT', id: saNoPerm.id },
73
+ name: 'observer-key',
74
+ });
75
+
76
+ const { authMiddleware, rbacMiddleware, errorHandler } = createHonoMiddleware(fortress, {
77
+ routeMap: {
78
+ 'POST /deploy/run': { resource: 'deploy', action: 'run' },
79
+ },
80
+ });
81
+
82
+ const app = new Hono<FortressEnv>();
83
+ app.onError(errorHandler);
84
+ app.use('/deploy/*', authMiddleware, rbacMiddleware);
85
+ app.use('/me/*', authMiddleware); // no rbac
86
+
87
+ app.post('/deploy/run', (c) => {
88
+ const subject = getSubject(c);
89
+ return c.json({ startedBy: subject });
90
+ });
91
+
92
+ // Endpoint that only USER subjects can reach (tests that getUserId throws
93
+ // for non-USER principals).
94
+ app.get('/me/profile', (c) => {
95
+ const uid = getUserId(c);
96
+ return c.json({ userId: uid });
97
+ });
98
+
99
+ // Endpoint that echoes the full subject so we can assert fortressSubject
100
+ // is populated and fortressUserId is USER-only.
101
+ app.get('/me/whoami', (c) => {
102
+ const subject = c.get('fortressSubject') as Subject;
103
+ const userId = c.get('fortressUserId');
104
+ return c.json({ subject, userId });
105
+ });
106
+
107
+ return {
108
+ fortress,
109
+ app,
110
+ userId: user.id,
111
+ userKey,
112
+ saId: sa.id,
113
+ saKey,
114
+ saKeyNoPerm,
115
+ saNoPermId: saNoPerm.id,
116
+ userAccessToken: userLogin.accessToken,
117
+ };
118
+ }
119
+
120
+ describe('hono user routes: api-key authentication', () => {
121
+ let ctx: Ctx;
122
+ beforeEach(async () => {
123
+ ctx = await setup();
124
+ });
125
+
126
+ it('authorization: ApiKey <key> authenticates a USER on a custom user route', async () => {
127
+ const res = await ctx.app.request('/me/whoami', {
128
+ headers: { Authorization: `ApiKey ${ctx.userKey}` },
129
+ });
130
+ expect(res.status).toBe(200);
131
+ const body = await res.json() as { subject: Subject; userId: string };
132
+ expect(body.subject).toEqual({ type: 'USER', id: ctx.userId });
133
+ expect(body.userId).toBe(ctx.userId);
134
+ });
135
+
136
+ it('x-API-Key authenticates a SERVICE_ACCOUNT on a custom user route', async () => {
137
+ const res = await ctx.app.request('/me/whoami', {
138
+ headers: { 'X-API-Key': ctx.saKey },
139
+ });
140
+ expect(res.status).toBe(200);
141
+ const body = await res.json() as { subject: Subject; userId: string | null };
142
+ expect(body.subject).toEqual({ type: 'SERVICE_ACCOUNT', id: ctx.saId });
143
+ // fortressUserId is a USER-only alias — undefined for service accounts
144
+ expect(body.userId).toBeFalsy();
145
+ });
146
+
147
+ it('falls back to JWT bearer when no api-key header is present', async () => {
148
+ const res = await ctx.app.request('/me/whoami', {
149
+ headers: { Authorization: `Bearer ${ctx.userAccessToken}` },
150
+ });
151
+ expect(res.status).toBe(200);
152
+ const body = await res.json() as { subject: Subject; userId: string };
153
+ expect(body.subject).toEqual({ type: 'USER', id: ctx.userId });
154
+ expect(body.userId).toBe(ctx.userId);
155
+ });
156
+
157
+ it('returns 401 when no credential is present', async () => {
158
+ const res = await ctx.app.request('/me/whoami');
159
+ expect(res.status).toBe(401);
160
+ });
161
+
162
+ it('returns 401 for an unknown api-key', async () => {
163
+ const res = await ctx.app.request('/me/whoami', {
164
+ headers: { 'X-API-Key': 'test_sk_not-real' },
165
+ });
166
+ expect(res.status).toBe(401);
167
+ });
168
+
169
+ it('getUserId throws 401 for a SERVICE_ACCOUNT principal (USER-only helper)', async () => {
170
+ const res = await ctx.app.request('/me/profile', {
171
+ headers: { 'X-API-Key': ctx.saKey },
172
+ });
173
+ expect(res.status).toBe(401);
174
+ });
175
+
176
+ it('getUserId returns the id for a USER principal', async () => {
177
+ const res = await ctx.app.request('/me/profile', {
178
+ headers: { Authorization: `ApiKey ${ctx.userKey}` },
179
+ });
180
+ expect(res.status).toBe(200);
181
+ const body = await res.json() as { userId: string };
182
+ expect(body.userId).toBe(ctx.userId);
183
+ });
184
+ });
185
+
186
+ describe('hono user routes: api-key + RBAC middleware', () => {
187
+ let ctx: Ctx;
188
+ beforeEach(async () => {
189
+ ctx = await setup();
190
+ });
191
+
192
+ it('allows a SERVICE_ACCOUNT api-key when the bound role grants the permission', async () => {
193
+ const res = await ctx.app.request('/deploy/run', {
194
+ method: 'POST',
195
+ headers: { Authorization: `ApiKey ${ctx.saKey}` },
196
+ });
197
+ expect(res.status).toBe(200);
198
+ const body = await res.json() as { startedBy: Subject };
199
+ expect(body.startedBy).toEqual({ type: 'SERVICE_ACCOUNT', id: ctx.saId });
200
+ });
201
+
202
+ it('denies a SERVICE_ACCOUNT api-key that lacks the permission', async () => {
203
+ const res = await ctx.app.request('/deploy/run', {
204
+ method: 'POST',
205
+ headers: { 'X-API-Key': ctx.saKeyNoPerm },
206
+ });
207
+ expect(res.status).toBe(403);
208
+ });
209
+
210
+ it('allows a USER api-key when the user has the permission', async () => {
211
+ const res = await ctx.app.request('/deploy/run', {
212
+ method: 'POST',
213
+ headers: { Authorization: `ApiKey ${ctx.userKey}` },
214
+ });
215
+ expect(res.status).toBe(200);
216
+ });
217
+
218
+ it('allows the same USER over JWT bearer (control: JWT still works)', async () => {
219
+ const res = await ctx.app.request('/deploy/run', {
220
+ method: 'POST',
221
+ headers: { Authorization: `Bearer ${ctx.userAccessToken}` },
222
+ });
223
+ expect(res.status).toBe(200);
224
+ });
225
+ });