@simple-auth-kit/cli 1.4.3 → 1.5.0

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 (133) hide show
  1. package/package.json +1 -1
  2. package/registry/combos/express-drizzle/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  3. package/registry/combos/express-drizzle/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +23 -7
  4. package/registry/combos/{express-prisma/shared/src → express-drizzle/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  5. package/registry/combos/express-drizzle/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +32 -9
  6. package/registry/combos/express-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  7. package/registry/combos/express-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  8. package/registry/combos/express-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +20 -6
  9. package/registry/combos/express-drizzle/shared/src/request-context.ts +1 -1
  10. package/registry/combos/express-drizzle/shared/src/routers/auth.router.ts +395 -0
  11. package/registry/combos/express-drizzle/variants/base/src/create-auth-app.ts +52 -19
  12. package/registry/combos/express-drizzle/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  13. package/registry/combos/express-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +28 -10
  14. package/registry/combos/express-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +239 -48
  15. package/registry/combos/express-drizzle/variants/base/src/routers/admin.router.ts +497 -0
  16. package/registry/combos/express-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +480 -109
  17. package/registry/combos/express-drizzle/variants/workspaces/src/create-auth-app.ts +69 -23
  18. package/registry/combos/express-drizzle/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  19. package/registry/combos/express-drizzle/variants/workspaces/src/openapi-workspace.ts +147 -28
  20. package/registry/combos/express-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +30 -11
  21. package/registry/combos/express-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +319 -70
  22. package/registry/combos/express-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +127 -30
  23. package/registry/combos/express-drizzle/variants/workspaces/src/routers/admin.router.ts +534 -0
  24. package/registry/combos/express-drizzle/variants/workspaces/src/routers/workspace.router.ts +194 -0
  25. package/registry/combos/express-drizzle/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +544 -122
  26. package/registry/combos/express-prisma/shared/src/{auth-core-error.middleware.ts → middleware/auth-core-error.middleware.ts} +17 -4
  27. package/registry/combos/express-prisma/shared/src/{auth.middleware.ts → middleware/auth.middleware.ts} +19 -6
  28. package/registry/combos/{express-drizzle/shared/src → express-prisma/shared/src/middleware}/response-envelope.middleware.ts +15 -3
  29. package/registry/combos/express-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +27 -9
  30. package/registry/combos/express-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  31. package/registry/combos/express-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  32. package/registry/combos/express-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  33. package/registry/combos/express-prisma/shared/src/request-context.ts +1 -1
  34. package/registry/combos/express-prisma/shared/src/routers/auth.router.ts +398 -0
  35. package/registry/combos/express-prisma/variants/base/src/create-auth-app.ts +57 -22
  36. package/registry/combos/express-prisma/variants/base/src/{authz.middleware.ts → middleware/authz.middleware.ts} +15 -7
  37. package/registry/combos/express-prisma/variants/base/src/rbac.defaults.ts +67 -19
  38. package/registry/combos/express-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  39. package/registry/combos/express-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +199 -48
  40. package/registry/combos/express-prisma/variants/base/src/routers/admin.router.ts +508 -0
  41. package/registry/combos/express-prisma/variants/base/src/seed.ts +78 -20
  42. package/registry/combos/express-prisma/variants/base/src/{auth.service.ts → services/auth.service.ts} +454 -105
  43. package/registry/combos/express-prisma/variants/workspaces/src/create-auth-app.ts +81 -26
  44. package/registry/combos/express-prisma/variants/workspaces/src/{authz.middleware.ts → middleware/authz.middleware.ts} +32 -12
  45. package/registry/combos/express-prisma/variants/workspaces/src/openapi-workspace.ts +147 -28
  46. package/registry/combos/express-prisma/variants/workspaces/src/rbac.defaults.ts +73 -21
  47. package/registry/combos/express-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  48. package/registry/combos/express-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +272 -64
  49. package/registry/combos/express-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +86 -23
  50. package/registry/combos/express-prisma/variants/workspaces/src/routers/admin.router.ts +545 -0
  51. package/registry/combos/express-prisma/variants/workspaces/src/routers/workspace.router.ts +194 -0
  52. package/registry/combos/express-prisma/variants/workspaces/src/seed.ts +109 -29
  53. package/registry/combos/express-prisma/variants/workspaces/src/{auth.service.ts → services/auth.service.ts} +518 -119
  54. package/registry/combos/nestjs-drizzle/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  55. package/registry/combos/nestjs-drizzle/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  56. package/registry/combos/nestjs-drizzle/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  57. package/registry/combos/nestjs-drizzle/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  58. package/registry/combos/{nestjs-prisma/shared/src → nestjs-drizzle/shared/src/interceptors}/response.interceptor.ts +10 -2
  59. package/registry/combos/nestjs-drizzle/shared/src/repositories/oauth.repository.ts +63 -0
  60. package/registry/combos/nestjs-drizzle/shared/src/repositories/password-reset.repository.ts +59 -0
  61. package/registry/combos/nestjs-drizzle/shared/src/{session.repository.ts → repositories/session.repository.ts} +52 -14
  62. package/registry/combos/nestjs-drizzle/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +21 -7
  63. package/registry/combos/nestjs-drizzle/shared/src/request-context.ts +1 -1
  64. package/registry/combos/nestjs-drizzle/test/prove-cycle.ts +1097 -307
  65. package/registry/combos/nestjs-drizzle/variants/base/src/auth.module.ts +44 -20
  66. package/registry/combos/nestjs-drizzle/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +228 -62
  67. package/registry/combos/nestjs-drizzle/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  68. package/registry/combos/nestjs-drizzle/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +27 -10
  69. package/registry/combos/nestjs-drizzle/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +249 -51
  70. package/registry/combos/nestjs-drizzle/variants/base/src/{auth.service.ts → services/auth.service.ts} +499 -116
  71. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.module.ts +48 -22
  72. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +269 -70
  73. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  74. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  75. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +29 -11
  76. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +323 -71
  77. package/registry/combos/nestjs-drizzle/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +131 -30
  78. package/registry/combos/nestjs-drizzle/variants/workspaces/src/services/auth.service.ts +988 -0
  79. package/registry/combos/nestjs-prisma/shared/src/{auth.controller.ts → controllers/auth.controller.ts} +6 -6
  80. package/registry/combos/nestjs-prisma/shared/src/{auth-core-error.filter.ts → filters/auth-core-error.filter.ts} +29 -5
  81. package/registry/combos/nestjs-prisma/shared/src/{ability.guard.ts → guards/ability.guard.ts} +20 -7
  82. package/registry/combos/nestjs-prisma/shared/src/{auth.guard.ts → guards/auth.guard.ts} +19 -7
  83. package/registry/combos/{nestjs-drizzle/shared/src → nestjs-prisma/shared/src/interceptors}/response.interceptor.ts +10 -2
  84. package/registry/combos/nestjs-prisma/shared/src/{oauth.repository.ts → repositories/oauth.repository.ts} +25 -8
  85. package/registry/combos/nestjs-prisma/shared/src/repositories/password-reset.repository.ts +55 -0
  86. package/registry/combos/nestjs-prisma/shared/src/{session.repository.ts → repositories/session.repository.ts} +30 -9
  87. package/registry/combos/nestjs-prisma/shared/src/{two-factor.repository.ts → repositories/two-factor.repository.ts} +11 -4
  88. package/registry/combos/nestjs-prisma/shared/src/request-context.ts +1 -1
  89. package/registry/combos/nestjs-prisma/test/prove-cycle.ts +1097 -307
  90. package/registry/combos/nestjs-prisma/variants/base/src/auth.module.ts +59 -28
  91. package/registry/combos/nestjs-prisma/variants/base/src/{admin.controller.ts → controllers/admin.controller.ts} +469 -110
  92. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.gateway.ts → gateways/audit-log.gateway.ts} +22 -6
  93. package/registry/combos/nestjs-prisma/variants/base/src/{authz.guard.ts → guards/authz.guard.ts} +12 -5
  94. package/registry/combos/nestjs-prisma/variants/base/src/rbac.defaults.ts +98 -27
  95. package/registry/combos/nestjs-prisma/variants/base/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  96. package/registry/combos/nestjs-prisma/variants/base/src/{country.repository.ts → repositories/country.repository.ts} +98 -25
  97. package/registry/combos/nestjs-prisma/variants/base/src/{customer.repository.ts → repositories/customer.repository.ts} +106 -27
  98. package/registry/combos/nestjs-prisma/variants/base/src/{language.repository.ts → repositories/language.repository.ts} +93 -25
  99. package/registry/combos/nestjs-prisma/variants/base/src/{rbac.repository.ts → repositories/rbac.repository.ts} +229 -55
  100. package/registry/combos/nestjs-prisma/variants/base/src/seed.ts +78 -20
  101. package/registry/combos/nestjs-prisma/variants/base/src/services/auth.service.ts +1064 -0
  102. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.module.ts +55 -27
  103. package/registry/combos/nestjs-prisma/variants/workspaces/src/{admin.controller.ts → controllers/admin.controller.ts} +544 -128
  104. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.controller.ts → controllers/workspace.controller.ts} +93 -26
  105. package/registry/combos/nestjs-prisma/variants/workspaces/src/{authz.guard.ts → guards/authz.guard.ts} +26 -8
  106. package/registry/combos/nestjs-prisma/variants/workspaces/src/rbac.defaults.ts +104 -29
  107. package/registry/combos/nestjs-prisma/variants/workspaces/src/{audit-log.repository.ts → repositories/audit-log.repository.ts} +16 -5
  108. package/registry/combos/nestjs-prisma/variants/workspaces/src/{country.repository.ts → repositories/country.repository.ts} +127 -26
  109. package/registry/combos/nestjs-prisma/variants/workspaces/src/{customer.repository.ts → repositories/customer.repository.ts} +137 -28
  110. package/registry/combos/nestjs-prisma/variants/workspaces/src/repositories/language.repository.ts +243 -0
  111. package/registry/combos/nestjs-prisma/variants/workspaces/src/{rbac.repository.ts → repositories/rbac.repository.ts} +314 -74
  112. package/registry/combos/nestjs-prisma/variants/workspaces/src/{workspace.repository.ts → repositories/workspace.repository.ts} +94 -24
  113. package/registry/combos/nestjs-prisma/variants/workspaces/src/seed.ts +109 -29
  114. package/registry/combos/nestjs-prisma/variants/workspaces/src/services/auth.service.ts +961 -0
  115. package/registry.json +4 -4
  116. package/simple-auth-kit.ts +29 -2
  117. package/registry/combos/express-drizzle/shared/src/auth.router.ts +0 -266
  118. package/registry/combos/express-drizzle/shared/src/password-reset.repository.ts +0 -35
  119. package/registry/combos/express-drizzle/variants/base/src/admin.router.ts +0 -307
  120. package/registry/combos/express-drizzle/variants/workspaces/src/admin.router.ts +0 -323
  121. package/registry/combos/express-drizzle/variants/workspaces/src/workspace.router.ts +0 -123
  122. package/registry/combos/express-prisma/shared/src/auth.router.ts +0 -269
  123. package/registry/combos/express-prisma/shared/src/password-reset.repository.ts +0 -29
  124. package/registry/combos/express-prisma/variants/base/src/admin.router.ts +0 -314
  125. package/registry/combos/express-prisma/variants/workspaces/src/admin.router.ts +0 -330
  126. package/registry/combos/express-prisma/variants/workspaces/src/workspace.router.ts +0 -123
  127. package/registry/combos/nestjs-drizzle/shared/src/oauth.repository.ts +0 -36
  128. package/registry/combos/nestjs-drizzle/shared/src/password-reset.repository.ts +0 -29
  129. package/registry/combos/nestjs-drizzle/variants/workspaces/src/auth.service.ts +0 -553
  130. package/registry/combos/nestjs-prisma/shared/src/password-reset.repository.ts +0 -29
  131. package/registry/combos/nestjs-prisma/variants/base/src/auth.service.ts +0 -618
  132. package/registry/combos/nestjs-prisma/variants/workspaces/src/auth.service.ts +0 -509
  133. package/registry/combos/nestjs-prisma/variants/workspaces/src/language.repository.ts +0 -143
@@ -1,29 +1,60 @@
1
- import { BadRequestException, Body, Controller, Delete, ForbiddenException, Get, Inject, Param, Post, Put, Req, UseGuards } from "@nestjs/common";
2
- import { ApiBearerAuth, ApiBody, ApiHeader, ApiOperation, ApiParam, ApiResponse, ApiTags } from "@nestjs/swagger";
1
+ import {
2
+ BadRequestException,
3
+ Body,
4
+ Controller,
5
+ Delete,
6
+ ForbiddenException,
7
+ Get,
8
+ Inject,
9
+ Param,
10
+ Post,
11
+ Put,
12
+ Req,
13
+ UseGuards,
14
+ } from "@nestjs/common";
15
+ import {
16
+ ApiBearerAuth,
17
+ ApiBody,
18
+ ApiHeader,
19
+ ApiOperation,
20
+ ApiParam,
21
+ ApiResponse,
22
+ ApiTags,
23
+ } from "@nestjs/swagger";
3
24
  import type { Request } from "express";
4
- import { AbilityGuard } from "./ability.guard.js";
5
- import { AuthGuard } from "./auth.guard.js";
6
- import { WORKSPACE_HEADER, WorkspaceGuard } from "./authz.guard.js";
7
- import { Authenticated, CheckAbility } from "./route-tiers.js";
8
- import { OkResponseDto } from "./dto/auth.dto.js";
9
- import { AddMemberDto, CreateWorkspaceDto, MemberRolesResponseDto, MembershipSummaryDto, SetMemberRolesDto, WorkspaceSummaryDto } from "./dto/workspace.dto.js";
10
- import { RbacRepository } from "./rbac.repository.js";
11
- import { WorkspaceRepository } from "./workspace.repository.js";
25
+ import { AbilityGuard } from "../guards/ability.guard.js";
26
+ import { AuthGuard } from "../guards/auth.guard.js";
27
+ import { WORKSPACE_HEADER, WorkspaceGuard } from "../guards/authz.guard.js";
28
+ import { Authenticated, CheckAbility } from "../route-tiers.js";
29
+ import { OkResponseDto } from "../dto/auth.dto.js";
30
+ import {
31
+ AddMemberDto,
32
+ CreateWorkspaceDto,
33
+ MemberRolesResponseDto,
34
+ MembershipSummaryDto,
35
+ SetMemberRolesDto,
36
+ WorkspaceSummaryDto,
37
+ } from "../dto/workspace.dto.js";
38
+ import { RbacRepository } from "../repositories/rbac.repository.js";
39
+ import { WorkspaceRepository } from "../repositories/workspace.repository.js";
12
40
 
13
41
  function requireString(value: unknown, field: string): string {
14
- if (typeof value !== "string" || value.length === 0) throw new BadRequestException(`${field} is required`);
42
+ if (typeof value !== "string" || value.length === 0)
43
+ throw new BadRequestException(`${field} is required`);
15
44
  return value;
16
45
  }
17
46
 
18
47
  function requireStringArray(value: unknown, field: string): string[] {
19
- if (!Array.isArray(value) || value.some((v) => typeof v !== "string")) throw new BadRequestException(`${field} must be an array of strings`);
48
+ if (!Array.isArray(value) || value.some((v) => typeof v !== "string"))
49
+ throw new BadRequestException(`${field} must be an array of strings`);
20
50
  return value as string[];
21
51
  }
22
52
 
23
53
  const WORKSPACE_HEADER_DOC = {
24
54
  name: WORKSPACE_HEADER,
25
55
  required: true,
26
- description: "The workspace this request acts in. The caller must be a member of it.",
56
+ description:
57
+ "The workspace this request acts in. The caller must be a member of it.",
27
58
  };
28
59
 
29
60
  /**
@@ -48,24 +79,34 @@ const WORKSPACE_HEADER_DOC = {
48
79
  @ApiBearerAuth()
49
80
  export class WorkspaceController {
50
81
  constructor(
51
- @Inject(WorkspaceRepository) private readonly workspaces: WorkspaceRepository,
82
+ @Inject(WorkspaceRepository)
83
+ private readonly workspaces: WorkspaceRepository,
52
84
  @Inject(RbacRepository) private readonly rbac: RbacRepository,
53
85
  ) {}
54
86
 
55
87
  @Post()
56
88
  @Authenticated()
57
89
  @UseGuards(AuthGuard)
58
- @ApiOperation({ summary: "Create a workspace", description: "The creator becomes its first member, with the admin role." })
90
+ @ApiOperation({
91
+ summary: "Create a workspace",
92
+ description: "The creator becomes its first member, with the admin role.",
93
+ })
59
94
  @ApiBody({ type: CreateWorkspaceDto })
60
95
  @ApiResponse({ status: 201, type: WorkspaceSummaryDto })
61
96
  async create(@Body() body: Record<string, unknown>, @Req() req: Request) {
62
- return this.workspaces.create(req.auth!.sub, requireString(body.name, "name"));
97
+ return this.workspaces.create(
98
+ req.auth!.sub,
99
+ requireString(body.name, "name"),
100
+ );
63
101
  }
64
102
 
65
103
  @Get()
66
104
  @Authenticated()
67
105
  @UseGuards(AuthGuard)
68
- @ApiOperation({ summary: "List the workspaces the current user belongs to, with their roles in each" })
106
+ @ApiOperation({
107
+ summary:
108
+ "List the workspaces the current user belongs to, with their roles in each",
109
+ })
69
110
  @ApiResponse({ status: 200, type: [WorkspaceSummaryDto] })
70
111
  async listMine(@Req() req: Request) {
71
112
  return this.workspaces.listForUser(req.auth!.sub);
@@ -85,38 +126,64 @@ export class WorkspaceController {
85
126
  @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
86
127
  @CheckAbility("members:manage")
87
128
  @ApiHeader(WORKSPACE_HEADER_DOC)
88
- @ApiOperation({ summary: "[admin] Add an existing user to the named workspace" })
129
+ @ApiOperation({
130
+ summary: "[admin] Add an existing user to the named workspace",
131
+ })
89
132
  @ApiBody({ type: AddMemberDto })
90
133
  @ApiResponse({ status: 201, type: MembershipSummaryDto })
91
134
  async addMember(@Body() body: Record<string, unknown>, @Req() req: Request) {
92
135
  // No `roles` in the body means "whatever this workspace flags as the default role" — the
93
136
  // default lives in a row, not in this line.
94
- const roles = body.roles === undefined ? undefined : requireStringArray(body.roles, "roles");
95
- return this.workspaces.addMember(req.authz!.workspaceId, requireString(body.email, "email"), roles);
137
+ const roles =
138
+ body.roles === undefined
139
+ ? undefined
140
+ : requireStringArray(body.roles, "roles");
141
+ return this.workspaces.addMember(
142
+ req.authz!.workspaceId,
143
+ requireString(body.email, "email"),
144
+ roles,
145
+ );
96
146
  }
97
147
 
98
148
  @Put("members/:memberId/roles")
99
149
  @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
100
150
  @CheckAbility("roles:assign")
101
151
  @ApiHeader(WORKSPACE_HEADER_DOC)
102
- @ApiOperation({ summary: "[admin] Replace a member's roles within the named workspace" })
152
+ @ApiOperation({
153
+ summary: "[admin] Replace a member's roles within the named workspace",
154
+ })
103
155
  @ApiParam({ name: "memberId" })
104
156
  @ApiBody({ type: SetMemberRolesDto })
105
157
  @ApiResponse({ status: 200, type: MemberRolesResponseDto })
106
- async setMemberRoles(@Param("memberId") memberId: string, @Body() body: Record<string, unknown>, @Req() req: Request) {
107
- if (memberId === req.authz!.memberId) throw new ForbiddenException("cannot change your own roles");
108
- return this.rbac.setMemberRoles(req.authz!.workspaceId, memberId, requireStringArray(body.roles, "roles"));
158
+ async setMemberRoles(
159
+ @Param("memberId") memberId: string,
160
+ @Body() body: Record<string, unknown>,
161
+ @Req() req: Request,
162
+ ) {
163
+ if (memberId === req.authz!.memberId)
164
+ throw new ForbiddenException("cannot change your own roles");
165
+ return this.rbac.setMemberRoles(
166
+ req.authz!.workspaceId,
167
+ memberId,
168
+ requireStringArray(body.roles, "roles"),
169
+ );
109
170
  }
110
171
 
111
172
  @Delete("members/:memberId")
112
173
  @UseGuards(AuthGuard, WorkspaceGuard, AbilityGuard)
113
174
  @CheckAbility("members:manage")
114
175
  @ApiHeader(WORKSPACE_HEADER_DOC)
115
- @ApiOperation({ summary: "[admin] Remove a member from the named workspace", description: "Their direct permission grants go with the membership." })
176
+ @ApiOperation({
177
+ summary: "[admin] Remove a member from the named workspace",
178
+ description: "Their direct permission grants go with the membership.",
179
+ })
116
180
  @ApiParam({ name: "memberId" })
117
181
  @ApiResponse({ status: 200, type: OkResponseDto })
118
182
  async removeMember(@Param("memberId") memberId: string, @Req() req: Request) {
119
- if (memberId === req.authz!.memberId) throw new ForbiddenException("cannot remove yourself from a workspace you administer");
183
+ if (memberId === req.authz!.memberId)
184
+ throw new ForbiddenException(
185
+ "cannot remove yourself from a workspace you administer",
186
+ );
120
187
  await this.workspaces.removeMember(req.authz!.workspaceId, memberId);
121
188
  return { ok: true };
122
189
  }
@@ -1,7 +1,16 @@
1
- import { CanActivate, ExecutionContext, ForbiddenException, Inject, Injectable } from "@nestjs/common";
2
- import { defineAbilitiesFor } from "./ability.js";
3
- import { PermissionCache } from "./permission-cache.js";
4
- import { memberCacheKey, RbacRepository } from "./rbac.repository.js";
1
+ import {
2
+ CanActivate,
3
+ ExecutionContext,
4
+ ForbiddenException,
5
+ Inject,
6
+ Injectable,
7
+ } from "@nestjs/common";
8
+ import { defineAbilitiesFor } from "../ability.js";
9
+ import { PermissionCache } from "../permission-cache.js";
10
+ import {
11
+ memberCacheKey,
12
+ RbacRepository,
13
+ } from "../repositories/rbac.repository.js";
5
14
 
6
15
  export const WORKSPACE_HEADER = "x-workspace-id";
7
16
 
@@ -34,15 +43,23 @@ export interface AuthzContext {
34
43
  * Idempotent: a route may legitimately sit behind both guards, and resolving twice would double
35
44
  * the hot path's query count for no gain.
36
45
  */
37
- async function resolve(rbac: RbacRepository, cache: PermissionCache, context: ExecutionContext): Promise<void> {
46
+ async function resolve(
47
+ rbac: RbacRepository,
48
+ cache: PermissionCache,
49
+ context: ExecutionContext,
50
+ ): Promise<void> {
38
51
  const req = context.switchToHttp().getRequest();
39
52
  if (req.authz) return;
40
53
 
41
54
  const workspaceId = req.headers[WORKSPACE_HEADER];
42
- if (!req.auth || typeof workspaceId !== "string" || workspaceId.length === 0) return;
55
+ if (!req.auth || typeof workspaceId !== "string" || workspaceId.length === 0)
56
+ return;
43
57
 
44
58
  const userId = req.auth.sub as string;
45
- const authz = await cache.resolve<AuthzContext>(memberCacheKey(userId, workspaceId), () => rbac.resolveAuthzContext(userId, workspaceId));
59
+ const authz = await cache.resolve<AuthzContext>(
60
+ memberCacheKey(userId, workspaceId),
61
+ () => rbac.resolveAuthzContext(userId, workspaceId),
62
+ );
46
63
  // Deliberately the same answer for "no such workspace" and "not your workspace": a caller
47
64
  // outside a workspace must not be able to probe whether it exists.
48
65
  if (!authz) throw new ForbiddenException("not a member of this workspace");
@@ -81,7 +98,8 @@ export class WorkspaceGuard implements CanActivate {
81
98
  async canActivate(context: ExecutionContext): Promise<boolean> {
82
99
  await resolve(this.rbac, this.cache, context);
83
100
  const req = context.switchToHttp().getRequest();
84
- if (!req.authz) throw new ForbiddenException(`${WORKSPACE_HEADER} header is required`);
101
+ if (!req.authz)
102
+ throw new ForbiddenException(`${WORKSPACE_HEADER} header is required`);
85
103
  return true;
86
104
  }
87
105
  }
@@ -1,10 +1,15 @@
1
1
  import { Inject, Injectable } from "@nestjs/common";
2
2
  import { and, count, desc, eq, gte, lte, type SQL } from "drizzle-orm";
3
3
  import type { AuditEvent } from "@/lib/auth/core/types.js";
4
- import { DRIZZLE_DB, type Database } from "./db.js";
5
- import { buildPageMeta, normalizeLimit, normalizePage, type Paginated } from "./pagination.js";
6
- import { auditLogs } from "./schema.js";
7
- import { toIdOrNull, toIdOrUndefined } from "./id.helper.js";
4
+ import { DRIZZLE_DB, type Database } from "../db.js";
5
+ import {
6
+ buildPageMeta,
7
+ normalizeLimit,
8
+ normalizePage,
9
+ type Paginated,
10
+ } from "../pagination.js";
11
+ import { auditLogs } from "../schema.js";
12
+ import { toIdOrNull, toIdOrUndefined } from "../id.helper.js";
8
13
 
9
14
  export interface AuditLogEntry {
10
15
  id: string;
@@ -50,7 +55,10 @@ export function toAuditLogEntry(row: AuditLogRow): AuditLogEntry {
50
55
  /** "role_assigned" -> "Role assigned". Derived rather than passed in, so core never has to carry display text. */
51
56
  export function humanizeAction(action: string): string {
52
57
  const words = action.split("_");
53
- return [words[0].charAt(0).toUpperCase() + words[0].slice(1), ...words.slice(1)].join(" ");
58
+ return [
59
+ words[0].charAt(0).toUpperCase() + words[0].slice(1),
60
+ ...words.slice(1),
61
+ ].join(" ");
54
62
  }
55
63
 
56
64
  @Injectable()
@@ -62,7 +70,10 @@ export class AuditLogRepository {
62
70
  * those happen to a user, not inside a workspace. Callers that *are* acting in a workspace
63
71
  * pass it, which is what scopes the admin audit-log read below.
64
72
  */
65
- async append(event: AuditEvent, opts: { workspaceId?: string; remarks?: string } = {}): Promise<void> {
73
+ async append(
74
+ event: AuditEvent,
75
+ opts: { workspaceId?: string; remarks?: string } = {},
76
+ ): Promise<void> {
66
77
  await this.db.insert(auditLogs).values({
67
78
  action: event.type,
68
79
  name: humanizeAction(event.type),
@@ -80,12 +91,16 @@ export class AuditLogRepository {
80
91
 
81
92
  const conditions: SQL[] = [];
82
93
  const workspaceIdBig = toIdOrUndefined(filter.workspaceId);
83
- if (workspaceIdBig !== undefined) conditions.push(eq(auditLogs.workspaceId, workspaceIdBig));
94
+ if (workspaceIdBig !== undefined)
95
+ conditions.push(eq(auditLogs.workspaceId, workspaceIdBig));
84
96
  const userIdBig = toIdOrUndefined(filter.userId);
85
- if (userIdBig !== undefined) conditions.push(eq(auditLogs.userId, userIdBig));
97
+ if (userIdBig !== undefined)
98
+ conditions.push(eq(auditLogs.userId, userIdBig));
86
99
  if (filter.action) conditions.push(eq(auditLogs.action, filter.action));
87
- if (filter.since) conditions.push(gte(auditLogs.createdAt, new Date(filter.since)));
88
- if (filter.until) conditions.push(lte(auditLogs.createdAt, new Date(filter.until)));
100
+ if (filter.since)
101
+ conditions.push(gte(auditLogs.createdAt, new Date(filter.since)));
102
+ if (filter.until)
103
+ conditions.push(lte(auditLogs.createdAt, new Date(filter.until)));
89
104
  const where = conditions.length ? and(...conditions) : undefined;
90
105
 
91
106
  const rows = await this.db
@@ -95,7 +110,10 @@ export class AuditLogRepository {
95
110
  .orderBy(desc(auditLogs.createdAt), desc(auditLogs.id))
96
111
  .limit(limit)
97
112
  .offset((page - 1) * limit);
98
- const [{ value: total }] = await this.db.select({ value: count() }).from(auditLogs).where(where);
113
+ const [{ value: total }] = await this.db
114
+ .select({ value: count() })
115
+ .from(auditLogs)
116
+ .where(where);
99
117
 
100
118
  return { items: rows, meta: buildPageMeta(page, limit, total) };
101
119
  }