@awesome-lang-auth/node 1.10.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 (262) hide show
  1. package/LICENSE +21 -0
  2. package/README.detailed.md +4059 -0
  3. package/README.md +248 -0
  4. package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
  5. package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
  6. package/dist/abstract/base-auth-strategy.abstract.js +7 -0
  7. package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
  8. package/dist/abstract/base-oauth-strategy.abstract.d.ts +27 -0
  9. package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
  10. package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
  11. package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
  12. package/dist/adapters/express.d.ts +45 -0
  13. package/dist/adapters/express.d.ts.map +1 -0
  14. package/dist/adapters/express.js +49 -0
  15. package/dist/adapters/express.js.map +1 -0
  16. package/dist/adapters/fastify.d.ts +72 -0
  17. package/dist/adapters/fastify.d.ts.map +1 -0
  18. package/dist/adapters/fastify.js +63 -0
  19. package/dist/adapters/fastify.js.map +1 -0
  20. package/dist/auth-configurator.d.ts +65 -0
  21. package/dist/auth-configurator.d.ts.map +1 -0
  22. package/dist/auth-configurator.js +127 -0
  23. package/dist/auth-configurator.js.map +1 -0
  24. package/dist/events/auth-event-bus.d.ts +68 -0
  25. package/dist/events/auth-event-bus.d.ts.map +1 -0
  26. package/dist/events/auth-event-bus.js +60 -0
  27. package/dist/events/auth-event-bus.js.map +1 -0
  28. package/dist/events/auth-event-names.d.ts +34 -0
  29. package/dist/events/auth-event-names.d.ts.map +1 -0
  30. package/dist/events/auth-event-names.js +41 -0
  31. package/dist/events/auth-event-names.js.map +1 -0
  32. package/dist/http-types.d.ts +135 -0
  33. package/dist/http-types.d.ts.map +1 -0
  34. package/dist/http-types.js +18 -0
  35. package/dist/http-types.js.map +1 -0
  36. package/dist/index.d.ts +80 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +88 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/interfaces/api-key-store.interface.d.ts +94 -0
  41. package/dist/interfaces/api-key-store.interface.d.ts.map +1 -0
  42. package/dist/interfaces/api-key-store.interface.js +3 -0
  43. package/dist/interfaces/api-key-store.interface.js.map +1 -0
  44. package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
  45. package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
  46. package/dist/interfaces/auth-strategy.interface.js +3 -0
  47. package/dist/interfaces/auth-strategy.interface.js.map +1 -0
  48. package/dist/interfaces/linked-accounts-store.interface.d.ts +74 -0
  49. package/dist/interfaces/linked-accounts-store.interface.d.ts.map +1 -0
  50. package/dist/interfaces/linked-accounts-store.interface.js +3 -0
  51. package/dist/interfaces/linked-accounts-store.interface.js.map +1 -0
  52. package/dist/interfaces/pending-link-store.interface.d.ts +77 -0
  53. package/dist/interfaces/pending-link-store.interface.d.ts.map +1 -0
  54. package/dist/interfaces/pending-link-store.interface.js +3 -0
  55. package/dist/interfaces/pending-link-store.interface.js.map +1 -0
  56. package/dist/interfaces/roles-permissions-store.interface.d.ts +129 -0
  57. package/dist/interfaces/roles-permissions-store.interface.d.ts.map +1 -0
  58. package/dist/interfaces/roles-permissions-store.interface.js +3 -0
  59. package/dist/interfaces/roles-permissions-store.interface.js.map +1 -0
  60. package/dist/interfaces/session-store.interface.d.ts +92 -0
  61. package/dist/interfaces/session-store.interface.d.ts.map +1 -0
  62. package/dist/interfaces/session-store.interface.js +3 -0
  63. package/dist/interfaces/session-store.interface.js.map +1 -0
  64. package/dist/interfaces/settings-store.interface.d.ts +91 -0
  65. package/dist/interfaces/settings-store.interface.d.ts.map +1 -0
  66. package/dist/interfaces/settings-store.interface.js +3 -0
  67. package/dist/interfaces/settings-store.interface.js.map +1 -0
  68. package/dist/interfaces/sse-distributor.interface.d.ts +25 -0
  69. package/dist/interfaces/sse-distributor.interface.d.ts.map +1 -0
  70. package/dist/interfaces/sse-distributor.interface.js +3 -0
  71. package/dist/interfaces/sse-distributor.interface.js.map +1 -0
  72. package/dist/interfaces/telemetry-store.interface.d.ts +72 -0
  73. package/dist/interfaces/telemetry-store.interface.d.ts.map +1 -0
  74. package/dist/interfaces/telemetry-store.interface.js +3 -0
  75. package/dist/interfaces/telemetry-store.interface.js.map +1 -0
  76. package/dist/interfaces/template-store.interface.d.ts +37 -0
  77. package/dist/interfaces/template-store.interface.d.ts.map +1 -0
  78. package/dist/interfaces/template-store.interface.js +3 -0
  79. package/dist/interfaces/template-store.interface.js.map +1 -0
  80. package/dist/interfaces/tenant-store.interface.d.ts +97 -0
  81. package/dist/interfaces/tenant-store.interface.d.ts.map +1 -0
  82. package/dist/interfaces/tenant-store.interface.js +3 -0
  83. package/dist/interfaces/tenant-store.interface.js.map +1 -0
  84. package/dist/interfaces/token-store.interface.d.ts +10 -0
  85. package/dist/interfaces/token-store.interface.d.ts.map +1 -0
  86. package/dist/interfaces/token-store.interface.js +3 -0
  87. package/dist/interfaces/token-store.interface.js.map +1 -0
  88. package/dist/interfaces/user-metadata-store.interface.d.ts +50 -0
  89. package/dist/interfaces/user-metadata-store.interface.d.ts.map +1 -0
  90. package/dist/interfaces/user-metadata-store.interface.js +3 -0
  91. package/dist/interfaces/user-metadata-store.interface.js.map +1 -0
  92. package/dist/interfaces/user-store.interface.d.ts +113 -0
  93. package/dist/interfaces/user-store.interface.d.ts.map +1 -0
  94. package/dist/interfaces/user-store.interface.js +3 -0
  95. package/dist/interfaces/user-store.interface.js.map +1 -0
  96. package/dist/interfaces/webhook-store.interface.d.ts +139 -0
  97. package/dist/interfaces/webhook-store.interface.d.ts.map +1 -0
  98. package/dist/interfaces/webhook-store.interface.js +3 -0
  99. package/dist/interfaces/webhook-store.interface.js.map +1 -0
  100. package/dist/middleware/api-key.middleware.d.ts +35 -0
  101. package/dist/middleware/api-key.middleware.d.ts.map +1 -0
  102. package/dist/middleware/api-key.middleware.js +45 -0
  103. package/dist/middleware/api-key.middleware.js.map +1 -0
  104. package/dist/middleware/auth.middleware.d.ts +13 -0
  105. package/dist/middleware/auth.middleware.d.ts.map +1 -0
  106. package/dist/middleware/auth.middleware.js +55 -0
  107. package/dist/middleware/auth.middleware.js.map +1 -0
  108. package/dist/middleware/jwks-auth.middleware.d.ts +18 -0
  109. package/dist/middleware/jwks-auth.middleware.d.ts.map +1 -0
  110. package/dist/middleware/jwks-auth.middleware.js +77 -0
  111. package/dist/middleware/jwks-auth.middleware.js.map +1 -0
  112. package/dist/models/api-key.model.d.ts +66 -0
  113. package/dist/models/api-key.model.d.ts.map +1 -0
  114. package/dist/models/api-key.model.js +3 -0
  115. package/dist/models/api-key.model.js.map +1 -0
  116. package/dist/models/auth-config.model.d.ts +387 -0
  117. package/dist/models/auth-config.model.d.ts.map +1 -0
  118. package/dist/models/auth-config.model.js +3 -0
  119. package/dist/models/auth-config.model.js.map +1 -0
  120. package/dist/models/errors.d.ts +7 -0
  121. package/dist/models/errors.d.ts.map +1 -0
  122. package/dist/models/errors.js +14 -0
  123. package/dist/models/errors.js.map +1 -0
  124. package/dist/models/session.model.d.ts +28 -0
  125. package/dist/models/session.model.d.ts.map +1 -0
  126. package/dist/models/session.model.js +3 -0
  127. package/dist/models/session.model.js.map +1 -0
  128. package/dist/models/tenant.model.d.ts +20 -0
  129. package/dist/models/tenant.model.d.ts.map +1 -0
  130. package/dist/models/tenant.model.js +3 -0
  131. package/dist/models/tenant.model.js.map +1 -0
  132. package/dist/models/token.model.d.ts +21 -0
  133. package/dist/models/token.model.d.ts.map +1 -0
  134. package/dist/models/token.model.js +3 -0
  135. package/dist/models/token.model.js.map +1 -0
  136. package/dist/models/user.model.d.ts +92 -0
  137. package/dist/models/user.model.d.ts.map +1 -0
  138. package/dist/models/user.model.js +3 -0
  139. package/dist/models/user.model.js.map +1 -0
  140. package/dist/router/admin.router.d.ts +188 -0
  141. package/dist/router/admin.router.d.ts.map +1 -0
  142. package/dist/router/admin.router.js +1507 -0
  143. package/dist/router/admin.router.js.map +1 -0
  144. package/dist/router/auth.router.d.ts +199 -0
  145. package/dist/router/auth.router.d.ts.map +1 -0
  146. package/dist/router/auth.router.js +1636 -0
  147. package/dist/router/auth.router.js.map +1 -0
  148. package/dist/router/openapi.d.ts +98 -0
  149. package/dist/router/openapi.d.ts.map +1 -0
  150. package/dist/router/openapi.js +1518 -0
  151. package/dist/router/openapi.js.map +1 -0
  152. package/dist/router/router-events.d.ts +38 -0
  153. package/dist/router/router-events.d.ts.map +1 -0
  154. package/dist/router/router-events.js +74 -0
  155. package/dist/router/router-events.js.map +1 -0
  156. package/dist/router/tools.router.d.ts +104 -0
  157. package/dist/router/tools.router.d.ts.map +1 -0
  158. package/dist/router/tools.router.js +227 -0
  159. package/dist/router/tools.router.js.map +1 -0
  160. package/dist/router/ui.router.d.ts +49 -0
  161. package/dist/router/ui.router.d.ts.map +1 -0
  162. package/dist/router/ui.router.js +272 -0
  163. package/dist/router/ui.router.js.map +1 -0
  164. package/dist/services/api-key.service.d.ts +53 -0
  165. package/dist/services/api-key.service.d.ts.map +1 -0
  166. package/dist/services/api-key.service.js +66 -0
  167. package/dist/services/api-key.service.js.map +1 -0
  168. package/dist/services/jwks.service.d.ts +73 -0
  169. package/dist/services/jwks.service.d.ts.map +1 -0
  170. package/dist/services/jwks.service.js +174 -0
  171. package/dist/services/jwks.service.js.map +1 -0
  172. package/dist/services/mailer.service.d.ts +30 -0
  173. package/dist/services/mailer.service.d.ts.map +1 -0
  174. package/dist/services/mailer.service.js +248 -0
  175. package/dist/services/mailer.service.js.map +1 -0
  176. package/dist/services/notification.service.d.ts +110 -0
  177. package/dist/services/notification.service.d.ts.map +1 -0
  178. package/dist/services/notification.service.js +79 -0
  179. package/dist/services/notification.service.js.map +1 -0
  180. package/dist/services/password.service.d.ts +5 -0
  181. package/dist/services/password.service.d.ts.map +1 -0
  182. package/dist/services/password.service.js +17 -0
  183. package/dist/services/password.service.js.map +1 -0
  184. package/dist/services/sms.service.d.ts +14 -0
  185. package/dist/services/sms.service.d.ts.map +1 -0
  186. package/dist/services/sms.service.js +51 -0
  187. package/dist/services/sms.service.js.map +1 -0
  188. package/dist/services/token.service.d.ts +62 -0
  189. package/dist/services/token.service.d.ts.map +1 -0
  190. package/dist/services/token.service.js +354 -0
  191. package/dist/services/token.service.js.map +1 -0
  192. package/dist/stores/memory-template.store.d.ts +12 -0
  193. package/dist/stores/memory-template.store.d.ts.map +1 -0
  194. package/dist/stores/memory-template.store.js +35 -0
  195. package/dist/stores/memory-template.store.js.map +1 -0
  196. package/dist/strategies/api-key/api-key.strategy.d.ts +72 -0
  197. package/dist/strategies/api-key/api-key.strategy.d.ts.map +1 -0
  198. package/dist/strategies/api-key/api-key.strategy.js +182 -0
  199. package/dist/strategies/api-key/api-key.strategy.js.map +1 -0
  200. package/dist/strategies/local/local.strategy.d.ts +19 -0
  201. package/dist/strategies/local/local.strategy.d.ts.map +1 -0
  202. package/dist/strategies/local/local.strategy.js +45 -0
  203. package/dist/strategies/local/local.strategy.js.map +1 -0
  204. package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
  205. package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
  206. package/dist/strategies/magic-link/magic-link.strategy.js +53 -0
  207. package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
  208. package/dist/strategies/oauth/generic-oauth.strategy.d.ts +120 -0
  209. package/dist/strategies/oauth/generic-oauth.strategy.d.ts.map +1 -0
  210. package/dist/strategies/oauth/generic-oauth.strategy.js +88 -0
  211. package/dist/strategies/oauth/generic-oauth.strategy.js.map +1 -0
  212. package/dist/strategies/oauth/github.strategy.d.ts +31 -0
  213. package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
  214. package/dist/strategies/oauth/github.strategy.js +72 -0
  215. package/dist/strategies/oauth/github.strategy.js.map +1 -0
  216. package/dist/strategies/oauth/google.strategy.d.ts +31 -0
  217. package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
  218. package/dist/strategies/oauth/google.strategy.js +61 -0
  219. package/dist/strategies/oauth/google.strategy.js.map +1 -0
  220. package/dist/strategies/sms/sms.strategy.d.ts +7 -0
  221. package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
  222. package/dist/strategies/sms/sms.strategy.js +39 -0
  223. package/dist/strategies/sms/sms.strategy.js.map +1 -0
  224. package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
  225. package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
  226. package/dist/strategies/two-factor/totp.strategy.js +32 -0
  227. package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
  228. package/dist/tools/auth-tools.d.ts +200 -0
  229. package/dist/tools/auth-tools.d.ts.map +1 -0
  230. package/dist/tools/auth-tools.js +232 -0
  231. package/dist/tools/auth-tools.js.map +1 -0
  232. package/dist/tools/sse-manager.d.ts +118 -0
  233. package/dist/tools/sse-manager.d.ts.map +1 -0
  234. package/dist/tools/sse-manager.js +163 -0
  235. package/dist/tools/sse-manager.js.map +1 -0
  236. package/dist/tools/sse-notify.decorator.d.ts +74 -0
  237. package/dist/tools/sse-notify.decorator.d.ts.map +1 -0
  238. package/dist/tools/sse-notify.decorator.js +78 -0
  239. package/dist/tools/sse-notify.decorator.js.map +1 -0
  240. package/dist/tools/webhook-action.d.ts +130 -0
  241. package/dist/tools/webhook-action.d.ts.map +1 -0
  242. package/dist/tools/webhook-action.js +144 -0
  243. package/dist/tools/webhook-action.js.map +1 -0
  244. package/dist/tools/webhook-sender.d.ts +31 -0
  245. package/dist/tools/webhook-sender.d.ts.map +1 -0
  246. package/dist/tools/webhook-sender.js +80 -0
  247. package/dist/tools/webhook-sender.js.map +1 -0
  248. package/dist/ui-assets/2fa.html +115 -0
  249. package/dist/ui-assets/account-conflict.html +108 -0
  250. package/dist/ui-assets/admin.css +263 -0
  251. package/dist/ui-assets/admin.js +1927 -0
  252. package/dist/ui-assets/auth.js +725 -0
  253. package/dist/ui-assets/base.css +219 -0
  254. package/dist/ui-assets/forgot-password.html +93 -0
  255. package/dist/ui-assets/link-verify.html +74 -0
  256. package/dist/ui-assets/login.html +135 -0
  257. package/dist/ui-assets/magic-link.html +112 -0
  258. package/dist/ui-assets/register.html +122 -0
  259. package/dist/ui-assets/reset-password.html +103 -0
  260. package/dist/ui-assets/ui-i18n-keys.json +78 -0
  261. package/dist/ui-assets/verify-email.html +66 -0
  262. package/package.json +89 -0
@@ -0,0 +1,1518 @@
1
+ "use strict";
2
+ /**
3
+ * Lightweight OpenAPI 3.0 spec builders for all awesome-node-auth routers.
4
+ *
5
+ * No external dependencies — specs are assembled in-memory from the
6
+ * feature flags passed to each router.
7
+ *
8
+ * Exported builders:
9
+ * - `buildOpenApiSpec` — tools router (`/tools/*`)
10
+ * - `buildAuthOpenApiSpec` — auth router (`/auth/*`)
11
+ * - `buildAdminOpenApiSpec` — admin router (`/admin/api/*`)
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.buildAuthOpenApiSpec = buildAuthOpenApiSpec;
15
+ exports.buildAdminOpenApiSpec = buildAdminOpenApiSpec;
16
+ exports.buildOpenApiSpec = buildOpenApiSpec;
17
+ exports.buildSwaggerUiHtml = buildSwaggerUiHtml;
18
+ // ─────────────────────────────────────────────────────────────────────────────
19
+ // Shared helpers
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ const BEARER_SCHEME = {
22
+ BearerAuth: {
23
+ type: 'http',
24
+ scheme: 'bearer',
25
+ bearerFormat: 'JWT',
26
+ },
27
+ };
28
+ const bearer = { BearerAuth: [] };
29
+ /**
30
+ * Build an OpenAPI 3.0 document for the auth router.
31
+ *
32
+ * @param options Feature flags controlling optional endpoint visibility.
33
+ * @param basePath Base path where the auth router is mounted (default `'/auth'`).
34
+ */
35
+ function buildAuthOpenApiSpec(options = {}, basePath = '/auth') {
36
+ const { hasRegister = false, hasSessionsCleanup = false, hasGoogleOAuth = false, hasGithubOAuth = false, oauthProviders = [], hasLinkedAccounts = false, } = options;
37
+ const paths = {};
38
+ // ── POST /login ────────────────────────────────────────────────────────────
39
+ paths[`${basePath}/login`] = {
40
+ post: {
41
+ summary: 'Authenticate with email and password',
42
+ operationId: 'login',
43
+ tags: ['Authentication'],
44
+ requestBody: {
45
+ required: true,
46
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/LoginRequest' } } },
47
+ },
48
+ parameters: [
49
+ { name: 'X-Auth-Strategy', in: 'header', required: false, schema: { type: 'string', enum: ['bearer'] }, description: 'Pass `bearer` to receive tokens in the response body instead of cookies' },
50
+ ],
51
+ responses: {
52
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
53
+ 400: { description: '2FA challenge required', content: { 'application/json': { schema: { $ref: '#/components/schemas/TwoFAChallenge' } } } },
54
+ 401: { description: 'Invalid credentials' },
55
+ },
56
+ },
57
+ };
58
+ // ── POST /logout ───────────────────────────────────────────────────────────
59
+ paths[`${basePath}/logout`] = {
60
+ post: {
61
+ summary: 'Log out and revoke the refresh token',
62
+ operationId: 'logout',
63
+ tags: ['Authentication'],
64
+ security: [bearer],
65
+ requestBody: { required: false, content: { 'application/json': { schema: { type: 'object', properties: { refreshToken: { type: 'string', description: 'Bearer clients: the current refresh token, revoked with its session' } } } } } },
66
+ responses: {
67
+ 200: { description: 'Logged out', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
68
+ 401: { description: 'Unauthorized' },
69
+ },
70
+ },
71
+ };
72
+ // ── POST /refresh ──────────────────────────────────────────────────────────
73
+ paths[`${basePath}/refresh`] = {
74
+ post: {
75
+ summary: 'Refresh access token using a valid refresh token',
76
+ operationId: 'refreshToken',
77
+ tags: ['Authentication'],
78
+ parameters: [
79
+ { name: 'X-Auth-Strategy', in: 'header', required: false, schema: { type: 'string', enum: ['bearer'] } },
80
+ ],
81
+ requestBody: {
82
+ required: false,
83
+ description: 'Required when using bearer strategy; omit when using cookies',
84
+ content: { 'application/json': { schema: { type: 'object', properties: { refreshToken: { type: 'string' } }, required: ['refreshToken'] } } },
85
+ },
86
+ responses: {
87
+ 200: { description: 'New token pair issued', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
88
+ 401: { description: 'Invalid or expired refresh token' },
89
+ },
90
+ },
91
+ };
92
+ // ── GET /me ────────────────────────────────────────────────────────────────
93
+ paths[`${basePath}/me`] = {
94
+ get: {
95
+ summary: 'Get the authenticated user profile',
96
+ operationId: 'getMe',
97
+ tags: ['Authentication'],
98
+ security: [bearer],
99
+ responses: {
100
+ 200: { description: 'User profile', content: { 'application/json': { schema: { $ref: '#/components/schemas/UserProfile' } } } },
101
+ 401: { description: 'Unauthorized' },
102
+ },
103
+ },
104
+ };
105
+ // ── POST /register (optional) ──────────────────────────────────────────────
106
+ if (hasRegister) {
107
+ paths[`${basePath}/register`] = {
108
+ post: {
109
+ summary: 'Register a new user account',
110
+ operationId: 'register',
111
+ tags: ['Authentication'],
112
+ requestBody: {
113
+ required: true,
114
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/RegisterRequest' } } },
115
+ },
116
+ responses: {
117
+ 201: { description: 'Account created', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, userId: { type: 'string' } }, required: ['success', 'userId'] } } } },
118
+ 400: { description: 'Validation error' },
119
+ 409: { description: 'An account with this e-mail address already exists (built-in handler: code USER_EXISTS)' },
120
+ },
121
+ },
122
+ };
123
+ }
124
+ // ── POST /forgot-password ──────────────────────────────────────────────────
125
+ paths[`${basePath}/forgot-password`] = {
126
+ post: {
127
+ summary: 'Request a password-reset email',
128
+ operationId: 'forgotPassword',
129
+ tags: ['Password'],
130
+ requestBody: {
131
+ required: true,
132
+ content: { 'application/json': { schema: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email' }, emailLang: { type: 'string' } } } } },
133
+ },
134
+ responses: {
135
+ 200: { description: 'Reset email sent (or silently ignored if email not found)', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
136
+ },
137
+ },
138
+ };
139
+ // ── POST /reset-password ───────────────────────────────────────────────────
140
+ paths[`${basePath}/reset-password`] = {
141
+ post: {
142
+ summary: 'Reset password using a reset token',
143
+ operationId: 'resetPassword',
144
+ tags: ['Password'],
145
+ requestBody: {
146
+ required: true,
147
+ content: { 'application/json': { schema: { type: 'object', required: ['token', 'newPassword'], properties: { token: { type: 'string' }, newPassword: { type: 'string', format: 'password', minLength: 8 } } } } },
148
+ },
149
+ responses: {
150
+ 200: { description: 'Password reset', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
151
+ 400: { description: 'Invalid or expired token' },
152
+ },
153
+ },
154
+ };
155
+ // ── POST /change-password ──────────────────────────────────────────────────
156
+ paths[`${basePath}/change-password`] = {
157
+ post: {
158
+ summary: 'Change password (authenticated)',
159
+ operationId: 'changePassword',
160
+ tags: ['Password'],
161
+ security: [bearer],
162
+ requestBody: {
163
+ required: true,
164
+ content: { 'application/json': { schema: { type: 'object', required: ['currentPassword', 'newPassword'], properties: { currentPassword: { type: 'string', format: 'password' }, newPassword: { type: 'string', format: 'password', minLength: 8 } } } } },
165
+ },
166
+ responses: {
167
+ 200: { description: 'Password changed', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
168
+ 401: { description: 'Unauthorized or wrong current password' },
169
+ },
170
+ },
171
+ };
172
+ // ── 2FA ───────────────────────────────────────────────────────────────────
173
+ paths[`${basePath}/2fa/setup`] = {
174
+ post: {
175
+ summary: 'Begin TOTP 2FA setup — returns QR code and secret',
176
+ operationId: 'setup2fa',
177
+ tags: ['Two-Factor Auth'],
178
+ security: [bearer],
179
+ responses: {
180
+ 200: { description: 'TOTP setup info', content: { 'application/json': { schema: { type: 'object', properties: { secret: { type: 'string' }, qrCode: { type: 'string', description: 'Data-URL PNG of the QR code' } } } } } },
181
+ 401: { description: 'Unauthorized' },
182
+ },
183
+ },
184
+ };
185
+ paths[`${basePath}/2fa/verify-setup`] = {
186
+ post: {
187
+ summary: 'Complete TOTP 2FA setup by verifying the first code',
188
+ operationId: 'verifySetup2fa',
189
+ tags: ['Two-Factor Auth'],
190
+ security: [bearer],
191
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['token', 'secret'], properties: { token: { type: 'string', minLength: 6, maxLength: 6 }, secret: { type: 'string' } } } } } },
192
+ responses: {
193
+ 200: { description: '2FA enabled', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
194
+ 400: { description: 'Invalid TOTP token' },
195
+ 401: { description: 'Unauthorized' },
196
+ },
197
+ },
198
+ };
199
+ paths[`${basePath}/2fa/verify`] = {
200
+ post: {
201
+ summary: 'Verify TOTP code during login',
202
+ operationId: 'verify2fa',
203
+ tags: ['Two-Factor Auth'],
204
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['userId', 'token'], properties: { userId: { type: 'string' }, token: { type: 'string', minLength: 6, maxLength: 6 } } } } } },
205
+ parameters: [{ name: 'X-Auth-Strategy', in: 'header', required: false, schema: { type: 'string', enum: ['bearer'] } }],
206
+ responses: {
207
+ 200: { description: 'Login completed', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
208
+ 401: { description: 'Invalid code' },
209
+ },
210
+ },
211
+ };
212
+ paths[`${basePath}/2fa/disable`] = {
213
+ post: {
214
+ summary: 'Disable TOTP 2FA (authenticated)',
215
+ operationId: 'disable2fa',
216
+ tags: ['Two-Factor Auth'],
217
+ security: [bearer],
218
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['token'], properties: { token: { type: 'string', minLength: 6, maxLength: 6 } } } } } },
219
+ responses: {
220
+ 200: { description: '2FA disabled', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
221
+ 401: { description: 'Unauthorized or invalid TOTP code' },
222
+ 403: { description: '2FA is mandatory and cannot be disabled' },
223
+ },
224
+ },
225
+ };
226
+ // ── Email verification ─────────────────────────────────────────────────────
227
+ paths[`${basePath}/send-verification-email`] = {
228
+ post: {
229
+ summary: 'Send (or resend) a verification email',
230
+ operationId: 'sendVerificationEmail',
231
+ tags: ['Email'],
232
+ security: [bearer],
233
+ requestBody: { required: false, content: { 'application/json': { schema: { type: 'object', properties: { emailLang: { type: 'string' } } } } } },
234
+ responses: {
235
+ 200: { description: 'Email sent', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
236
+ 401: { description: 'Unauthorized' },
237
+ },
238
+ },
239
+ };
240
+ paths[`${basePath}/verify-email`] = {
241
+ get: {
242
+ summary: 'Verify email address using a token from the verification link',
243
+ operationId: 'verifyEmail',
244
+ tags: ['Email'],
245
+ parameters: [{ name: 'token', in: 'query', required: true, schema: { type: 'string' } }],
246
+ responses: {
247
+ 200: { description: 'Email verified', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
248
+ 400: { description: 'Invalid or expired token' },
249
+ },
250
+ },
251
+ };
252
+ paths[`${basePath}/change-email/request`] = {
253
+ post: {
254
+ summary: 'Request an email address change (sends verification to new address)',
255
+ operationId: 'changeEmailRequest',
256
+ tags: ['Email'],
257
+ security: [bearer],
258
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['newEmail'], properties: { newEmail: { type: 'string', format: 'email' }, emailLang: { type: 'string' } } } } } },
259
+ responses: {
260
+ 200: { description: 'Verification email sent to new address', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
261
+ 401: { description: 'Unauthorized' },
262
+ },
263
+ },
264
+ };
265
+ paths[`${basePath}/change-email/confirm`] = {
266
+ post: {
267
+ summary: 'Confirm email change using the token from the verification link',
268
+ operationId: 'changeEmailConfirm',
269
+ tags: ['Email'],
270
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['token'], properties: { token: { type: 'string' } } } } } },
271
+ responses: {
272
+ 200: { description: 'Email changed', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
273
+ 400: { description: 'Invalid or expired token' },
274
+ },
275
+ },
276
+ };
277
+ // ── Magic link ─────────────────────────────────────────────────────────────
278
+ paths[`${basePath}/magic-link/send`] = {
279
+ post: {
280
+ summary: 'Send a magic-link login email',
281
+ operationId: 'magicLinkSend',
282
+ tags: ['Magic Link'],
283
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email' }, emailLang: { type: 'string' } } } } } },
284
+ responses: {
285
+ 200: { description: 'Magic link sent', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
286
+ },
287
+ },
288
+ };
289
+ paths[`${basePath}/magic-link/verify`] = {
290
+ post: {
291
+ summary: 'Verify a magic-link token and complete login',
292
+ operationId: 'magicLinkVerify',
293
+ tags: ['Magic Link'],
294
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['token'], properties: { token: { type: 'string' } } } } } },
295
+ parameters: [{ name: 'X-Auth-Strategy', in: 'header', required: false, schema: { type: 'string', enum: ['bearer'] } }],
296
+ responses: {
297
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
298
+ 401: { description: 'Invalid or expired magic link' },
299
+ },
300
+ },
301
+ };
302
+ // ── SMS OTP ────────────────────────────────────────────────────────────────
303
+ paths[`${basePath}/sms/send`] = {
304
+ post: {
305
+ summary: 'Send an SMS OTP code',
306
+ operationId: 'smsSend',
307
+ tags: ['SMS'],
308
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['phoneNumber'], properties: { phoneNumber: { type: 'string' } } } } } },
309
+ responses: {
310
+ 200: { description: 'SMS sent', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
311
+ },
312
+ },
313
+ };
314
+ paths[`${basePath}/sms/verify`] = {
315
+ post: {
316
+ summary: 'Verify an SMS OTP code and complete login',
317
+ operationId: 'smsVerify',
318
+ tags: ['SMS'],
319
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['phoneNumber', 'code'], properties: { phoneNumber: { type: 'string' }, code: { type: 'string' } } } } } },
320
+ parameters: [{ name: 'X-Auth-Strategy', in: 'header', required: false, schema: { type: 'string', enum: ['bearer'] } }],
321
+ responses: {
322
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
323
+ 401: { description: 'Invalid or expired code' },
324
+ },
325
+ },
326
+ };
327
+ // ── OAuth — Google (optional) ──────────────────────────────────────────────
328
+ if (hasGoogleOAuth) {
329
+ paths[`${basePath}/oauth/google`] = {
330
+ get: {
331
+ summary: 'Redirect to Google OAuth consent screen',
332
+ operationId: 'oauthGoogleRedirect',
333
+ tags: ['OAuth'],
334
+ responses: { 302: { description: 'Redirect to Google' } },
335
+ },
336
+ };
337
+ paths[`${basePath}/oauth/google/callback`] = {
338
+ get: {
339
+ summary: 'Handle Google OAuth callback',
340
+ operationId: 'oauthGoogleCallback',
341
+ tags: ['OAuth'],
342
+ parameters: [{ name: 'code', in: 'query', required: true, schema: { type: 'string' } }],
343
+ responses: {
344
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
345
+ 302: { description: 'Redirect after login (cookie strategy)' },
346
+ 401: { description: 'OAuth failed' },
347
+ },
348
+ },
349
+ };
350
+ }
351
+ // ── OAuth — GitHub (optional) ──────────────────────────────────────────────
352
+ if (hasGithubOAuth) {
353
+ paths[`${basePath}/oauth/github`] = {
354
+ get: {
355
+ summary: 'Redirect to GitHub OAuth consent screen',
356
+ operationId: 'oauthGithubRedirect',
357
+ tags: ['OAuth'],
358
+ responses: { 302: { description: 'Redirect to GitHub' } },
359
+ },
360
+ };
361
+ paths[`${basePath}/oauth/github/callback`] = {
362
+ get: {
363
+ summary: 'Handle GitHub OAuth callback',
364
+ operationId: 'oauthGithubCallback',
365
+ tags: ['OAuth'],
366
+ parameters: [{ name: 'code', in: 'query', required: true, schema: { type: 'string' } }],
367
+ responses: {
368
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
369
+ 302: { description: 'Redirect after login (cookie strategy)' },
370
+ 401: { description: 'OAuth failed' },
371
+ },
372
+ },
373
+ };
374
+ }
375
+ // ── OAuth — Generic providers (optional) ───────────────────────────────────
376
+ for (const providerName of oauthProviders) {
377
+ paths[`${basePath}/oauth/${providerName}`] = {
378
+ get: {
379
+ summary: `Redirect to ${providerName} OAuth consent screen`,
380
+ operationId: `oauth${providerName.charAt(0).toUpperCase() + providerName.slice(1)}Redirect`,
381
+ tags: ['OAuth'],
382
+ responses: { 302: { description: `Redirect to ${providerName}` } },
383
+ },
384
+ };
385
+ paths[`${basePath}/oauth/${providerName}/callback`] = {
386
+ get: {
387
+ summary: `Handle ${providerName} OAuth callback`,
388
+ operationId: `oauth${providerName.charAt(0).toUpperCase() + providerName.slice(1)}Callback`,
389
+ tags: ['OAuth'],
390
+ parameters: [{ name: 'code', in: 'query', required: true, schema: { type: 'string' } }],
391
+ responses: {
392
+ 200: { description: 'Login successful', content: { 'application/json': { schema: { $ref: '#/components/schemas/AuthResponse' } } } },
393
+ 302: { description: 'Redirect after login (cookie strategy)' },
394
+ 401: { description: 'OAuth failed' },
395
+ },
396
+ },
397
+ };
398
+ }
399
+ // ── Sessions cleanup (optional) ─────────────────────────────────────────────
400
+ if (hasSessionsCleanup) {
401
+ paths[`${basePath}/sessions/cleanup`] = {
402
+ post: {
403
+ summary: 'Delete all expired sessions (cron-callable)',
404
+ operationId: 'sessionsCleanup',
405
+ tags: ['Sessions'],
406
+ responses: {
407
+ 200: { description: 'Sessions cleaned', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, deleted: { type: 'integer' } } } } } },
408
+ },
409
+ },
410
+ };
411
+ }
412
+ // ── Linked accounts (optional) ─────────────────────────────────────────────
413
+ if (hasLinkedAccounts) {
414
+ paths[`${basePath}/linked-accounts`] = {
415
+ get: {
416
+ summary: 'List OAuth accounts linked to the authenticated user',
417
+ operationId: 'getLinkedAccounts',
418
+ tags: ['Linked Accounts'],
419
+ security: [bearer],
420
+ responses: {
421
+ 200: { description: 'Linked accounts', content: { 'application/json': { schema: { type: 'object', properties: { linkedAccounts: { type: 'array', items: { $ref: '#/components/schemas/LinkedAccount' } } } } } } },
422
+ 401: { description: 'Unauthorized' },
423
+ },
424
+ },
425
+ };
426
+ paths[`${basePath}/linked-accounts/{provider}/{providerAccountId}`] = {
427
+ delete: {
428
+ summary: 'Unlink an OAuth provider account',
429
+ operationId: 'unlinkAccount',
430
+ tags: ['Linked Accounts'],
431
+ security: [bearer],
432
+ parameters: [
433
+ { name: 'provider', in: 'path', required: true, schema: { type: 'string' } },
434
+ { name: 'providerAccountId', in: 'path', required: true, schema: { type: 'string' } },
435
+ ],
436
+ responses: {
437
+ 200: { description: 'Account unlinked', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
438
+ 401: { description: 'Unauthorized' },
439
+ },
440
+ },
441
+ };
442
+ paths[`${basePath}/link-request`] = {
443
+ post: {
444
+ summary: 'Request to link a new email / provider account',
445
+ operationId: 'linkRequest',
446
+ tags: ['Linked Accounts'],
447
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['email'], properties: { email: { type: 'string', format: 'email' }, provider: { type: 'string' }, emailLang: { type: 'string' } } } } } },
448
+ responses: {
449
+ 200: { description: 'Verification email sent', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
450
+ 400: { description: 'Validation error' },
451
+ },
452
+ },
453
+ };
454
+ paths[`${basePath}/link-verify`] = {
455
+ post: {
456
+ summary: 'Verify a pending account link token',
457
+ operationId: 'linkVerify',
458
+ tags: ['Linked Accounts'],
459
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['token'], properties: { token: { type: 'string' }, loginAfterLinking: { type: 'boolean' } } } } } },
460
+ responses: {
461
+ 200: { description: 'Account linked', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
462
+ 400: { description: 'Invalid or expired token' },
463
+ },
464
+ },
465
+ };
466
+ }
467
+ // ── DELETE /account ────────────────────────────────────────────────────────
468
+ paths[`${basePath}/account`] = {
469
+ delete: {
470
+ summary: 'Delete the authenticated user\'s account permanently',
471
+ operationId: 'deleteAccount',
472
+ tags: ['Authentication'],
473
+ security: [bearer],
474
+ responses: {
475
+ 200: { description: 'Account deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
476
+ 401: { description: 'Unauthorized' },
477
+ },
478
+ },
479
+ };
480
+ // ── Schemas ────────────────────────────────────────────────────────────────
481
+ const schemas = {
482
+ LoginRequest: {
483
+ type: 'object',
484
+ required: ['email', 'password'],
485
+ properties: {
486
+ email: { type: 'string', format: 'email', example: 'user@example.com' },
487
+ password: { type: 'string', format: 'password', example: 'secret123' },
488
+ },
489
+ },
490
+ RegisterRequest: {
491
+ type: 'object',
492
+ required: ['email', 'password'],
493
+ properties: {
494
+ email: { type: 'string', format: 'email' },
495
+ password: { type: 'string', format: 'password', minLength: 8 },
496
+ },
497
+ additionalProperties: true,
498
+ },
499
+ AuthResponse: {
500
+ type: 'object',
501
+ properties: {
502
+ success: { type: 'boolean', example: true },
503
+ accessToken: { type: 'string', description: 'Present only when using bearer strategy' },
504
+ refreshToken: { type: 'string', description: 'Present only when using bearer strategy' },
505
+ },
506
+ required: ['success'],
507
+ },
508
+ TwoFAChallenge: {
509
+ type: 'object',
510
+ properties: {
511
+ twoFactorRequired: { type: 'boolean', example: true },
512
+ userId: { type: 'string' },
513
+ },
514
+ },
515
+ SuccessResponse: {
516
+ type: 'object',
517
+ properties: { success: { type: 'boolean', example: true } },
518
+ required: ['success'],
519
+ },
520
+ UserProfile: {
521
+ type: 'object',
522
+ properties: {
523
+ sub: { type: 'string' },
524
+ email: { type: 'string', format: 'email' },
525
+ role: { type: 'string' },
526
+ loginProvider: { type: 'string' },
527
+ isEmailVerified: { type: 'boolean' },
528
+ isTotpEnabled: { type: 'boolean' },
529
+ roles: { type: 'array', items: { type: 'string' } },
530
+ permissions: { type: 'array', items: { type: 'string' } },
531
+ metadata: { type: 'object', additionalProperties: true },
532
+ },
533
+ },
534
+ LinkedAccount: {
535
+ type: 'object',
536
+ properties: {
537
+ provider: { type: 'string' },
538
+ providerAccountId: { type: 'string' },
539
+ },
540
+ },
541
+ };
542
+ const tags = [
543
+ { name: 'Authentication', description: 'Login, logout, token refresh, and account management' },
544
+ { name: 'Password', description: 'Password reset and change' },
545
+ { name: 'Two-Factor Auth', description: 'TOTP 2FA setup and verification' },
546
+ { name: 'Email', description: 'Email verification and change' },
547
+ { name: 'Magic Link', description: 'Passwordless login via email magic link' },
548
+ { name: 'SMS', description: 'Passwordless login via SMS OTP' },
549
+ ...(hasGoogleOAuth || hasGithubOAuth || oauthProviders.length > 0 ? [{ name: 'OAuth', description: 'Social login via OAuth 2.0 providers' }] : []),
550
+ ...(hasSessionsCleanup ? [{ name: 'Sessions', description: 'Session management' }] : []),
551
+ ...(hasLinkedAccounts ? [{ name: 'Linked Accounts', description: 'Link and unlink OAuth provider accounts' }] : []),
552
+ ];
553
+ return {
554
+ openapi: '3.0.3',
555
+ info: {
556
+ title: 'awesome-node-auth API',
557
+ version: '1.0.0',
558
+ description: 'Authentication and authorization endpoints.',
559
+ },
560
+ tags,
561
+ paths,
562
+ components: {
563
+ securitySchemes: { BearerAuth: BEARER_SCHEME.BearerAuth },
564
+ schemas,
565
+ },
566
+ };
567
+ }
568
+ /**
569
+ * Build an OpenAPI 3.0 document for the admin router.
570
+ *
571
+ * @param options Feature flags controlling optional endpoint visibility.
572
+ * @param basePath Base path where the admin router is mounted (default `'/admin'`).
573
+ */
574
+ function buildAdminOpenApiSpec(options = {}, basePath = '/admin') {
575
+ const { hasSessions = false, hasRoles = false, hasTenants = false, hasMetadata = false, hasSettings = false, hasLinkedAccounts = false, hasApiKeys = false, hasWebhooks = false, hasUi = false, } = options;
576
+ const paths = {};
577
+ const adminAuth = { AdminAuth: [] };
578
+ // ── GET /api/ping ──────────────────────────────────────────────────────────
579
+ paths[`${basePath}/api/ping`] = {
580
+ get: {
581
+ summary: 'Health check',
582
+ operationId: 'adminPing',
583
+ tags: ['Admin'],
584
+ security: [adminAuth],
585
+ responses: { 200: { description: 'pong', content: { 'application/json': { schema: { type: 'object', properties: { ok: { type: 'boolean' } } } } } } },
586
+ },
587
+ };
588
+ // ── Users ──────────────────────────────────────────────────────────────────
589
+ paths[`${basePath}/api/users`] = {
590
+ get: {
591
+ summary: 'List users (paginated)',
592
+ operationId: 'adminListUsers',
593
+ tags: ['Admin — Users'],
594
+ security: [adminAuth],
595
+ parameters: [
596
+ { name: 'limit', in: 'query', schema: { type: 'integer', default: 20 } },
597
+ { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
598
+ { name: 'filter', in: 'query', schema: { type: 'string' }, description: 'Filter by email or ID (case-insensitive substring)' },
599
+ ],
600
+ responses: {
601
+ 200: { description: 'User list', content: { 'application/json': { schema: { type: 'object', properties: { users: { type: 'array', items: { $ref: '#/components/schemas/AdminUser' } }, total: { type: 'integer' } } } } } },
602
+ 401: { description: 'Unauthorized' },
603
+ },
604
+ },
605
+ };
606
+ paths[`${basePath}/api/users/{id}`] = {
607
+ get: {
608
+ summary: 'Get a specific user by ID',
609
+ operationId: 'adminGetUser',
610
+ tags: ['Admin — Users'],
611
+ security: [adminAuth],
612
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
613
+ responses: {
614
+ 200: { description: 'User detail', content: { 'application/json': { schema: { type: 'object', properties: { user: { $ref: '#/components/schemas/AdminUser' } } } } } },
615
+ 401: { description: 'Unauthorized' },
616
+ 404: { description: 'User not found' },
617
+ },
618
+ },
619
+ delete: {
620
+ summary: 'Delete a user',
621
+ operationId: 'adminDeleteUser',
622
+ tags: ['Admin — Users'],
623
+ security: [adminAuth],
624
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
625
+ responses: {
626
+ 200: { description: 'User deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
627
+ 401: { description: 'Unauthorized' },
628
+ },
629
+ },
630
+ };
631
+ // ── 2FA policy ─────────────────────────────────────────────────────────────
632
+ paths[`${basePath}/api/2fa-policy`] = {
633
+ post: {
634
+ summary: 'Enforce or revoke TOTP 2FA for a user',
635
+ operationId: 'admin2faPolicy',
636
+ tags: ['Admin — Users'],
637
+ security: [adminAuth],
638
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['userId', 'require2FA'], properties: { userId: { type: 'string' }, require2FA: { type: 'boolean' } } } } } },
639
+ responses: {
640
+ 200: { description: '2FA policy applied', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
641
+ 401: { description: 'Unauthorized' },
642
+ },
643
+ },
644
+ };
645
+ // ── User metadata (optional) ───────────────────────────────────────────────
646
+ if (hasMetadata) {
647
+ paths[`${basePath}/api/users/{id}/metadata`] = {
648
+ get: {
649
+ summary: 'Get user metadata',
650
+ operationId: 'adminGetUserMetadata',
651
+ tags: ['Admin — Users'],
652
+ security: [adminAuth],
653
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
654
+ responses: {
655
+ 200: { description: 'Metadata key/value pairs', content: { 'application/json': { schema: { type: 'object', properties: { metadata: { type: 'object', additionalProperties: true } } } } } },
656
+ 401: { description: 'Unauthorized' },
657
+ },
658
+ },
659
+ put: {
660
+ summary: 'Update user metadata',
661
+ operationId: 'adminUpdateUserMetadata',
662
+ tags: ['Admin — Users'],
663
+ security: [adminAuth],
664
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
665
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', additionalProperties: true } } } },
666
+ responses: {
667
+ 200: { description: 'Metadata updated', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
668
+ 401: { description: 'Unauthorized' },
669
+ },
670
+ },
671
+ };
672
+ }
673
+ // ── User linked accounts (optional) ────────────────────────────────────────
674
+ if (hasLinkedAccounts) {
675
+ paths[`${basePath}/api/users/{id}/linked-accounts`] = {
676
+ get: {
677
+ summary: 'Get linked OAuth accounts for a user',
678
+ operationId: 'adminGetUserLinkedAccounts',
679
+ tags: ['Admin — Users'],
680
+ security: [adminAuth],
681
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
682
+ responses: {
683
+ 200: { description: 'Linked accounts', content: { 'application/json': { schema: { type: 'object', properties: { linkedAccounts: { type: 'array', items: { type: 'object' } } } } } } },
684
+ 401: { description: 'Unauthorized' },
685
+ },
686
+ },
687
+ };
688
+ }
689
+ // ── User roles (optional) ──────────────────────────────────────────────────
690
+ if (hasRoles) {
691
+ paths[`${basePath}/api/users/{id}/roles`] = {
692
+ get: {
693
+ summary: 'Get roles assigned to a user',
694
+ operationId: 'adminGetUserRoles',
695
+ tags: ['Admin — Roles'],
696
+ security: [adminAuth],
697
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
698
+ responses: {
699
+ 200: { description: 'User roles', content: { 'application/json': { schema: { type: 'object', properties: { roles: { type: 'array', items: { type: 'string' } } } } } } },
700
+ 401: { description: 'Unauthorized' },
701
+ },
702
+ },
703
+ post: {
704
+ summary: 'Assign a role to a user',
705
+ operationId: 'adminAssignUserRole',
706
+ tags: ['Admin — Roles'],
707
+ security: [adminAuth],
708
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
709
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['role'], properties: { role: { type: 'string' } } } } } },
710
+ responses: {
711
+ 200: { description: 'Role assigned', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
712
+ 401: { description: 'Unauthorized' },
713
+ },
714
+ },
715
+ };
716
+ paths[`${basePath}/api/users/{id}/roles/{role}`] = {
717
+ delete: {
718
+ summary: 'Remove a role from a user',
719
+ operationId: 'adminRemoveUserRole',
720
+ tags: ['Admin — Roles'],
721
+ security: [adminAuth],
722
+ parameters: [
723
+ { name: 'id', in: 'path', required: true, schema: { type: 'string' } },
724
+ { name: 'role', in: 'path', required: true, schema: { type: 'string' } },
725
+ ],
726
+ responses: {
727
+ 200: { description: 'Role removed', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
728
+ 401: { description: 'Unauthorized' },
729
+ },
730
+ },
731
+ };
732
+ }
733
+ // ── User tenants (optional) ────────────────────────────────────────────────
734
+ if (hasTenants) {
735
+ paths[`${basePath}/api/users/{id}/tenants`] = {
736
+ get: {
737
+ summary: 'Get tenants the user belongs to',
738
+ operationId: 'adminGetUserTenants',
739
+ tags: ['Admin — Tenants'],
740
+ security: [adminAuth],
741
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
742
+ responses: {
743
+ 200: { description: 'Tenant list', content: { 'application/json': { schema: { type: 'object', properties: { tenants: { type: 'array', items: { $ref: '#/components/schemas/AdminTenant' } } } } } } },
744
+ 401: { description: 'Unauthorized' },
745
+ },
746
+ },
747
+ };
748
+ }
749
+ // ── Settings (optional) ────────────────────────────────────────────────────
750
+ if (hasSettings) {
751
+ paths[`${basePath}/api/settings`] = {
752
+ get: {
753
+ summary: 'Get global auth settings',
754
+ operationId: 'adminGetSettings',
755
+ tags: ['Admin — Settings'],
756
+ security: [adminAuth],
757
+ responses: {
758
+ 200: { description: 'Settings', content: { 'application/json': { schema: { type: 'object', properties: { settings: { $ref: '#/components/schemas/AdminSettings' } } } } } },
759
+ 401: { description: 'Unauthorized' },
760
+ },
761
+ },
762
+ put: {
763
+ summary: 'Update global auth settings',
764
+ operationId: 'adminUpdateSettings',
765
+ tags: ['Admin — Settings'],
766
+ security: [adminAuth],
767
+ requestBody: { required: true, content: { 'application/json': { schema: { $ref: '#/components/schemas/AdminSettings' } } } },
768
+ responses: {
769
+ 200: { description: 'Settings updated', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
770
+ 401: { description: 'Unauthorized' },
771
+ },
772
+ },
773
+ };
774
+ }
775
+ // ── Sessions (optional) ────────────────────────────────────────────────────
776
+ if (hasSessions) {
777
+ paths[`${basePath}/api/sessions`] = {
778
+ get: {
779
+ summary: 'List active sessions',
780
+ operationId: 'adminListSessions',
781
+ tags: ['Admin — Sessions'],
782
+ security: [adminAuth],
783
+ parameters: [
784
+ { name: 'userId', in: 'query', schema: { type: 'string' } },
785
+ { name: 'limit', in: 'query', schema: { type: 'integer', default: 20 } },
786
+ { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
787
+ { name: 'filter', in: 'query', schema: { type: 'string' } },
788
+ ],
789
+ responses: {
790
+ 200: { description: 'Session list', content: { 'application/json': { schema: { type: 'object', properties: { sessions: { type: 'array', items: { $ref: '#/components/schemas/AdminSession' } }, total: { type: 'integer' } } } } } },
791
+ 401: { description: 'Unauthorized' },
792
+ },
793
+ },
794
+ };
795
+ paths[`${basePath}/api/sessions/{handle}`] = {
796
+ delete: {
797
+ summary: 'Revoke a session by handle',
798
+ operationId: 'adminRevokeSession',
799
+ tags: ['Admin — Sessions'],
800
+ security: [adminAuth],
801
+ parameters: [{ name: 'handle', in: 'path', required: true, schema: { type: 'string' } }],
802
+ responses: {
803
+ 200: { description: 'Session revoked', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
804
+ 401: { description: 'Unauthorized' },
805
+ },
806
+ },
807
+ };
808
+ }
809
+ // ── Roles (optional) ───────────────────────────────────────────────────────
810
+ if (hasRoles) {
811
+ paths[`${basePath}/api/roles`] = {
812
+ get: {
813
+ summary: 'List all roles',
814
+ operationId: 'adminListRoles',
815
+ tags: ['Admin — Roles'],
816
+ security: [adminAuth],
817
+ responses: {
818
+ 200: { description: 'Role list', content: { 'application/json': { schema: { type: 'object', properties: { roles: { type: 'array', items: { type: 'object' } } } } } } },
819
+ 401: { description: 'Unauthorized' },
820
+ },
821
+ },
822
+ post: {
823
+ summary: 'Create a role',
824
+ operationId: 'adminCreateRole',
825
+ tags: ['Admin — Roles'],
826
+ security: [adminAuth],
827
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, permissions: { type: 'array', items: { type: 'string' } } } } } } },
828
+ responses: {
829
+ 200: { description: 'Role created', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
830
+ 401: { description: 'Unauthorized' },
831
+ },
832
+ },
833
+ };
834
+ paths[`${basePath}/api/roles/{name}`] = {
835
+ delete: {
836
+ summary: 'Delete a role',
837
+ operationId: 'adminDeleteRole',
838
+ tags: ['Admin — Roles'],
839
+ security: [adminAuth],
840
+ parameters: [{ name: 'name', in: 'path', required: true, schema: { type: 'string' } }],
841
+ responses: {
842
+ 200: { description: 'Role deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
843
+ 401: { description: 'Unauthorized' },
844
+ },
845
+ },
846
+ };
847
+ }
848
+ // ── Tenants (optional) ─────────────────────────────────────────────────────
849
+ if (hasTenants) {
850
+ paths[`${basePath}/api/tenants`] = {
851
+ get: {
852
+ summary: 'List all tenants',
853
+ operationId: 'adminListTenants',
854
+ tags: ['Admin — Tenants'],
855
+ security: [adminAuth],
856
+ responses: {
857
+ 200: { description: 'Tenant list', content: { 'application/json': { schema: { type: 'object', properties: { tenants: { type: 'array', items: { $ref: '#/components/schemas/AdminTenant' } } } } } } },
858
+ 401: { description: 'Unauthorized' },
859
+ },
860
+ },
861
+ post: {
862
+ summary: 'Create a tenant',
863
+ operationId: 'adminCreateTenant',
864
+ tags: ['Admin — Tenants'],
865
+ security: [adminAuth],
866
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, isActive: { type: 'boolean' } } } } } },
867
+ responses: {
868
+ 200: { description: 'Tenant created', content: { 'application/json': { schema: { type: 'object', properties: { tenant: { $ref: '#/components/schemas/AdminTenant' } } } } } },
869
+ 401: { description: 'Unauthorized' },
870
+ },
871
+ },
872
+ };
873
+ paths[`${basePath}/api/tenants/{id}`] = {
874
+ delete: {
875
+ summary: 'Delete a tenant',
876
+ operationId: 'adminDeleteTenant',
877
+ tags: ['Admin — Tenants'],
878
+ security: [adminAuth],
879
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
880
+ responses: {
881
+ 200: { description: 'Tenant deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
882
+ 401: { description: 'Unauthorized' },
883
+ },
884
+ },
885
+ };
886
+ paths[`${basePath}/api/tenants/{id}/users`] = {
887
+ get: {
888
+ summary: 'List users in a tenant',
889
+ operationId: 'adminGetTenantUsers',
890
+ tags: ['Admin — Tenants'],
891
+ security: [adminAuth],
892
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
893
+ responses: {
894
+ 200: { description: 'User IDs', content: { 'application/json': { schema: { type: 'object', properties: { userIds: { type: 'array', items: { type: 'string' } } } } } } },
895
+ 401: { description: 'Unauthorized' },
896
+ },
897
+ },
898
+ post: {
899
+ summary: 'Add a user to a tenant',
900
+ operationId: 'adminAddTenantUser',
901
+ tags: ['Admin — Tenants'],
902
+ security: [adminAuth],
903
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
904
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', required: ['userId'], properties: { userId: { type: 'string' } } } } } },
905
+ responses: {
906
+ 200: { description: 'User added', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
907
+ 401: { description: 'Unauthorized' },
908
+ },
909
+ },
910
+ };
911
+ paths[`${basePath}/api/tenants/{id}/users/{userId}`] = {
912
+ delete: {
913
+ summary: 'Remove a user from a tenant',
914
+ operationId: 'adminRemoveTenantUser',
915
+ tags: ['Admin — Tenants'],
916
+ security: [adminAuth],
917
+ parameters: [
918
+ { name: 'id', in: 'path', required: true, schema: { type: 'string' } },
919
+ { name: 'userId', in: 'path', required: true, schema: { type: 'string' } },
920
+ ],
921
+ responses: {
922
+ 200: { description: 'User removed', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
923
+ 401: { description: 'Unauthorized' },
924
+ },
925
+ },
926
+ };
927
+ }
928
+ // ── API Keys (optional) ────────────────────────────────────────────────────
929
+ if (hasApiKeys) {
930
+ paths[`${basePath}/api/api-keys`] = {
931
+ get: {
932
+ summary: 'List API keys (paginated)',
933
+ operationId: 'adminListApiKeys',
934
+ tags: ['Admin — API Keys'],
935
+ security: [adminAuth],
936
+ parameters: [
937
+ { name: 'limit', in: 'query', schema: { type: 'integer', default: 20 } },
938
+ { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
939
+ { name: 'filter', in: 'query', schema: { type: 'string' }, description: 'Filter by name, service ID, or prefix' },
940
+ ],
941
+ responses: {
942
+ 200: { description: 'API key list', content: { 'application/json': { schema: { type: 'object', properties: { keys: { type: 'array', items: { $ref: '#/components/schemas/AdminApiKey' } }, total: { type: 'integer' } } } } } },
943
+ 401: { description: 'Unauthorized' },
944
+ },
945
+ },
946
+ post: {
947
+ summary: 'Create a new API key (returns rawKey once)',
948
+ operationId: 'adminCreateApiKey',
949
+ tags: ['Admin — API Keys'],
950
+ security: [adminAuth],
951
+ requestBody: {
952
+ required: true,
953
+ content: { 'application/json': { schema: { type: 'object', required: ['name'], properties: { name: { type: 'string' }, serviceId: { type: 'string' }, scopes: { type: 'array', items: { type: 'string' } }, allowedIps: { type: 'array', items: { type: 'string' } }, expiresAt: { type: 'string', format: 'date-time' } } } } },
954
+ },
955
+ responses: {
956
+ 200: { description: 'API key created. rawKey is shown once only.', content: { 'application/json': { schema: { type: 'object', properties: { rawKey: { type: 'string' }, record: { $ref: '#/components/schemas/AdminApiKey' } } } } } },
957
+ 400: { description: 'Validation error' },
958
+ 401: { description: 'Unauthorized' },
959
+ },
960
+ },
961
+ };
962
+ paths[`${basePath}/api/api-keys/{id}/revoke`] = {
963
+ delete: {
964
+ summary: 'Revoke an API key (sets isActive: false)',
965
+ operationId: 'adminRevokeApiKey',
966
+ tags: ['Admin — API Keys'],
967
+ security: [adminAuth],
968
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
969
+ responses: {
970
+ 200: { description: 'API key revoked', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
971
+ 401: { description: 'Unauthorized' },
972
+ },
973
+ },
974
+ };
975
+ paths[`${basePath}/api/api-keys/{id}`] = {
976
+ delete: {
977
+ summary: 'Hard-delete an API key record',
978
+ operationId: 'adminDeleteApiKey',
979
+ tags: ['Admin — API Keys'],
980
+ security: [adminAuth],
981
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
982
+ responses: {
983
+ 200: { description: 'API key deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
984
+ 401: { description: 'Unauthorized' },
985
+ },
986
+ },
987
+ };
988
+ }
989
+ // ── Webhooks admin (optional) ──────────────────────────────────────────────
990
+ if (hasWebhooks) {
991
+ paths[`${basePath}/api/webhooks`] = {
992
+ get: {
993
+ summary: 'List registered webhooks (paginated)',
994
+ operationId: 'adminListWebhooks',
995
+ tags: ['Admin — Webhooks'],
996
+ security: [adminAuth],
997
+ parameters: [
998
+ { name: 'limit', in: 'query', schema: { type: 'integer', default: 20 } },
999
+ { name: 'offset', in: 'query', schema: { type: 'integer', default: 0 } },
1000
+ ],
1001
+ responses: {
1002
+ 200: { description: 'Webhook list', content: { 'application/json': { schema: { type: 'object', properties: { webhooks: { type: 'array', items: { $ref: '#/components/schemas/AdminWebhook' } }, total: { type: 'integer' } } } } } },
1003
+ 401: { description: 'Unauthorized' },
1004
+ },
1005
+ },
1006
+ post: {
1007
+ summary: 'Register a new outgoing webhook',
1008
+ operationId: 'adminCreateWebhook',
1009
+ tags: ['Admin — Webhooks'],
1010
+ security: [adminAuth],
1011
+ requestBody: {
1012
+ required: true,
1013
+ content: { 'application/json': { schema: { type: 'object', required: ['url'], properties: { url: { type: 'string', format: 'uri' }, events: { type: 'array', items: { type: 'string' } }, secret: { type: 'string' }, tenantId: { type: 'string' }, isActive: { type: 'boolean', default: true }, maxRetries: { type: 'integer', default: 3 } } } } },
1014
+ },
1015
+ responses: {
1016
+ 200: { description: 'Webhook created', content: { 'application/json': { schema: { type: 'object', properties: { webhook: { $ref: '#/components/schemas/AdminWebhook' } } } } } },
1017
+ 400: { description: 'Validation error' },
1018
+ 401: { description: 'Unauthorized' },
1019
+ },
1020
+ },
1021
+ };
1022
+ paths[`${basePath}/api/webhooks/{id}`] = {
1023
+ patch: {
1024
+ summary: 'Partially update a webhook (e.g. toggle isActive)',
1025
+ operationId: 'adminUpdateWebhook',
1026
+ tags: ['Admin — Webhooks'],
1027
+ security: [adminAuth],
1028
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
1029
+ requestBody: { required: true, content: { 'application/json': { schema: { type: 'object', properties: { isActive: { type: 'boolean' }, url: { type: 'string' }, events: { type: 'array', items: { type: 'string' } } } } } } },
1030
+ responses: {
1031
+ 200: { description: 'Webhook updated', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
1032
+ 401: { description: 'Unauthorized' },
1033
+ },
1034
+ },
1035
+ delete: {
1036
+ summary: 'Delete a webhook registration',
1037
+ operationId: 'adminDeleteWebhook',
1038
+ tags: ['Admin — Webhooks'],
1039
+ security: [adminAuth],
1040
+ parameters: [{ name: 'id', in: 'path', required: true, schema: { type: 'string' } }],
1041
+ responses: {
1042
+ 200: { description: 'Webhook deleted', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
1043
+ 401: { description: 'Unauthorized' },
1044
+ },
1045
+ },
1046
+ };
1047
+ }
1048
+ // ── UI Customization admin (optional) ──────────────────────────────────────
1049
+ if (hasUi) {
1050
+ paths[`${basePath}/api/ui-settings`] = {
1051
+ get: {
1052
+ summary: 'Get UI customization settings',
1053
+ operationId: 'adminGetUiSettings',
1054
+ tags: ['Admin — UI'],
1055
+ security: [adminAuth],
1056
+ responses: {
1057
+ 200: { description: 'UI settings', content: { 'application/json': { schema: { $ref: '#/components/schemas/UiSettings' } } } },
1058
+ 401: { description: 'Unauthorized' },
1059
+ },
1060
+ },
1061
+ post: {
1062
+ summary: 'Update UI customization settings',
1063
+ operationId: 'adminUpdateUiSettings',
1064
+ tags: ['Admin — UI'],
1065
+ security: [adminAuth],
1066
+ requestBody: {
1067
+ required: true,
1068
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/UiSettings' } } },
1069
+ },
1070
+ responses: {
1071
+ 200: { description: 'Settings updated', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
1072
+ 401: { description: 'Unauthorized' },
1073
+ },
1074
+ },
1075
+ };
1076
+ paths[`${basePath}/api/ui/logo`] = {
1077
+ post: {
1078
+ summary: 'Upload a custom logo image',
1079
+ operationId: 'adminUploadLogo',
1080
+ tags: ['Admin — UI'],
1081
+ security: [adminAuth],
1082
+ requestBody: {
1083
+ content: {
1084
+ 'multipart/form-data': {
1085
+ schema: { type: 'object', properties: { logo: { type: 'string', format: 'binary' } } },
1086
+ },
1087
+ },
1088
+ },
1089
+ responses: {
1090
+ 200: { description: 'Logo uploaded', content: { 'application/json': { schema: { type: 'object', properties: { success: { type: 'boolean' }, logoUrl: { type: 'string' } } } } } },
1091
+ 401: { description: 'Unauthorized' },
1092
+ },
1093
+ },
1094
+ delete: {
1095
+ summary: 'Remove custom logo',
1096
+ operationId: 'adminDeleteLogo',
1097
+ tags: ['Admin — UI'],
1098
+ security: [adminAuth],
1099
+ responses: {
1100
+ 200: { description: 'Logo removed', content: { 'application/json': { schema: { $ref: '#/components/schemas/SuccessResponse' } } } },
1101
+ 401: { description: 'Unauthorized' },
1102
+ },
1103
+ },
1104
+ };
1105
+ }
1106
+ // ── Schemas ────────────────────────────────────────────────────────────────
1107
+ const schemas = {
1108
+ SuccessResponse: {
1109
+ type: 'object',
1110
+ properties: { success: { type: 'boolean', example: true } },
1111
+ required: ['success'],
1112
+ },
1113
+ AdminUser: {
1114
+ type: 'object',
1115
+ properties: {
1116
+ id: { type: 'string' },
1117
+ email: { type: 'string', format: 'email' },
1118
+ role: { type: 'string' },
1119
+ isEmailVerified: { type: 'boolean' },
1120
+ isTotpEnabled: { type: 'boolean' },
1121
+ loginProvider: { type: 'string' },
1122
+ lastLogin: { type: 'string', format: 'date-time' },
1123
+ createdAt: { type: 'string', format: 'date-time' },
1124
+ },
1125
+ },
1126
+ AdminSession: {
1127
+ type: 'object',
1128
+ properties: {
1129
+ handle: { type: 'string' },
1130
+ userId: { type: 'string' },
1131
+ deviceInfo: { type: 'string' },
1132
+ ip: { type: 'string' },
1133
+ createdAt: { type: 'string', format: 'date-time' },
1134
+ expiresAt: { type: 'string', format: 'date-time' },
1135
+ },
1136
+ },
1137
+ AdminTenant: {
1138
+ type: 'object',
1139
+ properties: {
1140
+ id: { type: 'string' },
1141
+ name: { type: 'string' },
1142
+ isActive: { type: 'boolean' },
1143
+ },
1144
+ },
1145
+ AdminSettings: {
1146
+ type: 'object',
1147
+ properties: {
1148
+ require2FA: { type: 'boolean' },
1149
+ emailVerificationMode: { type: 'string', enum: ['none', 'lazy', 'strict'] },
1150
+ emailVerificationGracePeriodDays: { type: 'integer' },
1151
+ },
1152
+ },
1153
+ AdminApiKey: {
1154
+ type: 'object',
1155
+ properties: {
1156
+ id: { type: 'string' },
1157
+ name: { type: 'string' },
1158
+ keyPrefix: { type: 'string', description: 'First ~11 chars of the key (safe to display)' },
1159
+ serviceId: { type: 'string', nullable: true },
1160
+ scopes: { type: 'array', items: { type: 'string' } },
1161
+ allowedIps: { type: 'array', items: { type: 'string' }, nullable: true },
1162
+ isActive: { type: 'boolean' },
1163
+ expiresAt: { type: 'string', format: 'date-time', nullable: true },
1164
+ createdAt: { type: 'string', format: 'date-time' },
1165
+ lastUsedAt: { type: 'string', format: 'date-time', nullable: true },
1166
+ },
1167
+ },
1168
+ AdminWebhook: {
1169
+ type: 'object',
1170
+ properties: {
1171
+ id: { type: 'string' },
1172
+ url: { type: 'string', format: 'uri' },
1173
+ events: { type: 'array', items: { type: 'string' } },
1174
+ isActive: { type: 'boolean' },
1175
+ tenantId: { type: 'string', nullable: true },
1176
+ maxRetries: { type: 'integer' },
1177
+ retryDelayMs: { type: 'integer' },
1178
+ secret: { type: 'string', description: 'Masked as *** if set', nullable: true },
1179
+ },
1180
+ },
1181
+ UiSettings: {
1182
+ type: 'object',
1183
+ properties: {
1184
+ primaryColor: { type: 'string', example: '#1a1a2e' },
1185
+ secondaryColor: { type: 'string', example: '#ffffff' },
1186
+ logoUrl: { type: 'string', example: '/auth/ui/assets/logo/logo.png', nullable: true },
1187
+ siteName: { type: 'string', example: 'My Auth Service' },
1188
+ },
1189
+ },
1190
+ };
1191
+ const tags = [
1192
+ { name: 'Admin', description: 'Admin health check' },
1193
+ { name: 'Admin — Users', description: 'User management' },
1194
+ ...(hasSessions ? [{ name: 'Admin — Sessions', description: 'Session management' }] : []),
1195
+ ...(hasRoles ? [{ name: 'Admin — Roles', description: 'Role and permission management' }] : []),
1196
+ ...(hasTenants ? [{ name: 'Admin — Tenants', description: 'Tenant management' }] : []),
1197
+ ...(hasSettings ? [{ name: 'Admin — Settings', description: 'Global auth settings' }] : []),
1198
+ ...(hasApiKeys ? [{ name: 'Admin — API Keys', description: 'API key / service token management' }] : []),
1199
+ ...(hasWebhooks ? [{ name: 'Admin — Webhooks', description: 'Outgoing webhook management' }] : []),
1200
+ ...(hasUi ? [{ name: 'Admin — UI', description: 'UI customization and preview' }] : []),
1201
+ ];
1202
+ return {
1203
+ openapi: '3.0.3',
1204
+ info: {
1205
+ title: 'awesome-node-auth Admin API',
1206
+ version: '1.0.0',
1207
+ description: 'Admin REST API for user, session, role, tenant, settings, API key, and webhook management.',
1208
+ },
1209
+ tags,
1210
+ paths,
1211
+ components: {
1212
+ securitySchemes: {
1213
+ AdminAuth: {
1214
+ type: 'http',
1215
+ scheme: 'bearer',
1216
+ description: 'Admin secret token — pass as `Authorization: Bearer <adminSecret>`',
1217
+ },
1218
+ },
1219
+ schemas,
1220
+ },
1221
+ };
1222
+ }
1223
+ // ─────────────────────────────────────────────────────────────────────────────
1224
+ // Tools router spec (unchanged public API)
1225
+ // ─────────────────────────────────────────────────────────────────────────────
1226
+ /**
1227
+ * Build an OpenAPI 3.0 JSON document describing the enabled `/tools` routes.
1228
+ *
1229
+ * Routes that are disabled via `options` are omitted from the spec.
1230
+ *
1231
+ * @param options The same `ToolsRouterOptions` passed to `createToolsRouter`.
1232
+ * @param basePath Base path where the tools router is mounted (default `'/tools'`).
1233
+ */
1234
+ function buildOpenApiSpec(options = {}, basePath = '/tools') {
1235
+ const { telemetry = true, notify = true, stream = true, webhook = true, } = options;
1236
+ const hasTelemetryQuery = telemetry && !!options.telemetryStore?.query;
1237
+ const paths = {};
1238
+ const bearer = { BearerAuth: [] };
1239
+ // ── POST /track/:eventName ────────────────────────────────────────────────
1240
+ if (telemetry) {
1241
+ paths[`${basePath}/track/{eventName}`] = {
1242
+ post: {
1243
+ summary: 'Track a telemetry event',
1244
+ operationId: 'trackEvent',
1245
+ tags: ['Telemetry'],
1246
+ security: [bearer],
1247
+ parameters: [
1248
+ {
1249
+ name: 'eventName',
1250
+ in: 'path',
1251
+ required: true,
1252
+ description: 'Event name in domain.resource.action format (e.g. identity.auth.login.success)',
1253
+ schema: { type: 'string', example: 'identity.auth.login.success' },
1254
+ },
1255
+ ],
1256
+ requestBody: {
1257
+ required: false,
1258
+ content: {
1259
+ 'application/json': {
1260
+ schema: { $ref: '#/components/schemas/TrackPayload' },
1261
+ },
1262
+ },
1263
+ },
1264
+ responses: {
1265
+ 202: {
1266
+ description: 'Event accepted',
1267
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/OkResponse' } } },
1268
+ },
1269
+ 401: { description: 'Unauthorized' },
1270
+ },
1271
+ },
1272
+ };
1273
+ }
1274
+ // ── GET /telemetry ────────────────────────────────────────────────────────
1275
+ if (hasTelemetryQuery) {
1276
+ paths[`${basePath}/telemetry`] = {
1277
+ get: {
1278
+ summary: 'Query persisted telemetry events',
1279
+ operationId: 'queryTelemetry',
1280
+ tags: ['Telemetry'],
1281
+ security: [bearer],
1282
+ parameters: [
1283
+ { name: 'event', in: 'query', schema: { type: 'string' }, description: 'Filter by event name' },
1284
+ { name: 'userId', in: 'query', schema: { type: 'string' } },
1285
+ { name: 'tenantId', in: 'query', schema: { type: 'string' } },
1286
+ { name: 'from', in: 'query', schema: { type: 'string', format: 'date-time' } },
1287
+ { name: 'to', in: 'query', schema: { type: 'string', format: 'date-time' } },
1288
+ { name: 'limit', in: 'query', schema: { type: 'integer', minimum: 1 } },
1289
+ { name: 'offset', in: 'query', schema: { type: 'integer', minimum: 0 } },
1290
+ ],
1291
+ responses: {
1292
+ 200: {
1293
+ description: 'List of telemetry events',
1294
+ content: {
1295
+ 'application/json': {
1296
+ schema: {
1297
+ type: 'object',
1298
+ properties: {
1299
+ data: { type: 'array', items: { $ref: '#/components/schemas/TelemetryEvent' } },
1300
+ },
1301
+ },
1302
+ },
1303
+ },
1304
+ },
1305
+ 401: { description: 'Unauthorized' },
1306
+ },
1307
+ },
1308
+ };
1309
+ }
1310
+ // ── POST /notify/:target ──────────────────────────────────────────────────
1311
+ if (notify) {
1312
+ paths[`${basePath}/notify/{target}`] = {
1313
+ post: {
1314
+ summary: 'Send a real-time SSE notification to a topic',
1315
+ operationId: 'notifyTarget',
1316
+ tags: ['Notifications'],
1317
+ security: [bearer],
1318
+ parameters: [
1319
+ {
1320
+ name: 'target',
1321
+ in: 'path',
1322
+ required: true,
1323
+ description: 'Topic (e.g. user:123, tenant:acme, global)',
1324
+ schema: { type: 'string', example: 'user:123' },
1325
+ },
1326
+ ],
1327
+ requestBody: {
1328
+ required: true,
1329
+ content: {
1330
+ 'application/json': {
1331
+ schema: { $ref: '#/components/schemas/NotifyPayload' },
1332
+ },
1333
+ },
1334
+ },
1335
+ responses: {
1336
+ 202: {
1337
+ description: 'Notification dispatched',
1338
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/OkResponse' } } },
1339
+ },
1340
+ 401: { description: 'Unauthorized' },
1341
+ },
1342
+ },
1343
+ };
1344
+ }
1345
+ // ── GET /stream ───────────────────────────────────────────────────────────
1346
+ if (stream) {
1347
+ paths[`${basePath}/stream`] = {
1348
+ get: {
1349
+ summary: 'Subscribe to real-time events via Server-Sent Events',
1350
+ operationId: 'sseStream',
1351
+ tags: ['Notifications'],
1352
+ security: [bearer],
1353
+ parameters: [
1354
+ {
1355
+ name: 'topics',
1356
+ in: 'query',
1357
+ required: false,
1358
+ description: 'Comma-separated list of topics to subscribe to. The server enforces authorization.',
1359
+ schema: { type: 'string', example: 'global,user:123' },
1360
+ },
1361
+ ],
1362
+ responses: {
1363
+ 200: {
1364
+ description: 'SSE stream (text/event-stream)',
1365
+ content: {
1366
+ 'text/event-stream': {
1367
+ schema: { type: 'string', description: 'Newline-delimited SSE frames' },
1368
+ },
1369
+ },
1370
+ },
1371
+ 401: { description: 'Unauthorized' },
1372
+ 503: { description: 'SSE not enabled on this server' },
1373
+ },
1374
+ },
1375
+ };
1376
+ }
1377
+ // ── POST /webhook/:provider ───────────────────────────────────────────────
1378
+ if (webhook) {
1379
+ paths[`${basePath}/webhook/{provider}`] = {
1380
+ post: {
1381
+ summary: 'Receive an inbound webhook from an external provider',
1382
+ operationId: 'inboundWebhook',
1383
+ tags: ['Webhooks'],
1384
+ parameters: [
1385
+ {
1386
+ name: 'provider',
1387
+ in: 'path',
1388
+ required: true,
1389
+ description: 'Provider identifier (e.g. stripe, github)',
1390
+ schema: { type: 'string', example: 'stripe' },
1391
+ },
1392
+ {
1393
+ name: 'X-Hub-Signature-256',
1394
+ in: 'header',
1395
+ required: false,
1396
+ description: 'HMAC-SHA256 signature for payload verification (optional)',
1397
+ schema: { type: 'string' },
1398
+ },
1399
+ ],
1400
+ requestBody: {
1401
+ required: true,
1402
+ content: {
1403
+ 'application/json': { schema: { type: 'object', description: 'Provider-specific payload' } },
1404
+ },
1405
+ },
1406
+ responses: {
1407
+ 200: {
1408
+ description: 'Webhook accepted',
1409
+ content: { 'application/json': { schema: { $ref: '#/components/schemas/OkResponse' } } },
1410
+ },
1411
+ 400: { description: 'Webhook processing failed' },
1412
+ },
1413
+ },
1414
+ };
1415
+ }
1416
+ // ── Component schemas ─────────────────────────────────────────────────────
1417
+ const components = {
1418
+ securitySchemes: {
1419
+ BearerAuth: {
1420
+ type: 'http',
1421
+ scheme: 'bearer',
1422
+ bearerFormat: 'JWT',
1423
+ },
1424
+ },
1425
+ schemas: {
1426
+ OkResponse: {
1427
+ type: 'object',
1428
+ properties: { ok: { type: 'boolean', example: true } },
1429
+ required: ['ok'],
1430
+ },
1431
+ TrackPayload: {
1432
+ type: 'object',
1433
+ description: 'Telemetry event payload',
1434
+ properties: {
1435
+ data: { description: 'Arbitrary event data' },
1436
+ userId: { type: 'string' },
1437
+ tenantId: { type: 'string' },
1438
+ sessionId: { type: 'string' },
1439
+ correlationId: { type: 'string' },
1440
+ },
1441
+ },
1442
+ NotifyPayload: {
1443
+ type: 'object',
1444
+ description: 'SSE notification payload',
1445
+ required: ['data'],
1446
+ properties: {
1447
+ data: { description: 'Payload to deliver' },
1448
+ type: { type: 'string', description: 'Event type label', example: 'notification' },
1449
+ tenantId: { type: 'string' },
1450
+ userId: { type: 'string' },
1451
+ metadata: { type: 'object', additionalProperties: true },
1452
+ },
1453
+ },
1454
+ TelemetryEvent: {
1455
+ type: 'object',
1456
+ properties: {
1457
+ id: { type: 'string', format: 'uuid' },
1458
+ event: { type: 'string' },
1459
+ timestamp: { type: 'string', format: 'date-time' },
1460
+ data: {},
1461
+ userId: { type: 'string' },
1462
+ tenantId: { type: 'string' },
1463
+ sessionId: { type: 'string' },
1464
+ correlationId: { type: 'string' },
1465
+ ip: { type: 'string' },
1466
+ userAgent: { type: 'string' },
1467
+ },
1468
+ },
1469
+ },
1470
+ };
1471
+ const tags = [
1472
+ telemetry ? { name: 'Telemetry', description: 'Event tracking and query' } : null,
1473
+ (notify || stream) ? { name: 'Notifications', description: 'Real-time SSE notifications' } : null,
1474
+ webhook ? { name: 'Webhooks', description: 'Inbound webhook processing' } : null,
1475
+ ].filter(Boolean);
1476
+ return {
1477
+ openapi: '3.0.3',
1478
+ info: {
1479
+ title: 'awesome-node-auth Tools API',
1480
+ version: '1.0.0',
1481
+ description: 'Optional event-driven tools: telemetry, SSE notifications, and webhooks.',
1482
+ },
1483
+ tags,
1484
+ paths,
1485
+ components,
1486
+ };
1487
+ }
1488
+ /**
1489
+ * Generate a self-contained Swagger UI HTML page that loads the spec from
1490
+ * the provided `specUrl` using the official CDN bundles.
1491
+ *
1492
+ * @param specUrl URL of the OpenAPI JSON endpoint (default: `'./openapi.json'`).
1493
+ */
1494
+ function buildSwaggerUiHtml(specUrl = './openapi.json') {
1495
+ return `<!DOCTYPE html>
1496
+ <html lang="en">
1497
+ <head>
1498
+ <meta charset="utf-8" />
1499
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
1500
+ <title>awesome-node-auth Tools API — Swagger UI</title>
1501
+ <link rel="stylesheet" href="https://unpkg.com/swagger-ui-dist@5/swagger-ui.css" />
1502
+ </head>
1503
+ <body>
1504
+ <div id="swagger-ui"></div>
1505
+ <script src="https://unpkg.com/swagger-ui-dist@5/swagger-ui-bundle.js"></script>
1506
+ <script>
1507
+ SwaggerUIBundle({
1508
+ url: ${JSON.stringify(specUrl)},
1509
+ dom_id: '#swagger-ui',
1510
+ presets: [SwaggerUIBundle.presets.apis, SwaggerUIBundle.SwaggerUIStandalonePreset],
1511
+ layout: 'BaseLayout',
1512
+ deepLinking: true,
1513
+ });
1514
+ </script>
1515
+ </body>
1516
+ </html>`;
1517
+ }
1518
+ //# sourceMappingURL=openapi.js.map