@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,402 @@
1
+ # Admin Plugin
2
+
3
+ ## Overview
4
+
5
+ The `admin` plugin provides a complete set of endpoints for managing Fortress's own resources: users, roles, groups, permissions, role bindings, permission bindings, and resource sync. The first-admin bootstrap endpoint is opt-in and protected by a one-time secret.
6
+
7
+ All admin endpoints are protected by `fortress:*` permissions enforced through the RBAC middleware. Until a user has been bootstrapped with the `fortress-admin` role, no one can access these routes. There is no ambient superadmin bypass.
8
+
9
+ ## Installation
10
+
11
+ Import the `admin` factory and pass it in the `plugins` array when creating a Fortress instance:
12
+
13
+ ```ts
14
+ import { createFortress } from '@bajustone/fortress';
15
+ import { admin } from '@bajustone/fortress/plugins/admin';
16
+
17
+ const fortress = createFortress({
18
+ jwt: { key: 'your-secret-at-least-32-bytes!!' },
19
+ database: adapter,
20
+ plugins: [
21
+ admin({ bootstrap: { enabled: true, secret: process.env.FORTRESS_ADMIN_BOOTSTRAP_SECRET } }),
22
+ ],
23
+ });
24
+ ```
25
+
26
+ ## Configuration
27
+
28
+ All fields on `AdminPluginOptions` are optional:
29
+
30
+ | Option | Type | Default | Description |
31
+ |---|---|---|---|
32
+ | `resource` | `string` | `'fortress'` | Resource name used in permission declarations. Change this if `fortress` conflicts with your application's resource naming. |
33
+ | `bootstrap` | `{ enabled: boolean; secret?: string }` | disabled | Mount the one-time first-admin bootstrap route. `secret` defaults to `FORTRESS_ADMIN_BOOTSTRAP_SECRET`. |
34
+ | `apiKeyRoutes` | `boolean` | `false` | Mount the admin-side api-key management endpoints under `/admin/users/:userId/api-keys/*` and `/admin/service-accounts/:id/api-keys/*`. Requires the `api-key` plugin to also be registered. See [API Key Management](#api-key-management) below. |
35
+
36
+ ## Bootstrap
37
+
38
+ Before any admin endpoints can be used, you must bootstrap the first admin user. The bootstrap route is **not mounted by default**; enable it only during setup. It creates the `fortress-admin` role, registers all required permissions (auto-discovered from endpoint definitions across all plugins), and binds them to the specified user.
39
+
40
+ ```ts
41
+ // Via the plugin method or HTTP, while bootstrap.enabled is true.
42
+ await fortress.plugins.admin.bootstrap({
43
+ userId: '1',
44
+ secret: process.env.FORTRESS_ADMIN_BOOTSTRAP_SECRET!,
45
+ });
46
+ ```
47
+
48
+ Or via HTTP:
49
+
50
+ ```
51
+ POST /iam/admin/bootstrap
52
+ Authorization: Bearer <access-token>
53
+ Content-Type: application/json
54
+
55
+ { "userId": "1", "secret": "<one-time-secret>" }
56
+ ```
57
+
58
+ Bootstrap succeeds only while zero `fortress-admin` bindings exist and the supplied secret matches the configured one-time secret. It cannot be used to re-bootstrap.
59
+
60
+ ### What bootstrap does
61
+
62
+ 1. Verifies the target user exists.
63
+ 2. Scans all registered endpoint definitions (core auth, core IAM, and all plugins) to discover every declared `permission`.
64
+ 3. Creates the corresponding `resource` and `permission` records if they do not already exist.
65
+ 4. Creates the `fortress-admin` role (system role, cannot be updated or deleted via normal role endpoints).
66
+ 5. Links every discovered permission to the `fortress-admin` role.
67
+ 6. Binds the role to the specified user.
68
+
69
+ ## Usage
70
+
71
+ ### User management
72
+
73
+ ```ts
74
+ // List users with pagination and search
75
+ const { users, total } = await fortress.plugins.admin.listUsers({
76
+ limit: 20,
77
+ offset: 0,
78
+ search: 'alice',
79
+ sortBy: 'id',
80
+ sortDirection: 'desc',
81
+ });
82
+
83
+ // Get a single user
84
+ const user = await fortress.plugins.admin.getUserById({ id: '42' });
85
+
86
+ // Update a user
87
+ await fortress.plugins.admin.updateUser({ id: '42', name: 'New Name', isActive: false });
88
+
89
+ // Delete a user
90
+ await fortress.plugins.admin.deleteUser({ id: '42' });
91
+
92
+ // Create a user (admin-initiated, requires fortress:manageUsers)
93
+ const newUser = await fortress.plugins.admin.createUser({
94
+ email: 'alice@example.com',
95
+ name: 'Alice',
96
+ password: 'SecurePass123!',
97
+ });
98
+ ```
99
+
100
+ ### Role management
101
+
102
+ ```ts
103
+ // List all roles
104
+ const roles = await fortress.plugins.admin.getRoles();
105
+
106
+ // Get role with its permissions
107
+ const role = await fortress.plugins.admin.getRole({ id: '1' });
108
+ // role.permissions => Permission[]
109
+
110
+ // Create a role
111
+ const newRole = await fortress.plugins.admin.createRole({
112
+ name: 'editor',
113
+ permissions: [{ resource: 'post', action: 'publish' }],
114
+ description: 'Content editors',
115
+ });
116
+
117
+ // Update role name/description
118
+ await fortress.plugins.admin.updateRole({ id: '1', name: 'editor', description: 'Content editors' });
119
+
120
+ // Delete a role
121
+ await fortress.plugins.admin.deleteRole({ id: '1' });
122
+
123
+ // Add a permission to a role
124
+ await fortress.plugins.admin.addPermissionToRole({ id: '1', resource: 'post', action: 'publish' });
125
+
126
+ // Bind a role to a user
127
+ await fortress.plugins.admin.bindRoleToUser({ id: '1', userId: '42' });
128
+
129
+ // Bind a role to a group
130
+ await fortress.plugins.admin.bindRoleToGroup({ id: '1', groupId: '5' });
131
+
132
+ // Unbind a role
133
+ await fortress.plugins.admin.unbindRole({ id: '1', subjectType: 'USER', subjectId: '42' });
134
+ ```
135
+
136
+ System roles (like `fortress-admin`) cannot be updated or deleted through the admin endpoints.
137
+
138
+ ### Group management
139
+
140
+ ```ts
141
+ // List groups
142
+ const { groups, total } = await fortress.plugins.admin.listGroups({ limit: 20 });
143
+
144
+ // Create a group
145
+ const group = await fortress.plugins.admin.createGroup({ name: 'devs', description: 'Developer team' });
146
+
147
+ // Get group with members
148
+ const detail = await fortress.plugins.admin.getGroup({ id: '5' });
149
+ // detail.users => FortressUser[]
150
+
151
+ // Update group
152
+ await fortress.plugins.admin.updateGroup({ id: '5', name: 'devs', description: 'Developer team' });
153
+
154
+ // Delete group
155
+ await fortress.plugins.admin.deleteGroup({ id: '5' });
156
+
157
+ // List group members separately
158
+ const members = await fortress.plugins.admin.getGroupUsers({ id: '5' });
159
+
160
+ // Add/remove users from groups
161
+ await fortress.plugins.admin.addUserToGroup({ id: '5', userId: '42' });
162
+ await fortress.plugins.admin.removeUserFromGroup({ id: '5', userId: '42' });
163
+ ```
164
+
165
+ ### Permission management
166
+
167
+ ```ts
168
+ // List all permissions, optionally filtered by resource
169
+ const perms = await fortress.plugins.admin.listPermissions({ resource: 'post' });
170
+
171
+ // Create a new permission
172
+ const perm = await fortress.plugins.admin.createPermission({
173
+ resource: 'invoice',
174
+ action: 'create',
175
+ effect: 'ALLOW',
176
+ description: 'Allow creating invoices',
177
+ });
178
+
179
+ // Delete a permission
180
+ await fortress.plugins.admin.deletePermission({ id: '99' });
181
+
182
+ // Get user permissions
183
+ const userPerms = await fortress.plugins.admin.getUserPermissions({ id: '42' });
184
+
185
+ // Check a specific permission
186
+ const { allowed } = await fortress.plugins.admin.checkPermission({
187
+ userId: '42', resource: 'post', action: 'publish',
188
+ });
189
+
190
+ // Bind/unbind permissions directly to users or groups
191
+ await fortress.plugins.admin.bindPermissionToUser({ userId: '42', permission: { resource: 'post', action: 'publish' } });
192
+ await fortress.plugins.admin.bindPermissionToGroup({ groupId: '5', permission: { resource: 'post', action: 'publish' } });
193
+ await fortress.plugins.admin.unbindPermissionFromUser({ userId: '42', permissionId: '99' });
194
+ await fortress.plugins.admin.unbindPermissionFromGroup({ groupId: '5', permissionId: '99' });
195
+ ```
196
+
197
+ ### Resource sync
198
+
199
+ ```ts
200
+ // Pull resources from database into file
201
+ await fortress.plugins.admin.syncResources({ direction: 'pull' });
202
+
203
+ // Push resources from file into database
204
+ await fortress.plugins.admin.syncResources({ direction: 'push', filePath: './custom-resources.json' });
205
+ ```
206
+
207
+ ### Service account management
208
+
209
+ Service accounts are non-human IAM principals used for CI/CD, M2M, devices, etc. The admin plugin proxies the core `/iam/service-accounts/*` endpoints (CRUD + role bindings + direct permission bindings) so they run through the admin plugin's permission gating when the admin plugin is registered alongside core fortress.
210
+
211
+ ```ts
212
+ // Create a service account
213
+ const sa = await fortress.plugins.admin.createServiceAccount({
214
+ name: 'ci-deploy',
215
+ displayName: 'CI Deploy',
216
+ description: 'Runs production deploys from GitHub Actions',
217
+ });
218
+
219
+ // List / get / update / delete
220
+ const { serviceAccounts, total } = await fortress.plugins.admin.listServiceAccounts({ limit: 20 });
221
+ const found = await fortress.plugins.admin.getServiceAccount({ id: sa.id });
222
+ await fortress.plugins.admin.updateServiceAccount({ id: sa.id, isActive: false });
223
+ await fortress.plugins.admin.deleteServiceAccount({ id: sa.id });
224
+
225
+ // Bind / unbind roles and direct permissions (same shape as the user variants)
226
+ await fortress.plugins.admin.bindRoleToServiceAccount({ serviceAccountId: sa.id, id: roleId });
227
+ await fortress.plugins.admin.bindPermissionToServiceAccount({
228
+ serviceAccountId: sa.id,
229
+ permission: { resource: 'deploy', action: 'run' },
230
+ });
231
+ ```
232
+
233
+ See the [IAM service account docs](../../README.md#use-service-accounts) for the conceptual overview and `checkPermission({ type: 'SERVICE_ACCOUNT', id }, ...)` examples.
234
+
235
+ ### API key management
236
+
237
+ When `apiKeyRoutes: true` is passed to the admin plugin (and the `api-key` plugin is also registered), six admin HTTP routes are mounted under `/admin/users/:userId/api-keys/*` and `/admin/service-accounts/:id/api-keys/*`. All are guarded by the `apiKey:manage` permission, which bootstrap auto-registers into the `fortress-admin` role when the flag is on.
238
+
239
+ ```ts
240
+ import { createFortress } from '@bajustone/fortress';
241
+ import { apiKey } from '@bajustone/fortress/plugins/api-key';
242
+ import { admin } from '@bajustone/fortress/plugins/admin';
243
+
244
+ const fortress = createFortress({
245
+ jwt: { key: '...' },
246
+ database: adapter,
247
+ plugins: [
248
+ apiKey({ prefix: 'myapp' }),
249
+ admin({ apiKeyRoutes: true }),
250
+ ],
251
+ });
252
+ ```
253
+
254
+ **Bootstrapping a service account's first credential.** A fresh service account has no login path — it can't self-mint. The `POST /admin/service-accounts/:id/api-keys` endpoint is the only supported way to issue its initial key:
255
+
256
+ ```bash
257
+ curl -X POST http://localhost:3000/admin/service-accounts/42/api-keys \
258
+ -H "Authorization: Bearer $ADMIN_JWT" \
259
+ -H "Content-Type: application/json" \
260
+ -d '{"name":"ci-deploy-github-actions"}'
261
+ # Returns { "key": "myapp_sk_...", "id": 7 } — store the raw key immediately.
262
+ ```
263
+
264
+ From then on, the service account authenticates incoming requests with `Authorization: ApiKey myapp_sk_...` or `X-API-Key: myapp_sk_...` — the api-key plugin's `resolvePrincipal` hook turns the header into a subject principal and RBAC flows through as usual.
265
+
266
+ Admin-minted keys respect the same configured knobs (`prefix`, `maxKeysPerSubject`, `defaultExpirySeconds`) as self-service keys — the admin plugin re-enters the api-key plugin's `methods(ctx)` factory to mint, so there's no duplicate config.
267
+
268
+ ## Endpoints
269
+
270
+ All endpoints require bearer authentication and the corresponding `fortress:*` permission. The bootstrap endpoint only requires authentication (no permission check).
271
+
272
+ ### Bootstrap
273
+
274
+ | Method | Path | Permission |
275
+ |---|---|---|
276
+ | POST | `/iam/admin/bootstrap` | *(authenticated only)* |
277
+
278
+ ### User endpoints
279
+
280
+ | Method | Path | Permission |
281
+ |---|---|---|
282
+ | GET | `/auth/users` | `fortress:viewUsers` |
283
+ | GET | `/auth/users/:id` | `fortress:viewUsers` |
284
+ | POST | `/auth/users` | `fortress:manageUsers` |
285
+ | PUT | `/auth/users/:id` | `fortress:manageUsers` |
286
+ | DELETE | `/auth/users/:id` | `fortress:manageUsers` |
287
+
288
+ ### Role endpoints
289
+
290
+ | Method | Path | Permission |
291
+ |---|---|---|
292
+ | GET | `/iam/roles` | `fortress:viewRoles` |
293
+ | GET | `/iam/roles/:id` | `fortress:viewRoles` |
294
+ | POST | `/iam/roles` | `fortress:createRole` |
295
+ | PUT | `/iam/roles/:id` | `fortress:manageRoles` |
296
+ | DELETE | `/iam/roles/:id` | `fortress:deleteRole` |
297
+ | POST | `/iam/roles/:id/permissions` | `fortress:manageRoles` |
298
+ | POST | `/iam/roles/:id/bind/user` | `fortress:bindRole` |
299
+ | POST | `/iam/roles/:id/bind/group` | `fortress:bindRole` |
300
+ | DELETE | `/iam/roles/:id/bind` | `fortress:unbindRole` |
301
+
302
+ ### Group endpoints
303
+
304
+ | Method | Path | Permission |
305
+ |---|---|---|
306
+ | GET | `/iam/groups` | `fortress:viewGroups` |
307
+ | GET | `/iam/groups/:id` | `fortress:viewGroups` |
308
+ | POST | `/iam/groups` | `fortress:createGroup` |
309
+ | PUT | `/iam/groups/:id` | `fortress:manageGroup` |
310
+ | DELETE | `/iam/groups/:id` | `fortress:manageGroup` |
311
+ | GET | `/iam/groups/:id/users` | `fortress:viewGroups` |
312
+ | POST | `/iam/groups/:id/users` | `fortress:manageGroup` |
313
+ | DELETE | `/iam/groups/:id/users/:userId` | `fortress:manageGroup` |
314
+
315
+ ### Permission endpoints
316
+
317
+ | Method | Path | Permission |
318
+ |---|---|---|
319
+ | GET | `/iam/permissions` | `fortress:viewPermissions` |
320
+ | POST | `/iam/permissions` | `fortress:managePermissions` |
321
+ | DELETE | `/iam/permissions/:id` | `fortress:managePermissions` |
322
+ | GET | `/iam/users/:id/permissions` | `fortress:viewPermissions` |
323
+ | POST | `/iam/check` | `fortress:viewPermissions` |
324
+ | POST | `/iam/permissions/bind/user` | `fortress:managePermissions` |
325
+ | POST | `/iam/permissions/bind/group` | `fortress:managePermissions` |
326
+ | DELETE | `/iam/permissions/bind/user` | `fortress:managePermissions` |
327
+ | DELETE | `/iam/permissions/bind/group` | `fortress:managePermissions` |
328
+
329
+ ### Service account endpoints (core IAM, proxied through admin)
330
+
331
+ These come from core IAM — the admin plugin re-registers them on its own `routes` array so they dispatch through the admin plugin's permission gating.
332
+
333
+ | Method | Path | Permission |
334
+ |---|---|---|
335
+ | POST | `/iam/service-accounts` | `fortress:createServiceAccount` |
336
+ | GET | `/iam/service-accounts` | `fortress:viewServiceAccounts` |
337
+ | GET | `/iam/service-accounts/:id` | `fortress:viewServiceAccounts` |
338
+ | PATCH | `/iam/service-accounts/:id` | `fortress:manageServiceAccount` |
339
+ | DELETE | `/iam/service-accounts/:id` | `fortress:manageServiceAccount` |
340
+ | GET | `/iam/service-accounts/:id/permissions` | `fortress:viewPermissions` |
341
+ | POST | `/iam/roles/:id/bind/service-account` | `fortress:bindRole` |
342
+ | DELETE | `/iam/roles/:id/bind/service-account` | `fortress:unbindRole` |
343
+ | POST | `/iam/permissions/bind/service-account` | `fortress:managePermissions` |
344
+ | DELETE | `/iam/permissions/bind/service-account` | `fortress:managePermissions` |
345
+
346
+ ### API key admin endpoints (opt-in via `apiKeyRoutes: true`)
347
+
348
+ Mounted only when `admin({ apiKeyRoutes: true })`. All six require the `apiKey:manage` permission, which bootstrap auto-registers. Requires the `api-key` plugin to also be registered.
349
+
350
+ | Method | Path | Purpose |
351
+ |---|---|---|
352
+ | POST | `/admin/users/:userId/api-keys` | Mint a key on behalf of any user. |
353
+ | GET | `/admin/users/:userId/api-keys` | List any user's non-revoked keys. |
354
+ | DELETE | `/admin/users/:userId/api-keys/:id` | Revoke any user's key (ownership is not enforced). |
355
+ | POST | `/admin/service-accounts/:id/api-keys` | Mint a key on behalf of a service account. **This is the only HTTP path for bootstrapping a fresh service account's first credential.** |
356
+ | GET | `/admin/service-accounts/:id/api-keys` | List a service account's non-revoked keys. |
357
+ | DELETE | `/admin/service-accounts/:id/api-keys/:keyId` | Revoke a service account's key. |
358
+
359
+ ### Resource endpoints
360
+
361
+ | Method | Path | Permission |
362
+ |---|---|---|
363
+ | GET | `/iam/resources` | `fortress:viewResources` |
364
+ | POST | `/iam/sync` | `fortress:managePermissions` |
365
+
366
+ ## Bootstrap Hardening
367
+
368
+ The bootstrap endpoint is intentionally not mounted unless `bootstrap.enabled` is true. A request must be authenticated and must include the configured one-time secret. The secret is compared in constant time, and a successful bootstrap permanently closes the path by creating the first `fortress-admin` binding.
369
+
370
+ For emergency access after bootstrap, grant or repair IAM role bindings out-of-band using a trusted migration/seed process with direct database access; the HTTP bootstrap route will not re-open.
371
+
372
+ ## Permissions Reference
373
+
374
+ The admin plugin uses all fortress permissions declared across admin and core IAM endpoints:
375
+
376
+ | Permission | Used by |
377
+ |---|---|
378
+ | `fortress:viewUsers` | List users, get user by ID |
379
+ | `fortress:manageUsers` | Create user, update user, delete user |
380
+ | `fortress:viewRoles` | List roles, get role with permissions |
381
+ | `fortress:createRole` | Create role |
382
+ | `fortress:deleteRole` | Delete role |
383
+ | `fortress:manageRoles` | Update role, add permission to role |
384
+ | `fortress:bindRole` | Bind role to user, group, or service account |
385
+ | `fortress:unbindRole` | Unbind role |
386
+ | `fortress:viewGroups` | List groups, get group, list group members |
387
+ | `fortress:createGroup` | Create group |
388
+ | `fortress:manageGroup` | Update group, delete group, add/remove group members |
389
+ | `fortress:viewPermissions` | List permissions, get user/service-account permissions, check permission |
390
+ | `fortress:managePermissions` | Create/delete permissions, bind/unbind permissions, resource sync |
391
+ | `fortress:viewResources` | List available resources |
392
+ | `fortress:createServiceAccount` | Create a service account |
393
+ | `fortress:viewServiceAccounts` | List and get service accounts |
394
+ | `fortress:manageServiceAccount` | Update / delete a service account |
395
+ | `apiKey:manage` | *(only when `apiKeyRoutes: true`)* Admin mint/list/revoke of any user's or service account's api keys |
396
+
397
+ ## How It Works
398
+
399
+ 1. **Default deny** -- All admin endpoints declare a `permission` in their endpoint metadata. The RBAC middleware enforces these permissions automatically. Without the `fortress-admin` role, every request is denied.
400
+ 2. **Auto-discovery** -- The bootstrap method scans all registered endpoint definitions (core + plugins) to discover every `{ resource, action }` pair. This means adding new plugins with permission-protected endpoints automatically includes those permissions in the bootstrap role.
401
+ 3. **Delegation** -- Admin methods delegate to the core `auth` and `iam` services. The plugin does not duplicate business logic; it provides the permission-gated route layer on top of existing service methods.
402
+ 4. **Bootstrap is one-time** -- The bootstrap route is opt-in, secret-gated, and refuses to run after any `fortress-admin` role binding exists.