@rhino-dev/rhino-nestjs 4.0.0 → 4.2.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 (63) hide show
  1. package/README.md +52 -1
  2. package/dist/blueprint/blueprint-runner.js +32 -1
  3. package/dist/blueprint/blueprint-runner.js.map +1 -1
  4. package/dist/blueprint/blueprint-sorter.d.ts +54 -0
  5. package/dist/blueprint/blueprint-sorter.js +107 -0
  6. package/dist/blueprint/blueprint-sorter.js.map +1 -0
  7. package/dist/controllers/auth.controller.d.ts +34 -5
  8. package/dist/controllers/auth.controller.js +132 -12
  9. package/dist/controllers/auth.controller.js.map +1 -1
  10. package/dist/controllers/invitation.controller.d.ts +41 -2
  11. package/dist/controllers/invitation.controller.js +97 -12
  12. package/dist/controllers/invitation.controller.js.map +1 -1
  13. package/dist/errors/rhino-exception.d.ts +12 -1
  14. package/dist/errors/rhino-exception.js +19 -1
  15. package/dist/errors/rhino-exception.js.map +1 -1
  16. package/dist/guards/group-membership.guard.d.ts +25 -0
  17. package/dist/guards/group-membership.guard.js +70 -0
  18. package/dist/guards/group-membership.guard.js.map +1 -0
  19. package/dist/guards/jwt-auth.guard.js +23 -4
  20. package/dist/guards/jwt-auth.guard.js.map +1 -1
  21. package/dist/index.d.ts +7 -1
  22. package/dist/index.js +17 -2
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces/rhino-config.interface.d.ts +71 -0
  25. package/dist/middleware/domain-route-resolver.d.ts +54 -0
  26. package/dist/middleware/domain-route-resolver.js +145 -0
  27. package/dist/middleware/domain-route-resolver.js.map +1 -0
  28. package/dist/middleware/route-group.middleware.d.ts +48 -4
  29. package/dist/middleware/route-group.middleware.js +274 -13
  30. package/dist/middleware/route-group.middleware.js.map +1 -1
  31. package/dist/rhino.config.d.ts +26 -1
  32. package/dist/rhino.config.js +56 -1
  33. package/dist/rhino.config.js.map +1 -1
  34. package/dist/rhino.module.d.ts +7 -0
  35. package/dist/rhino.module.js +23 -1
  36. package/dist/rhino.module.js.map +1 -1
  37. package/dist/services/auth-hooks.service.d.ts +27 -0
  38. package/dist/services/auth-hooks.service.js +85 -0
  39. package/dist/services/auth-hooks.service.js.map +1 -0
  40. package/dist/services/auth.service.d.ts +19 -0
  41. package/dist/services/auth.service.js +80 -5
  42. package/dist/services/auth.service.js.map +1 -1
  43. package/dist/services/invitation.service.d.ts +6 -0
  44. package/dist/services/invitation.service.js +45 -3
  45. package/dist/services/invitation.service.js.map +1 -1
  46. package/dist/services/membership.service.d.ts +56 -0
  47. package/dist/services/membership.service.js +113 -0
  48. package/dist/services/membership.service.js.map +1 -0
  49. package/dist/services/organization.service.js +8 -1
  50. package/dist/services/organization.service.js.map +1 -1
  51. package/dist/services/route-registration.service.d.ts +1 -0
  52. package/dist/services/route-registration.service.js +2 -0
  53. package/dist/services/route-registration.service.js.map +1 -1
  54. package/dist/tsconfig.build.tsbuildinfo +1 -1
  55. package/dist/utils/domain-pattern.d.ts +36 -0
  56. package/dist/utils/domain-pattern.js +77 -0
  57. package/dist/utils/domain-pattern.js.map +1 -0
  58. package/dist/utils/permission-matcher.js +8 -0
  59. package/dist/utils/permission-matcher.js.map +1 -1
  60. package/dist/utils/route-group-validator.d.ts +14 -0
  61. package/dist/utils/route-group-validator.js +124 -0
  62. package/dist/utils/route-group-validator.js.map +1 -0
  63. package/package.json +1 -1
@@ -0,0 +1,27 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import { RhinoConfigService } from '../rhino.config';
3
+ import type { AuthHookContext, AuthLifecycleHooks } from '../interfaces/rhino-config.interface';
4
+ export type AuthHookEvent = 'afterLogin' | 'afterLogout' | 'afterRegister' | 'afterPasswordRecover' | 'afterPasswordReset';
5
+ /**
6
+ * Resolves a group's optional lifecycle-hooks class/object and runs a single
7
+ * event (design §7). A hook may reject by throwing — the caller (auth
8
+ * controller) decides whether to revoke a token. When a group has no `hooks`
9
+ * config, every event is a silent no-op.
10
+ *
11
+ * `ModuleRef` is optional so the service works in lightweight unit tests that
12
+ * construct it without a Nest container; in that case a `Type` hooks value is
13
+ * instantiated directly via `new`.
14
+ */
15
+ export declare class AuthHooksService {
16
+ private readonly config;
17
+ private readonly moduleRef?;
18
+ constructor(config: RhinoConfigService, moduleRef?: ModuleRef);
19
+ /** Resolve the hooks instance for a group, or `null` when none is configured. */
20
+ resolve(routeGroup: string | null | undefined): AuthLifecycleHooks | null;
21
+ /**
22
+ * Run a single lifecycle event for the resolved group. Returns silently when
23
+ * the group has no hooks or the specific method is absent. A rejection
24
+ * propagates to the caller verbatim.
25
+ */
26
+ run(event: AuthHookEvent, routeGroup: string | null | undefined, ctx: AuthHookContext): Promise<void>;
27
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.AuthHooksService = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const rhino_config_1 = require("../rhino.config");
19
+ /**
20
+ * Resolves a group's optional lifecycle-hooks class/object and runs a single
21
+ * event (design §7). A hook may reject by throwing — the caller (auth
22
+ * controller) decides whether to revoke a token. When a group has no `hooks`
23
+ * config, every event is a silent no-op.
24
+ *
25
+ * `ModuleRef` is optional so the service works in lightweight unit tests that
26
+ * construct it without a Nest container; in that case a `Type` hooks value is
27
+ * instantiated directly via `new`.
28
+ */
29
+ let AuthHooksService = class AuthHooksService {
30
+ config;
31
+ moduleRef;
32
+ constructor(config, moduleRef) {
33
+ this.config = config;
34
+ this.moduleRef = moduleRef;
35
+ }
36
+ /** Resolve the hooks instance for a group, or `null` when none is configured. */
37
+ resolve(routeGroup) {
38
+ const hooks = this.config.routeGroupHooks(routeGroup);
39
+ if (!hooks)
40
+ return null;
41
+ // Plain object implementing the contract.
42
+ if (typeof hooks !== 'function')
43
+ return hooks;
44
+ // A class/Type: prefer the DI container, fall back to direct construction.
45
+ const Cls = hooks;
46
+ if (this.moduleRef) {
47
+ try {
48
+ const instance = this.moduleRef.get(Cls, { strict: false });
49
+ if (instance)
50
+ return instance;
51
+ }
52
+ catch {
53
+ /* not registered as a provider — construct directly below */
54
+ }
55
+ }
56
+ try {
57
+ return new Cls();
58
+ }
59
+ catch {
60
+ return null;
61
+ }
62
+ }
63
+ /**
64
+ * Run a single lifecycle event for the resolved group. Returns silently when
65
+ * the group has no hooks or the specific method is absent. A rejection
66
+ * propagates to the caller verbatim.
67
+ */
68
+ async run(event, routeGroup, ctx) {
69
+ const instance = this.resolve(routeGroup);
70
+ if (!instance)
71
+ return;
72
+ const fn = instance[event];
73
+ if (typeof fn !== 'function')
74
+ return;
75
+ await fn.call(instance, ctx);
76
+ }
77
+ };
78
+ exports.AuthHooksService = AuthHooksService;
79
+ exports.AuthHooksService = AuthHooksService = __decorate([
80
+ (0, common_1.Injectable)(),
81
+ __param(1, (0, common_1.Optional)()),
82
+ __metadata("design:paramtypes", [rhino_config_1.RhinoConfigService,
83
+ core_1.ModuleRef])
84
+ ], AuthHooksService);
85
+ //# sourceMappingURL=auth-hooks.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth-hooks.service.js","sourceRoot":"","sources":["../../src/services/auth-hooks.service.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAAsD;AACtD,uCAAyC;AACzC,kDAAqD;AAarD;;;;;;;;;GASG;AAEI,IAAM,gBAAgB,GAAtB,MAAM,gBAAgB;IAER;IACY;IAF/B,YACmB,MAA0B,EACd,SAAqB;QADjC,WAAM,GAAN,MAAM,CAAoB;QACd,cAAS,GAAT,SAAS,CAAY;IACjD,CAAC;IAEJ,iFAAiF;IACjF,OAAO,CAAC,UAAqC;QAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;QACtD,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,0CAA0C;QAC1C,IAAI,OAAO,KAAK,KAAK,UAAU;YAAE,OAAO,KAA2B,CAAC;QACpE,2EAA2E;QAC3E,MAAM,GAAG,GAAG,KAAmD,CAAC;QAChE,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;YACnB,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC5D,IAAI,QAAQ;oBAAE,OAAO,QAAQ,CAAC;YAChC,CAAC;YAAC,MAAM,CAAC;gBACP,6DAA6D;YAC/D,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,GAAG,CACP,KAAoB,EACpB,UAAqC,EACrC,GAAoB;QAEpB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC1C,IAAI,CAAC,QAAQ;YAAE,OAAO;QACtB,MAAM,EAAE,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;QAC3B,IAAI,OAAO,EAAE,KAAK,UAAU;YAAE,OAAO;QACrC,MAAM,EAAE,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC/B,CAAC;CACF,CAAA;AA7CY,4CAAgB;2BAAhB,gBAAgB;IAD5B,IAAA,mBAAU,GAAE;IAIR,WAAA,IAAA,iBAAQ,GAAE,CAAA;qCADc,iCAAkB;QACF,gBAAS;GAHzC,gBAAgB,CA6C5B"}
@@ -26,8 +26,27 @@ export declare class AuthService {
26
26
  verifyToken(token: string): TokenPayload;
27
27
  login(email: string, password: string): Promise<LoginResult>;
28
28
  logout(_user: any): Promise<void>;
29
+ /**
30
+ * Revoke a just-issued token. JWTs are stateless, so "revoking" means
31
+ * recording the token in a denylist (when the consumer provides a
32
+ * `RevokedToken` model) so the JwtAuthGuard can reject it on the next
33
+ * request. Used when a lifecycle hook rejects a login/register: the token is
34
+ * never returned AND — IF a denylist model exists — can no longer be used
35
+ * even if it leaked.
36
+ *
37
+ * Bounded guarantee: the caller (AuthController) ALWAYS drops the token from
38
+ * the response on rejection, so an attacker never receives it. Persisting it
39
+ * to a denylist is the *additional* protection against a leaked token. When
40
+ * no `RevokedToken` model is configured we log a clear WARNING so operators
41
+ * know revoke is advisory-only — pair it with short-TTL JWTs (see README /
42
+ * CLAUDE.md "Group-auth hooks & token revocation").
43
+ */
44
+ revokeToken(token: string): Promise<void>;
45
+ /** Whether a token has been revoked via {@link revokeToken}. */
46
+ isTokenRevoked(token: string): Promise<boolean>;
29
47
  requestPasswordRecovery(email: string): Promise<{
30
48
  token: string;
49
+ user: any;
31
50
  } | null>;
32
51
  resetPassword(email: string, token: string, newPassword: string): Promise<void>;
33
52
  }
@@ -90,10 +90,27 @@ let AuthService = AuthService_1 = class AuthService {
90
90
  }
91
91
  async login(email, password) {
92
92
  const auth = this.config.authConfig();
93
- const user = await this.userDelegate().findFirst({
94
- where: { [auth.emailField]: email },
95
- include: { userRoles: { include: { organization: true, role: true } } },
96
- }).catch(() => null);
93
+ // Only eager-load the org membership graph when multi-tenancy is configured.
94
+ // Single-tenant / org-less apps have no `userRoles` relation on their User
95
+ // model, so an unconditional include would throw inside Prisma and the
96
+ // `.catch(() => null)` below would surface as "Invalid credentials" — i.e.
97
+ // an org-less app could never log a valid user in. Gating the include on
98
+ // `multiTenantEnabled()` keeps multi-tenant behavior byte-for-byte while
99
+ // letting org-less apps authenticate. (We still degrade gracefully if the
100
+ // include fails for any other reason: retry the plain lookup.)
101
+ const wantsOrg = this.config.multiTenantEnabled();
102
+ let user = null;
103
+ if (wantsOrg) {
104
+ user = await this.userDelegate().findFirst({
105
+ where: { [auth.emailField]: email },
106
+ include: { userRoles: { include: { organization: true, role: true } } },
107
+ }).catch(() => null);
108
+ }
109
+ if (!user) {
110
+ user = await this.userDelegate().findFirst({
111
+ where: { [auth.emailField]: email },
112
+ }).catch(() => null);
113
+ }
97
114
  if (!user)
98
115
  throw rhino_exception_1.RhinoException.unauthorized('Invalid credentials');
99
116
  const hashed = user[auth.passwordField];
@@ -107,6 +124,64 @@ let AuthService = AuthService_1 = class AuthService {
107
124
  async logout(_user) {
108
125
  // JWT stateless; consumer can add blacklist logic via hooks.
109
126
  }
127
+ /**
128
+ * Revoke a just-issued token. JWTs are stateless, so "revoking" means
129
+ * recording the token in a denylist (when the consumer provides a
130
+ * `RevokedToken` model) so the JwtAuthGuard can reject it on the next
131
+ * request. Used when a lifecycle hook rejects a login/register: the token is
132
+ * never returned AND — IF a denylist model exists — can no longer be used
133
+ * even if it leaked.
134
+ *
135
+ * Bounded guarantee: the caller (AuthController) ALWAYS drops the token from
136
+ * the response on rejection, so an attacker never receives it. Persisting it
137
+ * to a denylist is the *additional* protection against a leaked token. When
138
+ * no `RevokedToken` model is configured we log a clear WARNING so operators
139
+ * know revoke is advisory-only — pair it with short-TTL JWTs (see README /
140
+ * CLAUDE.md "Group-auth hooks & token revocation").
141
+ */
142
+ async revokeToken(token) {
143
+ if (!token)
144
+ return;
145
+ let delegate;
146
+ try {
147
+ delegate = this.prisma.model('revokedToken');
148
+ }
149
+ catch {
150
+ this.logger.warn('AuthService.revokeToken: no `RevokedToken` model is configured, so token ' +
151
+ 'revocation is advisory only — the token is dropped from the response but ' +
152
+ 'cannot be denylisted if it has already leaked. Add a `RevokedToken` model ' +
153
+ '(token, createdAt) to your Prisma schema and use short-TTL JWTs.');
154
+ return;
155
+ }
156
+ try {
157
+ await delegate.create({ data: { token, createdAt: new Date() } });
158
+ }
159
+ catch (err) {
160
+ // The model exists but the write failed — surface it so a misconfigured
161
+ // denylist is not silently a no-op.
162
+ this.logger.warn(`AuthService.revokeToken: failed to persist revoked token (${err.message}).`);
163
+ }
164
+ }
165
+ /** Whether a token has been revoked via {@link revokeToken}. */
166
+ async isTokenRevoked(token) {
167
+ if (!token)
168
+ return false;
169
+ let delegate;
170
+ try {
171
+ delegate = this.prisma.model('revokedToken');
172
+ }
173
+ catch {
174
+ // No denylist model — nothing can be revoked, so nothing is revoked.
175
+ return false;
176
+ }
177
+ try {
178
+ const row = await delegate.findFirst({ where: { token } });
179
+ return Boolean(row);
180
+ }
181
+ catch {
182
+ return false;
183
+ }
184
+ }
110
185
  async requestPasswordRecovery(email) {
111
186
  const user = await this.userDelegate().findFirst({ where: { email } }).catch(() => null);
112
187
  if (!user)
@@ -123,7 +198,7 @@ let AuthService = AuthService_1 = class AuthService {
123
198
  this.logger.warn(`AuthService: could not persist password reset token (${err.message}). ` +
124
199
  'Add a `PasswordResetToken` model to your Prisma schema to enable this feature.');
125
200
  }
126
- return { token };
201
+ return { token, user };
127
202
  }
128
203
  async resetPassword(email, token, newPassword) {
129
204
  let reset = null;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.service.js","sourceRoot":"","sources":["../../src/services/auth.service.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,8 @@ export interface CreateInvitationInput {
5
5
  roleId: number;
6
6
  organization: any;
7
7
  invitedBy: any;
8
+ /** The group the invitee will join (design §8). NULL = wildcard membership. */
9
+ routeGroup?: string | null;
8
10
  }
9
11
  /**
10
12
  * Invitation lifecycle: create/resend/cancel/accept with token-based acceptance.
@@ -16,6 +18,8 @@ export declare class InvitationService {
16
18
  constructor(prisma: PrismaService, config: RhinoConfigService);
17
19
  private delegate;
18
20
  private generateToken;
21
+ /** Whether the inviter holds a membership row for `routeGroup` (wildcard ok). */
22
+ private inviterIsMember;
19
23
  private expiresAt;
20
24
  list(organizationId: number, status?: string): Promise<any>;
21
25
  create(input: CreateInvitationInput): Promise<any>;
@@ -26,10 +30,12 @@ export declare class InvitationService {
26
30
  organization: any;
27
31
  role: any;
28
32
  email: any;
33
+ routeGroup: any;
29
34
  accepted?: undefined;
30
35
  } | {
31
36
  accepted: boolean;
32
37
  organization: any;
38
+ routeGroup: any;
33
39
  requiresRegistration?: undefined;
34
40
  role?: undefined;
35
41
  email?: undefined;
@@ -14,6 +14,7 @@ const common_1 = require("@nestjs/common");
14
14
  const crypto_1 = require("crypto");
15
15
  const prisma_service_1 = require("../prisma/prisma.service");
16
16
  const rhino_config_1 = require("../rhino.config");
17
+ const rhino_exception_1 = require("../errors/rhino-exception");
17
18
  /**
18
19
  * Invitation lifecycle: create/resend/cancel/accept with token-based acceptance.
19
20
  * Mirrors Laravel's InvitationController + OrganizationInvitation model.
@@ -31,6 +32,21 @@ let InvitationService = class InvitationService {
31
32
  generateToken() {
32
33
  return (0, crypto_1.randomBytes)(32).toString('hex'); // 64 hex chars
33
34
  }
35
+ /** Whether the inviter holds a membership row for `routeGroup` (wildcard ok). */
36
+ inviterIsMember(inviter, routeGroup, org) {
37
+ const rows = inviter?.userRoles ?? inviter?.user_roles ?? [];
38
+ const orgId = org?.id ?? null;
39
+ for (const ur of rows) {
40
+ const rg = ur.routeGroup ?? ur.route_group ?? null;
41
+ if (rg !== null && String(rg) !== routeGroup)
42
+ continue;
43
+ const rowOrg = ur.organizationId ?? ur.organization_id ?? null;
44
+ if (orgId != null && rowOrg != null && rowOrg !== orgId)
45
+ continue;
46
+ return true;
47
+ }
48
+ return false;
49
+ }
34
50
  expiresAt() {
35
51
  const days = this.config.invitationsConfig().expiresDays;
36
52
  const d = new Date();
@@ -51,12 +67,35 @@ let InvitationService = class InvitationService {
51
67
  throw new common_1.BadRequestException('Role not allowed for invitations');
52
68
  }
53
69
  }
70
+ const routeGroup = input.routeGroup ?? null;
71
+ // The `public` group has no auth, so it cannot be invited into (design §8).
72
+ if (routeGroup === 'public') {
73
+ throw new common_1.BadRequestException('Cannot invite into the public group');
74
+ }
75
+ // When enforcement is on, the inviter must themselves be a member of the
76
+ // group they are inviting into (design §8). NULL (wildcard) skips this — a
77
+ // wildcard inviter can invite into any group.
78
+ if (this.config.enforceGroupMembership() && routeGroup != null) {
79
+ if (!this.inviterIsMember(input.invitedBy, routeGroup, input.organization)) {
80
+ // Coarse membership denial → 403 for parity with Laravel/Rails
81
+ // (Decision 9.C), matching the controller's gate.
82
+ throw rhino_exception_1.RhinoException.membershipDenied('You are not a member of that group');
83
+ }
84
+ }
85
+ // Tenant groups carry an org; non-tenant groups store a NULL org. Default
86
+ // to using the org (legacy behavior) — only drop it when enforcement is on
87
+ // AND the group is positively non-tenant, keeping flag-off paths unchanged.
88
+ const dropOrg = this.config.enforceGroupMembership() &&
89
+ routeGroup != null &&
90
+ !this.config.isTenantGroup(routeGroup);
91
+ const organizationId = dropOrg ? null : input.organization?.id ?? null;
54
92
  const token = this.generateToken();
55
93
  const invitation = await this.delegate().create({
56
94
  data: {
57
- organizationId: input.organization.id,
95
+ organizationId,
58
96
  email: input.email,
59
97
  roleId: input.roleId,
98
+ routeGroup,
60
99
  token,
61
100
  status: 'pending',
62
101
  invitedById: input.invitedBy.id,
@@ -110,6 +149,7 @@ let InvitationService = class InvitationService {
110
149
  await this.delegate().update({ where: { id: inv.id }, data: { status: 'expired' } });
111
150
  throw new common_1.BadRequestException('Invitation expired');
112
151
  }
152
+ const routeGroup = inv.routeGroup ?? inv.route_group ?? null;
113
153
  if (!userId) {
114
154
  // User is unauthenticated — return org/role so client can register
115
155
  return {
@@ -117,13 +157,15 @@ let InvitationService = class InvitationService {
117
157
  organization: inv.organization,
118
158
  role: inv.role,
119
159
  email: inv.email,
160
+ routeGroup,
120
161
  };
121
162
  }
122
163
  await this.prisma.model('userRole').create({
123
164
  data: {
124
165
  userId,
125
- organizationId: inv.organizationId,
166
+ organizationId: inv.organizationId ?? null,
126
167
  roleId: inv.roleId,
168
+ routeGroup,
127
169
  permissions: [],
128
170
  },
129
171
  });
@@ -131,7 +173,7 @@ let InvitationService = class InvitationService {
131
173
  where: { id: inv.id },
132
174
  data: { status: 'accepted', acceptedAt: new Date() },
133
175
  });
134
- return { accepted: true, organization: inv.organization };
176
+ return { accepted: true, organization: inv.organization, routeGroup };
135
177
  }
136
178
  };
137
179
  exports.InvitationService = InvitationService;
@@ -1 +1 @@
1
- {"version":3,"file":"invitation.service.js","sourceRoot":"","sources":["../../src/services/invitation.service.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"invitation.service.js","sourceRoot":"","sources":["../../src/services/invitation.service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ import { RhinoConfigService } from '../rhino.config';
2
+ /**
3
+ * A single `user_roles` membership row, normalized across snake_case /
4
+ * camelCase shapes (Prisma vs raw SQL). `routeGroup === null` is a WILDCARD
5
+ * (Decision 9.B) — the row is a member of every group.
6
+ */
7
+ export interface MembershipRow {
8
+ routeGroup: string | null;
9
+ organizationId: number | string | null;
10
+ permissions: unknown;
11
+ role?: any;
12
+ }
13
+ /**
14
+ * Membership enforcement & permission resolution (design §6).
15
+ *
16
+ * Gated entirely by `auth.enforceGroupMembership`:
17
+ * - OFF (default): {@link enabled} is `false`; callers skip every check and
18
+ * fall back to the legacy org-presence heuristic. Behavior is unchanged.
19
+ * - ON: a user must hold a `user_roles` row whose `route_group` matches the
20
+ * request's group (a NULL row is a wildcard) and — for tenant groups — whose
21
+ * organization matches the resolved org. Permissions then resolve from that
22
+ * matched row only.
23
+ *
24
+ * Membership is a COARSE gate (may you enter the group); the ResourcePolicy
25
+ * remains the FINE check. They run in sequence and are never merged.
26
+ */
27
+ export declare class MembershipService {
28
+ private readonly config;
29
+ constructor(config: RhinoConfigService);
30
+ /** Whether membership enforcement is active at all. */
31
+ enabled(): boolean;
32
+ private userRoles;
33
+ /**
34
+ * Find the membership rows that admit `user` into `routeGroup` for the given
35
+ * organization. A row matches when:
36
+ * - its `route_group` is NULL (wildcard) OR equals `routeGroup`, AND
37
+ * - for tenant groups, its organization equals the resolved org's id
38
+ * (NULL-org wildcard rows still match); for non-tenant groups the org is
39
+ * ignored entirely.
40
+ */
41
+ matchingRows(user: any, routeGroup: string | null | undefined, organization: {
42
+ id: number | string;
43
+ } | null | undefined, isTenant: boolean): MembershipRow[];
44
+ /**
45
+ * Whether `user` is a member of `routeGroup` (for the resolved org, when the
46
+ * group is a tenant group). Only meaningful when {@link enabled}.
47
+ */
48
+ isMember(user: any, routeGroup: string | null | undefined, organization: {
49
+ id: number | string;
50
+ } | null | undefined, isTenant: boolean): boolean;
51
+ /**
52
+ * Permissions granted to the user from the matched membership rows only
53
+ * (design §6: "permissions then resolve from that matching membership row").
54
+ */
55
+ permissionsFromRows(rows: MembershipRow[]): string[];
56
+ }
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.MembershipService = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const rhino_config_1 = require("../rhino.config");
15
+ const permission_matcher_1 = require("../utils/permission-matcher");
16
+ function readRouteGroup(ur) {
17
+ const v = ur.routeGroup ?? ur.route_group;
18
+ return v == null ? null : String(v);
19
+ }
20
+ function readOrgId(ur) {
21
+ const v = ur.organizationId ?? ur.organization_id;
22
+ return v == null ? null : v;
23
+ }
24
+ /**
25
+ * Membership enforcement & permission resolution (design §6).
26
+ *
27
+ * Gated entirely by `auth.enforceGroupMembership`:
28
+ * - OFF (default): {@link enabled} is `false`; callers skip every check and
29
+ * fall back to the legacy org-presence heuristic. Behavior is unchanged.
30
+ * - ON: a user must hold a `user_roles` row whose `route_group` matches the
31
+ * request's group (a NULL row is a wildcard) and — for tenant groups — whose
32
+ * organization matches the resolved org. Permissions then resolve from that
33
+ * matched row only.
34
+ *
35
+ * Membership is a COARSE gate (may you enter the group); the ResourcePolicy
36
+ * remains the FINE check. They run in sequence and are never merged.
37
+ */
38
+ let MembershipService = class MembershipService {
39
+ config;
40
+ constructor(config) {
41
+ this.config = config;
42
+ }
43
+ /** Whether membership enforcement is active at all. */
44
+ enabled() {
45
+ return this.config.enforceGroupMembership();
46
+ }
47
+ userRoles(user) {
48
+ if (!user)
49
+ return [];
50
+ return user.userRoles ?? user.user_roles ?? [];
51
+ }
52
+ /**
53
+ * Find the membership rows that admit `user` into `routeGroup` for the given
54
+ * organization. A row matches when:
55
+ * - its `route_group` is NULL (wildcard) OR equals `routeGroup`, AND
56
+ * - for tenant groups, its organization equals the resolved org's id
57
+ * (NULL-org wildcard rows still match); for non-tenant groups the org is
58
+ * ignored entirely.
59
+ */
60
+ matchingRows(user, routeGroup, organization, isTenant) {
61
+ const group = routeGroup == null ? null : String(routeGroup);
62
+ const orgId = organization?.id ?? null;
63
+ const rows = [];
64
+ for (const ur of this.userRoles(user)) {
65
+ const rowGroup = readRouteGroup(ur);
66
+ // route_group gate: NULL row is a wildcard, else must equal the group.
67
+ if (rowGroup !== null && group !== null && rowGroup !== group)
68
+ continue;
69
+ // (a concrete group request with a non-matching concrete row is rejected
70
+ // above; a NULL request — legacy/global — matches any row.)
71
+ const rowOrg = readOrgId(ur);
72
+ if (isTenant && orgId != null) {
73
+ // Tenant group with a resolved org: the row must target that org, or be
74
+ // an org-wildcard (NULL org) row.
75
+ if (rowOrg !== null && rowOrg !== orgId)
76
+ continue;
77
+ }
78
+ // Non-tenant group: org is ignored.
79
+ rows.push({
80
+ routeGroup: rowGroup,
81
+ organizationId: rowOrg,
82
+ permissions: ur.permissions,
83
+ role: ur.role,
84
+ });
85
+ }
86
+ return rows;
87
+ }
88
+ /**
89
+ * Whether `user` is a member of `routeGroup` (for the resolved org, when the
90
+ * group is a tenant group). Only meaningful when {@link enabled}.
91
+ */
92
+ isMember(user, routeGroup, organization, isTenant) {
93
+ return this.matchingRows(user, routeGroup, organization, isTenant).length > 0;
94
+ }
95
+ /**
96
+ * Permissions granted to the user from the matched membership rows only
97
+ * (design §6: "permissions then resolve from that matching membership row").
98
+ */
99
+ permissionsFromRows(rows) {
100
+ const out = [];
101
+ for (const r of rows) {
102
+ for (const p of (0, permission_matcher_1.coercePermissions)(r.permissions))
103
+ out.push(p);
104
+ }
105
+ return out;
106
+ }
107
+ };
108
+ exports.MembershipService = MembershipService;
109
+ exports.MembershipService = MembershipService = __decorate([
110
+ (0, common_1.Injectable)(),
111
+ __metadata("design:paramtypes", [rhino_config_1.RhinoConfigService])
112
+ ], MembershipService);
113
+ //# sourceMappingURL=membership.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"membership.service.js","sourceRoot":"","sources":["../../src/services/membership.service.ts"],"names":[],"mappings":"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"}
@@ -36,7 +36,14 @@ let OrganizationService = class OrganizationService {
36
36
  const org = await delegate.findFirst({ where: { [column]: value } });
37
37
  if (!org)
38
38
  throw new common_1.NotFoundException('Organization not found');
39
- if (user) {
39
+ // FIX 11.2: when group-membership enforcement is ON, an authenticated
40
+ // non-member must receive 403 from GroupMembershipGuard, which takes
41
+ // precedence over this org-resolution 404. So we DON'T 404 here for a
42
+ // non-member — we resolve & attach the org and let the downstream guard
43
+ // decide (allow vs 403). A genuinely non-existent org still 404s above.
44
+ // When enforcement is OFF (default), behavior is byte-for-byte unchanged:
45
+ // a non-member still gets the info-hiding 404 here.
46
+ if (user && !this.config.enforceGroupMembership()) {
40
47
  await this.ensureMembership(user, org);
41
48
  }
42
49
  return org;
@@ -1 +1 @@
1
- {"version":3,"file":"organization.service.js","sourceRoot":"","sources":["../../src/services/organization.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+D;AAC/D,6DAAyD;AACzD,kDAAqD;AAErD;;;GAGG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAEX;IACA;IAFnB,YACmB,MAAqB,EACrB,MAA0B;QAD1B,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAoB;IAC1C,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,UAA2B,EAAE,IAAU;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,iBAAiB,IAAI,cAAc,CAAC;QACrF,MAAM,KAAK,GACT,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC,CAAC,MAAM,KAAK,IAAI;gBACf,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;gBACpB,CAAC,CAAC,UAAU;YACd,CAAC,CAAC,UAAU,CAAC;QAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;QAEhE,IAAI,IAAI,EAAE,CAAC;YACT,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAS,EAAE,GAAQ;QACxC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,qBAAqB,IAAI,UAAU,CAAC;QACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC;YAC1C,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,EAAE;SACnD,CAAC,CAAC;QACH,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;IACzE,CAAC;CACF,CAAA;AAnCY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAGgB,8BAAa;QACb,iCAAkB;GAHlC,mBAAmB,CAmC/B"}
1
+ {"version":3,"file":"organization.service.js","sourceRoot":"","sources":["../../src/services/organization.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA+D;AAC/D,6DAAyD;AACzD,kDAAqD;AAErD;;;GAGG;AAEI,IAAM,mBAAmB,GAAzB,MAAM,mBAAmB;IAEX;IACA;IAFnB,YACmB,MAAqB,EACrB,MAA0B;QAD1B,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAoB;IAC1C,CAAC;IAEJ,KAAK,CAAC,OAAO,CAAC,UAA2B,EAAE,IAAU;QACnD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,4BAA4B,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,iBAAiB,IAAI,cAAc,CAAC;QACrF,MAAM,KAAK,GACT,MAAM,KAAK,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;YACjD,CAAC,CAAC,MAAM,KAAK,IAAI;gBACf,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;gBACpB,CAAC,CAAC,UAAU;YACd,CAAC,CAAC,UAAU,CAAC;QAEjB,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACrE,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;QAEhE,sEAAsE;QACtE,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,wEAAwE;QACxE,0EAA0E;QAC1E,oDAAoD;QACpD,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,sBAAsB,EAAE,EAAE,CAAC;YAClD,MAAM,IAAI,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACzC,CAAC;QAED,OAAO,GAAG,CAAC;IACb,CAAC;IAED,KAAK,CAAC,gBAAgB,CAAC,IAAS,EAAE,GAAQ;QACxC,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,WAAW,EAAE,qBAAqB,IAAI,UAAU,CAAC;QACxF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,SAAS,CAAC;YAC1C,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,EAAE,cAAc,EAAE,GAAG,CAAC,EAAE,EAAE;SACnD,CAAC,CAAC;QACH,IAAI,CAAC,UAAU;YAAE,MAAM,IAAI,0BAAiB,CAAC,wBAAwB,CAAC,CAAC;IACzE,CAAC;CACF,CAAA;AA1CY,kDAAmB;8BAAnB,mBAAmB;IAD/B,IAAA,mBAAU,GAAE;qCAGgB,8BAAa;QACb,iCAAkB;GAHlC,mBAAmB,CA0C/B"}
@@ -33,6 +33,7 @@ export declare function describeRoutes(config: RhinoConfigService): Array<{
33
33
  group: string;
34
34
  slug: string;
35
35
  prefix: string;
36
+ domain: string | null;
36
37
  model: string;
37
38
  softDeletes: boolean;
38
39
  hasAuditTrail: boolean;