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