@simple-auth-kit/cli 1.5.1 → 1.6.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 (182) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
  3. package/registry/combos/express-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
  4. package/registry/combos/express-drizzle/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
  5. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/config}/auth.config.ts +1 -1
  6. package/registry/combos/express-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
  7. package/registry/combos/express-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  8. package/registry/combos/express-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
  9. package/registry/combos/{nestjs-drizzle/shared/src → express-drizzle/shared/src/helpers}/pagination.ts +17 -3
  10. package/registry/combos/express-drizzle/shared/src/middleware/auth-core-error.middleware.ts +6 -8
  11. package/registry/combos/express-drizzle/shared/src/middleware/auth.middleware.ts +6 -8
  12. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/openapi}/openapi-fragment.ts +13 -4
  13. package/registry/combos/express-drizzle/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +251 -52
  14. package/registry/combos/express-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
  15. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +2 -4
  16. package/registry/combos/express-drizzle/shared/src/repositories/session.repository.ts +3 -3
  17. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  18. package/registry/combos/express-drizzle/shared/src/route-tiers.ts +52 -11
  19. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +2 -2
  20. package/registry/combos/express-drizzle/test/bootstrap.ts +4 -2
  21. package/registry/combos/express-drizzle/test/prove-cycle.ts +1095 -308
  22. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +5 -5
  23. package/registry/combos/express-drizzle/variants/base/src/index.ts +8 -0
  24. package/registry/combos/express-drizzle/variants/base/src/middleware/authz.middleware.ts +2 -2
  25. package/registry/combos/express-drizzle/variants/base/src/openapi/openapi-admin.ts +1082 -0
  26. package/registry/combos/express-drizzle/variants/base/src/rbac.defaults.ts +60 -17
  27. package/registry/combos/express-drizzle/variants/base/src/repositories/audit-log.repository.ts +2 -2
  28. package/registry/combos/express-drizzle/variants/base/src/repositories/rbac.repository.ts +5 -5
  29. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +18 -22
  30. package/registry/combos/express-drizzle/variants/base/src/seed.ts +76 -17
  31. package/registry/combos/express-drizzle/variants/base/src/services/auth.service.ts +7 -9
  32. package/registry/combos/express-drizzle/variants/base/test/variant-hooks.ts +167 -45
  33. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +5 -5
  34. package/registry/combos/express-drizzle/variants/workspaces/src/index.ts +8 -0
  35. package/registry/combos/express-drizzle/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
  36. package/registry/combos/express-drizzle/variants/workspaces/src/openapi/openapi-admin.ts +1176 -0
  37. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +1 -1
  38. package/registry/combos/express-drizzle/variants/workspaces/src/rbac.defaults.ts +66 -19
  39. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  40. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +5 -5
  41. package/registry/combos/express-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +9 -11
  42. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +18 -22
  43. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +1 -1
  44. package/registry/combos/express-drizzle/variants/workspaces/src/seed.ts +168 -35
  45. package/registry/combos/express-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
  46. package/registry/combos/express-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
  47. package/registry/combos/express-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
  48. package/registry/combos/express-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +25 -9
  49. package/registry/combos/express-prisma/shared/src/{rate-limit.store.ts → cache/rate-limit.store.ts} +4 -1
  50. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/config}/auth.config.ts +1 -1
  51. package/registry/combos/express-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  52. package/registry/combos/{nestjs-prisma/shared/src → express-prisma/shared/src/helpers}/id.helper.ts +6 -2
  53. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/helpers}/pagination.ts +17 -3
  54. package/registry/combos/express-prisma/shared/src/middleware/auth-core-error.middleware.ts +6 -8
  55. package/registry/combos/express-prisma/shared/src/middleware/auth.middleware.ts +6 -8
  56. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/openapi}/openapi-fragment.ts +13 -4
  57. package/registry/combos/express-prisma/shared/src/{openapi-spec.ts → openapi/openapi-spec.ts} +313 -55
  58. package/registry/combos/express-prisma/shared/src/repositories/oauth.repository.ts +2 -2
  59. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
  60. package/registry/combos/express-prisma/shared/src/repositories/session.repository.ts +2 -2
  61. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  62. package/registry/combos/express-prisma/shared/src/route-tiers.ts +52 -11
  63. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +2 -2
  64. package/registry/combos/express-prisma/test/bootstrap.ts +7 -3
  65. package/registry/combos/express-prisma/test/prove-cycle.ts +1095 -308
  66. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +6 -6
  67. package/registry/combos/express-prisma/variants/base/src/index.ts +8 -0
  68. package/registry/combos/express-prisma/variants/base/src/middleware/authz.middleware.ts +2 -2
  69. package/registry/combos/express-prisma/variants/base/src/openapi/openapi-admin.ts +1102 -0
  70. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +1 -1
  71. package/registry/combos/express-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
  72. package/registry/combos/express-prisma/variants/base/src/repositories/rbac.repository.ts +4 -4
  73. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +18 -22
  74. package/registry/combos/express-prisma/variants/base/src/seed.ts +1 -1
  75. package/registry/combos/express-prisma/variants/base/src/services/auth.service.ts +6 -8
  76. package/registry/combos/express-prisma/variants/base/test/variant-hooks.ts +176 -47
  77. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +6 -6
  78. package/registry/combos/express-prisma/variants/workspaces/src/index.ts +8 -0
  79. package/registry/combos/express-prisma/variants/workspaces/src/middleware/authz.middleware.ts +3 -3
  80. package/registry/combos/express-prisma/variants/workspaces/src/openapi/openapi-admin.ts +1218 -0
  81. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +1 -1
  82. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +1 -1
  83. package/registry/combos/express-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  84. package/registry/combos/express-prisma/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
  85. package/registry/combos/express-prisma/variants/workspaces/src/repositories/workspace.repository.ts +2 -2
  86. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +18 -22
  87. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +1 -1
  88. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +1 -1
  89. package/registry/combos/express-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
  90. package/registry/combos/express-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
  91. package/registry/combos/nestjs-drizzle/shared/src/{guards → ability}/ability.guard.ts +1 -1
  92. package/registry/combos/nestjs-drizzle/shared/src/{ability.ts → ability/ability.ts} +8 -2
  93. package/registry/combos/nestjs-drizzle/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
  94. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/cache}/rate-limit.store.ts +4 -1
  95. package/registry/combos/nestjs-drizzle/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
  96. package/registry/combos/nestjs-drizzle/shared/src/{db.ts → config/db.ts} +1 -1
  97. package/registry/combos/nestjs-drizzle/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  98. package/registry/combos/nestjs-drizzle/shared/src/controllers/auth.controller.ts +1 -1
  99. package/registry/combos/nestjs-drizzle/shared/src/guards/auth.guard.ts +1 -1
  100. package/registry/combos/nestjs-drizzle/shared/src/{id.helper.ts → helpers/id.helper.ts} +6 -2
  101. package/registry/combos/{express-prisma/shared/src → nestjs-drizzle/shared/src/helpers}/pagination.ts +17 -3
  102. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +2 -2
  103. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +8 -12
  104. package/registry/combos/nestjs-drizzle/shared/src/repositories/session.repository.ts +3 -3
  105. package/registry/combos/nestjs-drizzle/shared/src/repositories/two-factor.repository.ts +1 -1
  106. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  107. package/registry/combos/nestjs-drizzle/test/bootstrap.ts +1 -1
  108. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1 -1
  109. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +10 -6
  110. package/registry/combos/nestjs-drizzle/variants/base/src/controllers/admin.controller.ts +1 -1
  111. package/registry/combos/nestjs-drizzle/variants/base/src/guards/authz.guard.ts +2 -2
  112. package/registry/combos/nestjs-drizzle/variants/base/src/index.ts +9 -0
  113. package/registry/combos/nestjs-drizzle/variants/base/src/rbac.defaults.ts +58 -16
  114. package/registry/combos/nestjs-drizzle/variants/base/src/repositories/audit-log.repository.ts +3 -3
  115. package/registry/combos/nestjs-drizzle/variants/base/src/repositories/rbac.repository.ts +4 -4
  116. package/registry/combos/nestjs-drizzle/variants/base/src/seed.ts +76 -17
  117. package/registry/combos/nestjs-drizzle/variants/base/src/services/auth.service.ts +7 -9
  118. package/registry/combos/nestjs-drizzle/variants/base/test/variant-hooks.ts +167 -45
  119. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +10 -6
  120. package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/admin.controller.ts +1 -1
  121. package/registry/combos/nestjs-drizzle/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
  122. package/registry/combos/nestjs-drizzle/variants/workspaces/src/guards/authz.guard.ts +2 -2
  123. package/registry/combos/nestjs-drizzle/variants/workspaces/src/index.ts +9 -0
  124. package/registry/combos/nestjs-drizzle/variants/workspaces/src/rbac.defaults.ts +64 -18
  125. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/audit-log.repository.ts +3 -3
  126. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/rbac.repository.ts +4 -4
  127. package/registry/combos/nestjs-drizzle/variants/workspaces/src/repositories/workspace.repository.ts +8 -10
  128. package/registry/combos/nestjs-drizzle/variants/workspaces/src/seed.ts +171 -36
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +7 -9
  130. package/registry/combos/nestjs-drizzle/variants/workspaces/test/variant-hooks.ts +539 -133
  131. package/registry/combos/nestjs-prisma/shared/src/{guards → ability}/ability.guard.ts +1 -1
  132. package/registry/combos/nestjs-prisma/shared/src/{ability.ts → ability/ability.ts} +8 -2
  133. package/registry/combos/nestjs-prisma/shared/src/{permission-cache.ts → cache/permission-cache.ts} +27 -10
  134. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/cache}/rate-limit.store.ts +4 -1
  135. package/registry/combos/nestjs-prisma/shared/src/{auth.config.ts → config/auth.config.ts} +1 -1
  136. package/registry/combos/nestjs-prisma/shared/src/{key-provider.ts → config/key-provider.ts} +6 -2
  137. package/registry/combos/nestjs-prisma/shared/src/controllers/auth.controller.ts +1 -1
  138. package/registry/combos/nestjs-prisma/shared/src/guards/auth.guard.ts +1 -1
  139. package/registry/combos/{express-prisma/shared/src → nestjs-prisma/shared/src/helpers}/id.helper.ts +6 -2
  140. package/registry/combos/nestjs-prisma/shared/src/{pagination.ts → helpers/pagination.ts} +17 -3
  141. package/registry/combos/nestjs-prisma/shared/src/repositories/oauth.repository.ts +1 -1
  142. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +2 -4
  143. package/registry/combos/nestjs-prisma/shared/src/repositories/session.repository.ts +2 -2
  144. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  145. package/registry/combos/nestjs-prisma/test/bootstrap.ts +1 -1
  146. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1 -1
  147. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +10 -6
  148. package/registry/combos/nestjs-prisma/variants/base/src/controllers/admin.controller.ts +1 -1
  149. package/registry/combos/nestjs-prisma/variants/base/src/gateways/audit-log.gateway.ts +1 -1
  150. package/registry/combos/nestjs-prisma/variants/base/src/guards/authz.guard.ts +2 -2
  151. package/registry/combos/nestjs-prisma/variants/base/src/index.ts +9 -0
  152. package/registry/combos/nestjs-prisma/variants/base/src/{docs.ts → openapi/docs.ts} +23 -6
  153. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +1 -1
  154. package/registry/combos/nestjs-prisma/variants/base/src/repositories/audit-log.repository.ts +2 -2
  155. package/registry/combos/nestjs-prisma/variants/base/src/repositories/country.repository.ts +2 -2
  156. package/registry/combos/nestjs-prisma/variants/base/src/repositories/customer.repository.ts +6 -5
  157. package/registry/combos/nestjs-prisma/variants/base/src/repositories/language.repository.ts +2 -2
  158. package/registry/combos/nestjs-prisma/variants/base/src/repositories/rbac.repository.ts +3 -3
  159. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +1 -1
  160. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +6 -8
  161. package/registry/combos/nestjs-prisma/variants/base/test/variant-hooks.ts +176 -47
  162. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +10 -6
  163. package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/admin.controller.ts +1 -1
  164. package/registry/combos/nestjs-prisma/variants/workspaces/src/controllers/workspace.controller.ts +1 -1
  165. package/registry/combos/nestjs-prisma/variants/workspaces/src/guards/authz.guard.ts +2 -2
  166. package/registry/combos/nestjs-prisma/variants/workspaces/src/index.ts +9 -0
  167. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +1 -1
  168. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/audit-log.repository.ts +2 -2
  169. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/country.repository.ts +2 -2
  170. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/customer.repository.ts +6 -5
  171. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +2 -2
  172. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/rbac.repository.ts +3 -3
  173. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/workspace.repository.ts +1 -1
  174. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +1 -1
  175. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +6 -8
  176. package/registry/combos/nestjs-prisma/variants/workspaces/test/variant-hooks.ts +538 -134
  177. package/registry/combos/express-drizzle/variants/base/src/openapi-admin.ts +0 -565
  178. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-admin.ts +0 -596
  179. package/registry/combos/express-prisma/variants/base/src/openapi-admin.ts +0 -569
  180. package/registry/combos/express-prisma/variants/workspaces/src/openapi-admin.ts +0 -601
  181. /package/registry/combos/express-drizzle/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
  182. /package/registry/combos/express-prisma/shared/src/{http-error.ts → errors/http-error.ts} +0 -0
@@ -1,596 +0,0 @@
1
- // The administration half of the OpenAPI document, merged into the assembled spec by
2
- // openapi-spec.ts. Mirrors the reference combo's `dto/admin.dto.ts`: the identity endpoints are
3
- // documented once, in the shared half, and everything administration adds lives here — plus the
4
- // workspace-membership routes from ./openapi-workspace.js, which exist only alongside it.
5
- //
6
- // Administration is scoped to one workspace, so every path below takes the `X-Workspace-Id`
7
- // header: an admin here is an admin of the workspace they name and of nothing else.
8
- //
9
- // Every path is gated on exactly one key from `rbac.defaults.ts` and there is no role-based
10
- // bypass, which is why the 403s say which permission was missing rather than "admin role
11
- // required". A 403 is also the answer when the header names a workspace the caller does not
12
- // belong to, or none at all — deliberately the same answer, so the status cannot be used to
13
- // probe which workspaces exist.
14
- import { errorResponse, missingPermission, requiresPermission, type OpenApiFragment } from "./openapi-fragment.js";
15
- import { workspaceHeaderParameter, workspaceSpec } from "./openapi-workspace.js";
16
-
17
- const adminHeaderParameter = {
18
- ...workspaceHeaderParameter,
19
- description: "The workspace this request administers. The caller must be a member of it holding the permission the route names.",
20
- };
21
-
22
- export const adminSpec: OpenApiFragment = {
23
- tags: workspaceSpec.tags,
24
-
25
- scopeParameters: workspaceSpec.scopeParameters,
26
-
27
- schemas: {
28
- PermissionSummary: {
29
- type: "object",
30
- properties: {
31
- id: { type: "string" },
32
- slug: {
33
- type: "string",
34
- description: "The ability itself: `ability(slug)` on the server, `ability.can(slug, ABILITY_SUBJECT)` in a client.",
35
- example: "users:read",
36
- },
37
- displayName: { type: "string", example: "List users" },
38
- description: { type: "string", nullable: true },
39
- group: { type: "string", description: "Console grouping — a permission matrix renders one section per group.", example: "Users" },
40
- groupOrder: { type: "integer" },
41
- order: { type: "integer" },
42
- isActive: {
43
- type: "boolean",
44
- description: "false takes the permission out of every ability that would otherwise carry it, without unpicking a single grant.",
45
- },
46
- },
47
- },
48
- PermissionListResponse: {
49
- type: "object",
50
- properties: { permissions: { type: "array", items: { $ref: "#/components/schemas/PermissionSummary" } } },
51
- },
52
- DefinePermissionRequest: {
53
- type: "object",
54
- required: ["slug"],
55
- properties: {
56
- slug: { type: "string", example: "billing:manage" },
57
- displayName: { type: "string", description: "Defaults to the slug when creating." },
58
- description: { type: "string", nullable: true },
59
- group: { type: "string", description: 'Defaults to "Custom" when creating.' },
60
- groupOrder: { type: "integer" },
61
- order: { type: "integer" },
62
- isActive: { type: "boolean" },
63
- },
64
- },
65
- RoleListResponse: {
66
- type: "object",
67
- properties: { roles: { type: "array", items: { $ref: "#/components/schemas/RoleSummary" } } },
68
- },
69
- CreateRoleRequest: {
70
- type: "object",
71
- required: ["slug"],
72
- properties: {
73
- slug: { type: "string", description: "Stable identifier. Grants and assignments are keyed on it.", example: "billing-manager" },
74
- displayName: { type: "string", description: "Human label for the console. Defaults to the slug.", example: "Billing manager" },
75
- description: { type: "string", nullable: true },
76
- },
77
- },
78
- AttachPermissionRequest: {
79
- type: "object",
80
- required: ["permission"],
81
- properties: {
82
- permission: { type: "string", example: "billing:manage" },
83
- },
84
- },
85
- AssignRoleRequest: {
86
- type: "object",
87
- required: ["role"],
88
- properties: {
89
- role: { type: "string", description: "Role slug", example: "billing-manager" },
90
- },
91
- },
92
- GrantPermissionRequest: {
93
- type: "object",
94
- required: ["permission"],
95
- properties: {
96
- permission: { type: "string", example: "billing:manage" },
97
- },
98
- },
99
- CreateUserRequest: {
100
- type: "object",
101
- required: ["email", "password"],
102
- description: "No invitation email — the account is usable immediately with the password given here, and it is added to this workspace in the same step.",
103
- properties: {
104
- email: { type: "string", example: "alice@example.com" },
105
- password: { type: "string", description: "Set directly — there is no invitation email, the account is usable immediately." },
106
- firstName: { type: "string" },
107
- lastName: { type: "string" },
108
- displayName: { type: "string" },
109
- phone: { type: "string" },
110
- username: { type: "string" },
111
- roles: { type: "array", items: { type: "string" }, description: "Role slugs to assign in this workspace. Defaults to whichever roles are flagged isDefault, same as addMember." },
112
- },
113
- },
114
- UpdateUserRequest: {
115
- type: "object",
116
- description: "Profile fields only — email is the login identifier and is not editable here. Any field may be sent as `null` to clear it.",
117
- properties: {
118
- firstName: { type: "string", nullable: true },
119
- lastName: { type: "string", nullable: true },
120
- displayName: { type: "string", nullable: true },
121
- phone: { type: "string", nullable: true },
122
- username: { type: "string", nullable: true },
123
- photo: { type: "string", nullable: true },
124
- },
125
- },
126
- UpdateRoleRequest: {
127
- type: "object",
128
- properties: {
129
- name: { type: "string" },
130
- displayName: { type: "string" },
131
- description: { type: "string", nullable: true },
132
- isActive: { type: "boolean", description: "false suspends the role without deleting it; every assignment pointing at it survives." },
133
- },
134
- },
135
- DeleteReasonRequest: {
136
- type: "object",
137
- properties: {
138
- reason: { type: "string", description: "Free-text note recorded alongside the soft-delete, for whoever reads the row later." },
139
- },
140
- },
141
- RoleSummary: {
142
- type: "object",
143
- required: ["id", "slug", "displayName", "isDefault", "isActive"],
144
- properties: {
145
- id: { type: "string" },
146
- slug: { type: "string", example: "billing-manager" },
147
- displayName: { type: "string", example: "Billing manager" },
148
- isDefault: { type: "boolean", description: "Given to every new principal. The signup/membership default is this row, not a name spelled in code." },
149
- isActive: { type: "boolean", description: "false suspends the role without deleting it; every assignment pointing at it survives." },
150
- },
151
- },
152
- MemberSummary: {
153
- type: "object",
154
- required: ["memberId", "userId", "uuid", "email", "blocked", "isActive", "twoFactorEnabled", "roles", "createdBy", "updatedBy", "createdAt", "updatedAt"],
155
- properties: {
156
- memberId: { type: "string" },
157
- userId: { type: "string" },
158
- uuid: { type: "string" },
159
- email: { type: "string" },
160
- firstName: { type: "string", nullable: true },
161
- lastName: { type: "string", nullable: true },
162
- displayName: { type: "string", nullable: true },
163
- phone: { type: "string", nullable: true },
164
- username: { type: "string", nullable: true },
165
- photo: { type: "string", nullable: true },
166
- lastLogin: { type: "string", format: "date-time", nullable: true },
167
- blocked: { type: "boolean", description: "Security/moderation block — distinct from isActive, see the model note." },
168
- isActive: { type: "boolean", description: "Routine administrative on/off toggle — distinct from blocked, see the model note." },
169
- twoFactorEnabled: { type: "boolean" },
170
- roles: { type: "array", items: { type: "string" }, description: "This member's roles in this workspace" },
171
- createdBy: { type: "string", nullable: true, description: "User id of whoever created this account, if it wasn't a self-signup." },
172
- updatedBy: { type: "string", nullable: true, description: "User id of whoever last edited this account's profile." },
173
- createdAt: { type: "string", format: "date-time", description: "When this membership was created" },
174
- updatedAt: { type: "string", format: "date-time", description: "When the underlying user account was last updated" },
175
- },
176
- },
177
- PageMeta: {
178
- type: "object",
179
- required: ["page", "limit", "total", "pageCount", "hasPreviousPage", "hasNextPage"],
180
- properties: {
181
- page: { type: "integer", description: "1-indexed" },
182
- limit: { type: "integer" },
183
- total: { type: "integer" },
184
- pageCount: { type: "integer" },
185
- hasPreviousPage: { type: "boolean" },
186
- hasNextPage: { type: "boolean" },
187
- },
188
- },
189
- UserListResponse: {
190
- type: "object",
191
- required: ["items", "meta"],
192
- properties: {
193
- items: { type: "array", items: { $ref: "#/components/schemas/MemberSummary" } },
194
- meta: { $ref: "#/components/schemas/PageMeta" },
195
- },
196
- },
197
- AuditLogEntry: {
198
- type: "object",
199
- required: ["id", "workspaceId", "userId", "name", "action", "info", "remarks", "createdAt", "updatedAt"],
200
- properties: {
201
- id: { type: "string" },
202
- workspaceId: { type: "string", nullable: true, description: "Null for events that happen outside any workspace (login, password reset, ...)" },
203
- userId: { type: "string", nullable: true, description: "Who the event is about; null when it is not attributable to one user" },
204
- name: { type: "string", description: "Human-readable label for `action`", example: "Role assigned" },
205
- action: { type: "string", description: "AuditEvent discriminant", example: "role_assigned" },
206
- info: { type: "object", description: "The rest of the event's fields" },
207
- remarks: { type: "string", nullable: true },
208
- createdAt: { type: "string", format: "date-time" },
209
- updatedAt: { type: "string", format: "date-time" },
210
- },
211
- },
212
- AuditLogListResponse: {
213
- type: "object",
214
- required: ["items", "meta"],
215
- properties: {
216
- items: { type: "array", items: { $ref: "#/components/schemas/AuditLogEntry" } },
217
- meta: { $ref: "#/components/schemas/PageMeta" },
218
- },
219
- },
220
-
221
- ...workspaceSpec.schemas,
222
- },
223
-
224
- paths: {
225
- "/auth/admin/users": {
226
- get: {
227
- tags: ["auth"],
228
- summary: "[admin] List the members of this workspace",
229
- description: requiresPermission("users:read"),
230
- security: [{ bearerAuth: [] }],
231
- parameters: [
232
- adminHeaderParameter,
233
- { name: "search", in: "query", required: false, schema: { type: "string" }, description: "Email substring match" },
234
- { name: "page", in: "query", required: false, schema: { type: "integer" }, description: "1-indexed. Defaults to 1." },
235
- { name: "limit", in: "query", required: false, schema: { type: "integer" }, description: "Defaults to 25, capped at 100." },
236
- ],
237
- responses: {
238
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/UserListResponse" } } } },
239
- "401": errorResponse("Missing or invalid access token"),
240
- "403": missingPermission("users:read", "the request named no workspace you belong to"),
241
- },
242
- },
243
- post: {
244
- tags: ["auth"],
245
- summary: "[admin] Create a user and add them to this workspace",
246
- description: requiresPermission("users:manage") + " No invitation email — the account is usable immediately with the password given here.",
247
- security: [{ bearerAuth: [] }],
248
- parameters: [adminHeaderParameter],
249
- requestBody: {
250
- required: true,
251
- content: { "application/json": { schema: { $ref: "#/components/schemas/CreateUserRequest" } } },
252
- },
253
- responses: {
254
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/MemberSummary" } } } },
255
- "401": errorResponse("Missing or invalid access token"),
256
- "403": missingPermission("users:manage", "the request named no workspace you belong to"),
257
- "409": errorResponse("Email already registered"),
258
- },
259
- },
260
- },
261
- "/auth/admin/users/{userId}": {
262
- get: {
263
- tags: ["auth"],
264
- summary: "[admin] Fetch a single member's profile",
265
- description: requiresPermission("users:read"),
266
- security: [{ bearerAuth: [] }],
267
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
268
- responses: {
269
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/MemberSummary" } } } },
270
- "401": errorResponse("Missing or invalid access token"),
271
- "403": missingPermission("users:read", "the request named no workspace you belong to"),
272
- "404": errorResponse("The user is not a member of this workspace"),
273
- },
274
- },
275
- patch: {
276
- tags: ["auth"],
277
- summary: "[admin] Edit a member's profile",
278
- description: requiresPermission("users:manage") + " Profile fields only — email is the login identifier and is not editable here.",
279
- security: [{ bearerAuth: [] }],
280
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
281
- requestBody: {
282
- required: true,
283
- content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateUserRequest" } } },
284
- },
285
- responses: {
286
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/MemberSummary" } } } },
287
- "401": errorResponse("Missing or invalid access token"),
288
- "403": missingPermission("users:manage", "the request named no workspace you belong to"),
289
- "404": errorResponse("The user is not a member of this workspace"),
290
- },
291
- },
292
- delete: {
293
- tags: ["auth"],
294
- summary: "[admin] Delete a member's account",
295
- description:
296
- requiresPermission("users:manage") +
297
- " Soft-delete: the row survives for audit purposes, stops appearing in listings, and can no longer authenticate. " +
298
- "This disables the account across every workspace it belongs to, not just this one — the same reach `block` already has.",
299
- security: [{ bearerAuth: [] }],
300
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
301
- requestBody: {
302
- required: false,
303
- content: { "application/json": { schema: { $ref: "#/components/schemas/DeleteReasonRequest" } } },
304
- },
305
- responses: {
306
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
307
- "401": errorResponse("Missing or invalid access token"),
308
- "403": missingPermission("users:manage", "cannot delete your own account", "the request named no workspace you belong to"),
309
- "404": errorResponse("The user is not a member of this workspace"),
310
- },
311
- },
312
- },
313
- "/auth/admin/audit-log": {
314
- get: {
315
- tags: ["auth"],
316
- summary: "[admin] List this workspace's audit log entries, newest first",
317
- description: requiresPermission("audit-log:read"),
318
- security: [{ bearerAuth: [] }],
319
- parameters: [
320
- adminHeaderParameter,
321
- { name: "userId", in: "query", required: false, schema: { type: "string" } },
322
- { name: "action", in: "query", required: false, schema: { type: "string" }, description: "AuditEvent discriminant, e.g. 'role_assigned'" },
323
- { name: "since", in: "query", required: false, schema: { type: "string", format: "date-time" } },
324
- { name: "until", in: "query", required: false, schema: { type: "string", format: "date-time" } },
325
- { name: "page", in: "query", required: false, schema: { type: "integer" }, description: "1-indexed. Defaults to 1." },
326
- { name: "limit", in: "query", required: false, schema: { type: "integer" }, description: "Defaults to 25, capped at 100." },
327
- ],
328
- responses: {
329
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/AuditLogListResponse" } } } },
330
- "401": errorResponse("Missing or invalid access token"),
331
- "403": missingPermission("audit-log:read", "the request named no workspace you belong to"),
332
- },
333
- },
334
- },
335
- "/auth/admin/permissions": {
336
- get: {
337
- tags: ["auth"],
338
- summary: "[admin] List the permission catalog",
339
- description:
340
- requiresPermission("permissions:read") +
341
- " Grouped and ordered for a permission matrix. This is the whole vocabulary of the deployment — there is nothing about authorization outside these rows.",
342
- security: [{ bearerAuth: [] }],
343
- responses: {
344
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/PermissionListResponse" } } } },
345
- "401": errorResponse("Missing or invalid access token"),
346
- "403": missingPermission("permissions:read"),
347
- },
348
- },
349
- post: {
350
- tags: ["auth"],
351
- summary: "[admin] Define or edit a permission",
352
- description:
353
- requiresPermission("permissions:define") +
354
- " Upserted on `slug`, which is the stable identifier grants and revocations use — renaming the display name never breaks a grant." +
355
- " `isActive: false` takes the permission out of every ability that carries it, in one write, effective on the next request.",
356
- security: [{ bearerAuth: [] }],
357
- requestBody: {
358
- required: true,
359
- content: { "application/json": { schema: { $ref: "#/components/schemas/DefinePermissionRequest" } } },
360
- },
361
- responses: {
362
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/PermissionSummary" } } } },
363
- "401": errorResponse("Missing or invalid access token"),
364
- "403": missingPermission("permissions:define"),
365
- },
366
- },
367
- },
368
- "/auth/admin/roles": {
369
- get: {
370
- tags: ["auth"],
371
- summary: "[admin] List the roles this deployment defines",
372
- description: requiresPermission("roles:manage"),
373
- security: [{ bearerAuth: [] }],
374
- responses: {
375
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/RoleListResponse" } } } },
376
- "401": errorResponse("Missing or invalid access token"),
377
- "403": missingPermission("roles:manage"),
378
- },
379
- },
380
- post: {
381
- tags: ["auth"],
382
- summary: "[admin] Create a role in this workspace",
383
- description: requiresPermission("roles:manage"),
384
- security: [{ bearerAuth: [] }],
385
- parameters: [adminHeaderParameter],
386
- requestBody: {
387
- required: true,
388
- content: { "application/json": { schema: { $ref: "#/components/schemas/CreateRoleRequest" } } },
389
- },
390
- responses: {
391
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/RoleSummary" } } } },
392
- "401": errorResponse("Missing or invalid access token"),
393
- "403": missingPermission("roles:manage", "the request named no workspace you belong to"),
394
- },
395
- },
396
- },
397
- "/auth/admin/roles/{roleId}": {
398
- patch: {
399
- tags: ["auth"],
400
- summary: "[admin] Edit one of this workspace's roles",
401
- description: requiresPermission("roles:manage"),
402
- security: [{ bearerAuth: [] }],
403
- parameters: [adminHeaderParameter, { name: "roleId", in: "path", required: true, schema: { type: "string" } }],
404
- requestBody: {
405
- required: true,
406
- content: { "application/json": { schema: { $ref: "#/components/schemas/UpdateRoleRequest" } } },
407
- },
408
- responses: {
409
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/RoleSummary" } } } },
410
- "401": errorResponse("Missing or invalid access token"),
411
- "403": missingPermission("roles:manage", "the request named no workspace you belong to"),
412
- "404": errorResponse("Role not found in this workspace"),
413
- },
414
- },
415
- delete: {
416
- tags: ["auth"],
417
- summary: "[admin] Delete one of this workspace's roles",
418
- description:
419
- requiresPermission("roles:manage") + " Soft-delete: existing assignments are left in place rather than cascade-deleted, and the role simply stops being resolved.",
420
- security: [{ bearerAuth: [] }],
421
- parameters: [adminHeaderParameter, { name: "roleId", in: "path", required: true, schema: { type: "string" } }],
422
- requestBody: {
423
- required: false,
424
- content: { "application/json": { schema: { $ref: "#/components/schemas/DeleteReasonRequest" } } },
425
- },
426
- responses: {
427
- "200": { description: "OK", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
428
- "401": errorResponse("Missing or invalid access token"),
429
- "403": missingPermission("roles:manage", "the request named no workspace you belong to"),
430
- "404": errorResponse("Role not found in this workspace"),
431
- },
432
- },
433
- },
434
- "/auth/admin/roles/{roleId}/permissions": {
435
- post: {
436
- tags: ["auth"],
437
- summary: "[admin] Attach a permission to one of this workspace's roles",
438
- description: requiresPermission("roles:manage"),
439
- security: [{ bearerAuth: [] }],
440
- parameters: [adminHeaderParameter, { name: "roleId", in: "path", required: true, schema: { type: "string" } }],
441
- requestBody: {
442
- required: true,
443
- content: { "application/json": { schema: { $ref: "#/components/schemas/AttachPermissionRequest" } } },
444
- },
445
- responses: {
446
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
447
- "401": errorResponse("Missing or invalid access token"),
448
- "403": missingPermission("roles:manage", "the request named no workspace you belong to"),
449
- "404": errorResponse("Role not found in this workspace"),
450
- },
451
- },
452
- },
453
- "/auth/admin/users/{userId}/roles": {
454
- post: {
455
- tags: ["auth"],
456
- summary: "[admin] Assign a role to a member of this workspace",
457
- description: requiresPermission("roles:assign"),
458
- security: [{ bearerAuth: [] }],
459
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
460
- requestBody: {
461
- required: true,
462
- content: { "application/json": { schema: { $ref: "#/components/schemas/AssignRoleRequest" } } },
463
- },
464
- responses: {
465
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
466
- "401": errorResponse("Missing or invalid access token"),
467
- "403": missingPermission("roles:assign", "the request named no workspace you belong to"),
468
- "404": errorResponse("Role not defined in this workspace, or the user is not a member of it"),
469
- },
470
- },
471
- },
472
- "/auth/admin/users/{userId}/roles/{roleSlug}/revoke": {
473
- post: {
474
- tags: ["auth"],
475
- summary: "[admin] Revoke a role from a member of this workspace",
476
- description: requiresPermission("roles:assign"),
477
- security: [{ bearerAuth: [] }],
478
- parameters: [
479
- adminHeaderParameter,
480
- { name: "userId", in: "path", required: true, schema: { type: "string" } },
481
- { name: "roleSlug", in: "path", required: true, schema: { type: "string" } },
482
- ],
483
- responses: {
484
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
485
- "401": errorResponse("Missing or invalid access token"),
486
- "403": missingPermission("roles:assign", "cannot change your own roles", "the request named no workspace you belong to"),
487
- "404": errorResponse("The user is not a member of this workspace"),
488
- },
489
- },
490
- },
491
- "/auth/admin/users/{userId}/permissions": {
492
- post: {
493
- tags: ["auth"],
494
- summary: "[admin] Grant a permission directly to a member, bypassing roles",
495
- description: requiresPermission("permissions:grant") + " The grant is scoped to this workspace.",
496
- security: [{ bearerAuth: [] }],
497
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
498
- requestBody: {
499
- required: true,
500
- content: { "application/json": { schema: { $ref: "#/components/schemas/GrantPermissionRequest" } } },
501
- },
502
- responses: {
503
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
504
- "401": errorResponse("Missing or invalid access token"),
505
- "403": missingPermission("permissions:grant", "the request named no workspace you belong to"),
506
- "404": errorResponse("The user is not a member of this workspace"),
507
- },
508
- },
509
- },
510
- "/auth/admin/users/{userId}/permissions/{permissionSlug}/revoke": {
511
- post: {
512
- tags: ["auth"],
513
- summary: "[admin] Revoke a direct permission grant from a member",
514
- description: requiresPermission("permissions:grant"),
515
- security: [{ bearerAuth: [] }],
516
- parameters: [
517
- adminHeaderParameter,
518
- { name: "userId", in: "path", required: true, schema: { type: "string" } },
519
- { name: "permissionSlug", in: "path", required: true, schema: { type: "string" } },
520
- ],
521
- responses: {
522
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
523
- "401": errorResponse("Missing or invalid access token"),
524
- "403": missingPermission("permissions:grant", "the request named no workspace you belong to"),
525
- "404": errorResponse("The user is not a member of this workspace"),
526
- },
527
- },
528
- },
529
- "/auth/admin/users/{userId}/block": {
530
- post: {
531
- tags: ["auth"],
532
- summary: "[admin] Block a member, revoking all their sessions immediately",
533
- description:
534
- requiresPermission("users:block") +
535
- " Blocking disables the whole account, so it is only allowed against a member of the workspace you administer.",
536
- security: [{ bearerAuth: [] }],
537
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
538
- responses: {
539
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
540
- "401": errorResponse("Missing or invalid access token"),
541
- "403": missingPermission("users:block", "cannot block your own account", "the request named no workspace you belong to"),
542
- "404": errorResponse("The user is not a member of this workspace"),
543
- },
544
- },
545
- },
546
- "/auth/admin/users/{userId}/unblock": {
547
- post: {
548
- tags: ["auth"],
549
- summary: "[admin] Unblock a member",
550
- description: requiresPermission("users:block"),
551
- security: [{ bearerAuth: [] }],
552
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
553
- responses: {
554
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
555
- "401": errorResponse("Missing or invalid access token"),
556
- "403": missingPermission("users:block", "the request named no workspace you belong to"),
557
- "404": errorResponse("The user is not a member of this workspace"),
558
- },
559
- },
560
- },
561
- "/auth/admin/users/{userId}/deactivate": {
562
- post: {
563
- tags: ["auth"],
564
- summary: "[admin] Deactivate a member, revoking all their sessions immediately",
565
- description:
566
- requiresPermission("users:block") +
567
- " Distinct from block/unblock — a routine administrative toggle, not a security action. Both independently deny login.",
568
- security: [{ bearerAuth: [] }],
569
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
570
- responses: {
571
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
572
- "401": errorResponse("Missing or invalid access token"),
573
- "403": missingPermission("users:block", "cannot deactivate your own account", "the request named no workspace you belong to"),
574
- "404": errorResponse("The user is not a member of this workspace"),
575
- },
576
- },
577
- },
578
- "/auth/admin/users/{userId}/activate": {
579
- post: {
580
- tags: ["auth"],
581
- summary: "[admin] Reactivate a member",
582
- description: requiresPermission("users:block"),
583
- security: [{ bearerAuth: [] }],
584
- parameters: [adminHeaderParameter, { name: "userId", in: "path", required: true, schema: { type: "string" } }],
585
- responses: {
586
- "201": { description: "Created", content: { "application/json": { schema: { $ref: "#/components/schemas/OkResponse" } } } },
587
- "401": errorResponse("Missing or invalid access token"),
588
- "403": missingPermission("users:block", "the request named no workspace you belong to"),
589
- "404": errorResponse("The user is not a member of this workspace"),
590
- },
591
- },
592
- },
593
-
594
- ...workspaceSpec.paths,
595
- },
596
- };