@rhino-dev/rhino-nestjs 0.2.5 → 4.2.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 (59) hide show
  1. package/README.md +52 -1
  2. package/dist/cli/index.js +0 -0
  3. package/dist/controllers/auth.controller.d.ts +34 -5
  4. package/dist/controllers/auth.controller.js +132 -12
  5. package/dist/controllers/auth.controller.js.map +1 -1
  6. package/dist/controllers/invitation.controller.d.ts +41 -2
  7. package/dist/controllers/invitation.controller.js +97 -12
  8. package/dist/controllers/invitation.controller.js.map +1 -1
  9. package/dist/errors/rhino-exception.d.ts +12 -1
  10. package/dist/errors/rhino-exception.js +19 -1
  11. package/dist/errors/rhino-exception.js.map +1 -1
  12. package/dist/guards/group-membership.guard.d.ts +25 -0
  13. package/dist/guards/group-membership.guard.js +70 -0
  14. package/dist/guards/group-membership.guard.js.map +1 -0
  15. package/dist/guards/jwt-auth.guard.js +23 -4
  16. package/dist/guards/jwt-auth.guard.js.map +1 -1
  17. package/dist/index.d.ts +7 -1
  18. package/dist/index.js +17 -2
  19. package/dist/index.js.map +1 -1
  20. package/dist/interfaces/rhino-config.interface.d.ts +71 -0
  21. package/dist/middleware/domain-route-resolver.d.ts +54 -0
  22. package/dist/middleware/domain-route-resolver.js +145 -0
  23. package/dist/middleware/domain-route-resolver.js.map +1 -0
  24. package/dist/middleware/route-group.middleware.d.ts +48 -4
  25. package/dist/middleware/route-group.middleware.js +274 -13
  26. package/dist/middleware/route-group.middleware.js.map +1 -1
  27. package/dist/rhino.config.d.ts +26 -1
  28. package/dist/rhino.config.js +56 -1
  29. package/dist/rhino.config.js.map +1 -1
  30. package/dist/rhino.module.d.ts +7 -0
  31. package/dist/rhino.module.js +23 -1
  32. package/dist/rhino.module.js.map +1 -1
  33. package/dist/services/auth-hooks.service.d.ts +27 -0
  34. package/dist/services/auth-hooks.service.js +85 -0
  35. package/dist/services/auth-hooks.service.js.map +1 -0
  36. package/dist/services/auth.service.d.ts +19 -0
  37. package/dist/services/auth.service.js +80 -5
  38. package/dist/services/auth.service.js.map +1 -1
  39. package/dist/services/invitation.service.d.ts +6 -0
  40. package/dist/services/invitation.service.js +45 -3
  41. package/dist/services/invitation.service.js.map +1 -1
  42. package/dist/services/membership.service.d.ts +56 -0
  43. package/dist/services/membership.service.js +113 -0
  44. package/dist/services/membership.service.js.map +1 -0
  45. package/dist/services/organization.service.js +8 -1
  46. package/dist/services/organization.service.js.map +1 -1
  47. package/dist/services/route-registration.service.d.ts +1 -0
  48. package/dist/services/route-registration.service.js +2 -0
  49. package/dist/services/route-registration.service.js.map +1 -1
  50. package/dist/tsconfig.build.tsbuildinfo +1 -1
  51. package/dist/utils/domain-pattern.d.ts +36 -0
  52. package/dist/utils/domain-pattern.js +77 -0
  53. package/dist/utils/domain-pattern.js.map +1 -0
  54. package/dist/utils/permission-matcher.js +8 -0
  55. package/dist/utils/permission-matcher.js.map +1 -1
  56. package/dist/utils/route-group-validator.d.ts +14 -0
  57. package/dist/utils/route-group-validator.js +124 -0
  58. package/dist/utils/route-group-validator.js.map +1 -0
  59. package/package.json +6 -2
@@ -1,5 +1,5 @@
1
1
  import { HttpException } from '@nestjs/common';
2
- export type RhinoErrorCode = 'VALIDATION_FAILED' | 'FORBIDDEN_FIELDS' | 'CROSS_TENANT' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'ACTION_DISABLED' | 'UNKNOWN_RESOURCE' | 'INVITATION_EXPIRED' | 'INVITATION_USED' | 'INVITATION_INVALID_ROLE' | 'NESTED_OPERATION_FAILED' | 'INCLUDE_NOT_AUTHORIZED';
2
+ export type RhinoErrorCode = 'VALIDATION_FAILED' | 'FORBIDDEN_FIELDS' | 'CROSS_TENANT' | 'UNAUTHORIZED' | 'FORBIDDEN' | 'NOT_FOUND' | 'ACTION_DISABLED' | 'UNKNOWN_RESOURCE' | 'INVITATION_EXPIRED' | 'INVITATION_USED' | 'INVITATION_INVALID_ROLE' | 'NESTED_OPERATION_FAILED' | 'INCLUDE_NOT_AUTHORIZED' | 'AUTH_REJECTED' | 'MEMBERSHIP_DENIED';
3
3
  export interface RhinoErrorBody {
4
4
  code: RhinoErrorCode;
5
5
  message: string;
@@ -21,4 +21,15 @@ export declare class RhinoException extends HttpException {
21
21
  static actionDisabled(action: string): RhinoException;
22
22
  static unknownResource(slug: string): RhinoException;
23
23
  static includeNotAuthorized(slug: string): RhinoException;
24
+ static authRejected(message?: string, status?: number): RhinoException;
25
+ static membershipDenied(message?: string): RhinoException;
26
+ }
27
+ /**
28
+ * Thrown by a lifecycle hook to reject an otherwise-successful auth action.
29
+ * The controller catches it: for token-issuing actions (login/register) the
30
+ * just-issued token is revoked; the configured `status` (default 403) is then
31
+ * returned to the client. A hook may also throw any other error.
32
+ */
33
+ export declare class RhinoAuthRejected extends RhinoException {
34
+ constructor(message?: string, status?: number);
24
35
  }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RhinoException = void 0;
3
+ exports.RhinoAuthRejected = exports.RhinoException = void 0;
4
4
  const common_1 = require("@nestjs/common");
5
5
  /**
6
6
  * Single exception shape surfaced by the library. Frontends can rely on the
@@ -42,6 +42,24 @@ class RhinoException extends common_1.HttpException {
42
42
  static includeNotAuthorized(slug) {
43
43
  return new RhinoException('INCLUDE_NOT_AUTHORIZED', `Include not authorized: ${slug}`, 403, { slug });
44
44
  }
45
+ static authRejected(message = 'Authentication rejected', status = 403) {
46
+ return new RhinoException('AUTH_REJECTED', message, status);
47
+ }
48
+ static membershipDenied(message = 'You are not a member of this group.') {
49
+ return new RhinoException('MEMBERSHIP_DENIED', message, 403);
50
+ }
45
51
  }
46
52
  exports.RhinoException = RhinoException;
53
+ /**
54
+ * Thrown by a lifecycle hook to reject an otherwise-successful auth action.
55
+ * The controller catches it: for token-issuing actions (login/register) the
56
+ * just-issued token is revoked; the configured `status` (default 403) is then
57
+ * returned to the client. A hook may also throw any other error.
58
+ */
59
+ class RhinoAuthRejected extends RhinoException {
60
+ constructor(message = 'Authentication rejected', status = 403) {
61
+ super('AUTH_REJECTED', message, status);
62
+ }
63
+ }
64
+ exports.RhinoAuthRejected = RhinoAuthRejected;
47
65
  //# sourceMappingURL=rhino-exception.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rhino-exception.js","sourceRoot":"","sources":["../../src/errors/rhino-exception.ts"],"names":[],"mappings":";;;AAAA,2CAA+C;AAuB/C;;;GAGG;AACH,MAAa,cAAe,SAAQ,sBAAa;IACtC,IAAI,CAAiB;IAE9B,YAAY,IAAoB,EAAE,OAAe,EAAE,MAAc,EAAE,OAA6B;QAC9F,MAAM,IAAI,GAAmB,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,MAAgC;QACtD,OAAO,IAAI,cAAc,CAAC,mBAAmB,EAAE,mBAAmB,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,MAAgB;QACrC,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,mDAAmD,EAAE,GAAG,EAAE;YACtG,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,MAAgC;QACjD,OAAO,IAAI,cAAc,CAAC,cAAc,EAAE,+CAA+C,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9G,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,GAAG,cAAc;QAC1C,OAAO,IAAI,cAAc,CAAC,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,OAAO,GAAG,8BAA8B;QACvD,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAG,WAAW;QACnC,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,MAAc;QAClC,OAAO,IAAI,cAAc,CAAC,iBAAiB,EAAE,sBAAsB,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,IAAY;QACjC,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,qBAAqB,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,CAAC,oBAAoB,CAAC,IAAY;QACtC,OAAO,IAAI,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACxG,CAAC;CACF;AA9CD,wCA8CC"}
1
+ {"version":3,"file":"rhino-exception.js","sourceRoot":"","sources":["../../src/errors/rhino-exception.ts"],"names":[],"mappings":";;;AAAA,2CAA+C;AAyB/C;;;GAGG;AACH,MAAa,cAAe,SAAQ,sBAAa;IACtC,IAAI,CAAiB;IAE9B,YAAY,IAAoB,EAAE,OAAe,EAAE,MAAc,EAAE,OAA6B;QAC9F,MAAM,IAAI,GAAmB,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC;QAChF,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,MAAgC;QACtD,OAAO,IAAI,cAAc,CAAC,mBAAmB,EAAE,mBAAmB,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACvF,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,MAAgB;QACrC,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,mDAAmD,EAAE,GAAG,EAAE;YACtG,MAAM;SACP,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,WAAW,CAAC,MAAgC;QACjD,OAAO,IAAI,cAAc,CAAC,cAAc,EAAE,+CAA+C,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IAC9G,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,GAAG,cAAc;QAC1C,OAAO,IAAI,cAAc,CAAC,cAAc,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,CAAC,SAAS,CAAC,OAAO,GAAG,8BAA8B;QACvD,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAG,WAAW;QACnC,OAAO,IAAI,cAAc,CAAC,WAAW,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,CAAC,cAAc,CAAC,MAAc;QAClC,OAAO,IAAI,cAAc,CAAC,iBAAiB,EAAE,sBAAsB,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,CAAC,eAAe,CAAC,IAAY;QACjC,OAAO,IAAI,cAAc,CAAC,kBAAkB,EAAE,qBAAqB,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5F,CAAC;IAED,MAAM,CAAC,oBAAoB,CAAC,IAAY;QACtC,OAAO,IAAI,cAAc,CAAC,wBAAwB,EAAE,2BAA2B,IAAI,EAAE,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IACxG,CAAC;IAED,MAAM,CAAC,YAAY,CAAC,OAAO,GAAG,yBAAyB,EAAE,MAAM,GAAG,GAAG;QACnE,OAAO,IAAI,cAAc,CAAC,eAAe,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC9D,CAAC;IAED,MAAM,CAAC,gBAAgB,CAAC,OAAO,GAAG,qCAAqC;QACrE,OAAO,IAAI,cAAc,CAAC,mBAAmB,EAAE,OAAO,EAAE,GAAG,CAAC,CAAC;IAC/D,CAAC;CACF;AAtDD,wCAsDC;AAED;;;;;GAKG;AACH,MAAa,iBAAkB,SAAQ,cAAc;IACnD,YAAY,OAAO,GAAG,yBAAyB,EAAE,MAAM,GAAG,GAAG;QAC3D,KAAK,CAAC,eAAe,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC1C,CAAC;CACF;AAJD,8CAIC"}
@@ -0,0 +1,25 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { RhinoConfigService } from '../rhino.config';
3
+ import { MembershipService } from '../services/membership.service';
4
+ /**
5
+ * Coarse group-access gate (design §6), gated entirely by
6
+ * `auth.enforceGroupMembership`.
7
+ *
8
+ * When the flag is OFF this guard is a pure no-op: it returns `true`
9
+ * immediately and never inspects the request, so behavior is byte-for-byte
10
+ * unchanged. Install it AFTER `JwtAuthGuard` (needs `req.user`) and after
11
+ * `RouteGroupMiddleware` (needs `req.__routeGroup`).
12
+ *
13
+ * When ON:
14
+ * - `public`/skip-auth requests bypass the check (no auth).
15
+ * - the user must hold a membership row matching the resolved group (NULL row
16
+ * = wildcard) and, for tenant groups, the resolved org. No match → 403.
17
+ * - on success, the matched rows' permissions become the permission source
18
+ * for the downstream ResourcePolicy (attached as `__membershipPermissions`).
19
+ */
20
+ export declare class GroupMembershipGuard implements CanActivate {
21
+ private readonly config;
22
+ private readonly membership;
23
+ constructor(config: RhinoConfigService, membership: MembershipService);
24
+ canActivate(context: ExecutionContext): boolean;
25
+ }
@@ -0,0 +1,70 @@
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.GroupMembershipGuard = void 0;
13
+ const common_1 = require("@nestjs/common");
14
+ const rhino_config_1 = require("../rhino.config");
15
+ const membership_service_1 = require("../services/membership.service");
16
+ const rhino_exception_1 = require("../errors/rhino-exception");
17
+ /**
18
+ * Coarse group-access gate (design §6), gated entirely by
19
+ * `auth.enforceGroupMembership`.
20
+ *
21
+ * When the flag is OFF this guard is a pure no-op: it returns `true`
22
+ * immediately and never inspects the request, so behavior is byte-for-byte
23
+ * unchanged. Install it AFTER `JwtAuthGuard` (needs `req.user`) and after
24
+ * `RouteGroupMiddleware` (needs `req.__routeGroup`).
25
+ *
26
+ * When ON:
27
+ * - `public`/skip-auth requests bypass the check (no auth).
28
+ * - the user must hold a membership row matching the resolved group (NULL row
29
+ * = wildcard) and, for tenant groups, the resolved org. No match → 403.
30
+ * - on success, the matched rows' permissions become the permission source
31
+ * for the downstream ResourcePolicy (attached as `__membershipPermissions`).
32
+ */
33
+ let GroupMembershipGuard = class GroupMembershipGuard {
34
+ config;
35
+ membership;
36
+ constructor(config, membership) {
37
+ this.config = config;
38
+ this.membership = membership;
39
+ }
40
+ canActivate(context) {
41
+ if (!this.membership.enabled())
42
+ return true;
43
+ const req = context.switchToHttp().getRequest();
44
+ if (req.__skipAuth)
45
+ return true;
46
+ const user = req.user;
47
+ if (!user)
48
+ return true; // unauthenticated handling is JwtAuthGuard's job
49
+ const routeGroup = req.__routeGroup ?? null;
50
+ // The `public` group never enforces membership (no auth).
51
+ if (routeGroup === 'public')
52
+ return true;
53
+ const org = req.organization ?? null;
54
+ const isTenant = this.config.isTenantGroup(routeGroup);
55
+ const rows = this.membership.matchingRows(user, routeGroup, org, isTenant);
56
+ if (rows.length === 0) {
57
+ throw rhino_exception_1.RhinoException.membershipDenied();
58
+ }
59
+ // Switch the permission source to the matched membership row(s).
60
+ user.__membershipPermissions = this.membership.permissionsFromRows(rows);
61
+ return true;
62
+ }
63
+ };
64
+ exports.GroupMembershipGuard = GroupMembershipGuard;
65
+ exports.GroupMembershipGuard = GroupMembershipGuard = __decorate([
66
+ (0, common_1.Injectable)(),
67
+ __metadata("design:paramtypes", [rhino_config_1.RhinoConfigService,
68
+ membership_service_1.MembershipService])
69
+ ], GroupMembershipGuard);
70
+ //# sourceMappingURL=group-membership.guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group-membership.guard.js","sourceRoot":"","sources":["../../src/guards/group-membership.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAIwB;AACxB,kDAAqD;AACrD,uEAAmE;AACnE,+DAA2D;AAG3D;;;;;;;;;;;;;;;GAeG;AAEI,IAAM,oBAAoB,GAA1B,MAAM,oBAAoB;IAEZ;IACA;IAFnB,YACmB,MAA0B,EAC1B,UAA6B;QAD7B,WAAM,GAAN,MAAM,CAAoB;QAC1B,eAAU,GAAV,UAAU,CAAmB;IAC7C,CAAC;IAEJ,WAAW,CAAC,OAAyB;QACnC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE;YAAE,OAAO,IAAI,CAAC;QAE5C,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAC9D,IAAI,GAAG,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC,CAAC,iDAAiD;QAEzE,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC;QAC5C,0DAA0D;QAC1D,IAAI,UAAU,KAAK,QAAQ;YAAE,OAAO,IAAI,CAAC;QAEzC,MAAM,GAAG,GAAG,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC;QACrC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;QAEvD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;QAC3E,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,MAAM,gCAAc,CAAC,gBAAgB,EAAE,CAAC;QAC1C,CAAC;QAED,iEAAiE;QAChE,IAAY,CAAC,uBAAuB,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CAAC,CAAC;QAClF,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA9BY,oDAAoB;+BAApB,oBAAoB;IADhC,IAAA,mBAAU,GAAE;qCAGgB,iCAAkB;QACd,sCAAiB;GAHrC,oBAAoB,CA8BhC"}
@@ -39,12 +39,31 @@ let JwtAuthGuard = class JwtAuthGuard {
39
39
  throw rhino_exception_1.RhinoException.unauthorized('Missing bearer token');
40
40
  }
41
41
  const payload = this.auth.verifyToken(token);
42
+ if (await this.auth.isTokenRevoked(token)) {
43
+ throw rhino_exception_1.RhinoException.unauthorized('Token has been revoked');
44
+ }
42
45
  const userModel = this.config.authConfig().userModel;
43
46
  const delegate = this.prisma.model(userModel);
44
- const user = await delegate.findUnique({
45
- where: { id: payload.sub },
46
- include: { userRoles: { include: { role: true } } },
47
- });
47
+ // Only eager-load userRoles when multi-tenancy is configured. A
48
+ // single-tenant / org-less app's User model has no `userRoles` relation, so
49
+ // an unconditional include throws inside Prisma which would make every
50
+ // authenticated request fail with a 401 even though the token is valid.
51
+ // Gate the include and fall back to a plain lookup if it fails. Multi-tenant
52
+ // behavior is byte-for-byte unchanged.
53
+ let user = null;
54
+ if (this.config.multiTenantEnabled()) {
55
+ user = await delegate
56
+ .findUnique({
57
+ where: { id: payload.sub },
58
+ include: { userRoles: { include: { role: true } } },
59
+ })
60
+ .catch(() => null);
61
+ }
62
+ if (!user) {
63
+ user = await delegate
64
+ .findUnique({ where: { id: payload.sub } })
65
+ .catch(() => null);
66
+ }
48
67
  if (!user)
49
68
  throw rhino_exception_1.RhinoException.unauthorized('User not found');
50
69
  req.user = user;
@@ -1 +1 @@
1
- {"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../src/guards/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAIwB;AACxB,2DAAuD;AACvD,6DAAyD;AACzD,kDAAqD;AACrD,+DAA2D;AAG3D;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEJ;IACA;IACA;IAHnB,YACmB,IAAiB,EACjB,MAAqB,EACrB,MAA0B;QAF1B,SAAI,GAAJ,IAAI,CAAa;QACjB,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAoB;IAC1C,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAC9D,IAAI,GAAG,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,gCAAc,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC7C,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,UAAU,CAAC;YACrC,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE;YAC1B,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;SACpD,CAAC,CAAC;QACH,IAAI,CAAC,IAAI;YAAE,MAAM,gCAAc,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;QAC/D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA1BY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAGc,0BAAW;QACT,8BAAa;QACb,iCAAkB;GAJlC,YAAY,CA0BxB"}
1
+ {"version":3,"file":"jwt-auth.guard.js","sourceRoot":"","sources":["../../src/guards/jwt-auth.guard.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAIwB;AACxB,2DAAuD;AACvD,6DAAyD;AACzD,kDAAqD;AACrD,+DAA2D;AAG3D;;;;GAIG;AAEI,IAAM,YAAY,GAAlB,MAAM,YAAY;IAEJ;IACA;IACA;IAHnB,YACmB,IAAiB,EACjB,MAAqB,EACrB,MAA0B;QAF1B,SAAI,GAAJ,IAAI,CAAa;QACjB,WAAM,GAAN,MAAM,CAAe;QACrB,WAAM,GAAN,MAAM,CAAoB;IAC1C,CAAC;IAEJ,KAAK,CAAC,WAAW,CAAC,OAAyB;QACzC,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,UAAU,EAAgB,CAAC;QAC9D,IAAI,GAAG,CAAC,UAAU;YAAE,OAAO,IAAI,CAAC;QAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,aAAa,IAAI,EAAE,CAAC,CAAC;QACxD,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC1C,IAAI,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC;YAClC,MAAM,gCAAc,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;QAC7C,IAAI,MAAM,IAAI,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,MAAM,gCAAc,CAAC,YAAY,CAAC,wBAAwB,CAAC,CAAC;QAC9D,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,SAAS,CAAC;QACrD,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QAC9C,gEAAgE;QAChE,4EAA4E;QAC5E,yEAAyE;QACzE,wEAAwE;QACxE,6EAA6E;QAC7E,uCAAuC;QACvC,IAAI,IAAI,GAAQ,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,EAAE,EAAE,CAAC;YACrC,IAAI,GAAG,MAAM,QAAQ;iBAClB,UAAU,CAAC;gBACV,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE;gBAC1B,OAAO,EAAE,EAAE,SAAS,EAAE,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE;aACpD,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,IAAI,GAAG,MAAM,QAAQ;iBAClB,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;iBAC1C,KAAK,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,IAAI,CAAC,IAAI;YAAE,MAAM,gCAAc,CAAC,YAAY,CAAC,gBAAgB,CAAC,CAAC;QAC/D,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;CACF,CAAA;AA7CY,oCAAY;uBAAZ,YAAY;IADxB,IAAA,mBAAU,GAAE;qCAGc,0BAAW;QACT,8BAAa;QACb,iCAAkB;GAJlC,YAAY,CA6CxB"}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { RhinoModule, type RhinoModuleOptions } from './rhino.module';
2
- export { RhinoException, type RhinoErrorCode, type RhinoErrorBody, } from './errors/rhino-exception';
2
+ export { RhinoException, RhinoAuthRejected, type RhinoErrorCode, type RhinoErrorBody, } from './errors/rhino-exception';
3
3
  export type { RhinoRequest } from './interfaces/rhino-request.interface';
4
4
  export { RhinoConfigService, normalizeConfig, } from './rhino.config';
5
5
  export { RHINO_CONFIG, RHINO_PRISMA_CLIENT, RHINO_MODULE_OPTIONS, } from './constants/tokens';
@@ -7,11 +7,13 @@ export * from './interfaces/rhino-config.interface';
7
7
  export { ResourcePolicy } from './policies/resource-policy';
8
8
  export { JwtAuthGuard } from './guards/jwt-auth.guard';
9
9
  export { ResourcePolicyGuard } from './guards/resource-policy.guard';
10
+ export { GroupMembershipGuard } from './guards/group-membership.guard';
10
11
  export { ResponseInterceptor, paginated } from './interceptors/response.interceptor';
11
12
  export { HiddenColumnsInterceptor } from './interceptors/hidden-columns.interceptor';
12
13
  export { ResolveOrganizationMiddleware } from './middleware/resolve-organization.middleware';
13
14
  export { RouteGroupMiddleware } from './middleware/route-group.middleware';
14
15
  export { createTenantRouteRewrite, type TenantRouteRewriteOptions, } from './middleware/tenant-route-rewrite';
16
+ export { createDomainRouteResolver, type DomainRouteResolverOptions, } from './middleware/domain-route-resolver';
15
17
  export { GlobalController } from './controllers/global.controller';
16
18
  export { AuthController } from './controllers/auth.controller';
17
19
  export { InvitationController } from './controllers/invitation.controller';
@@ -24,6 +26,8 @@ export { ValidationService } from './services/validation.service';
24
26
  export { AuditService } from './services/audit.service';
25
27
  export { AuthService } from './services/auth.service';
26
28
  export { InvitationService } from './services/invitation.service';
29
+ export { AuthHooksService, type AuthHookEvent } from './services/auth-hooks.service';
30
+ export { MembershipService, type MembershipRow, } from './services/membership.service';
27
31
  export { NestedService } from './services/nested.service';
28
32
  export { OrganizationService } from './services/organization.service';
29
33
  export { ScopeService, type RhinoScope } from './services/scope.service';
@@ -33,6 +37,8 @@ export { withUuid } from './prisma/prisma-uuid.extension';
33
37
  export { RhinoModel, getRhinoModelMetadata, BelongsToOrganization, HasAuditTrail, HasUuid, HasSoftDeletes, ExceptActions, HidableColumns, PermittedAttrs, } from './decorators';
34
38
  export { coercePermissions, matchesPermission, resolveUserRoleSlug, resolveUserPermissions, userHasPermission, } from './utils/permission-matcher';
35
39
  export { formatPrice, type CurrencyCode } from './utils/format';
40
+ export { compileDomain, matchDomain, type CompiledDomain, type DomainMatch, } from './utils/domain-pattern';
41
+ export { validateRouteGroups, RouteGroupConflictError, } from './utils/route-group-validator';
36
42
  export { autoDiscoverScopes, type ScopeDiscoveryOptions } from './utils/scope-discovery';
37
43
  export { defineModel, type ModelDefinition } from './utils/model-builder';
38
44
  export { findOrganizationFkChain, type FkRelation, type FkChainStep, type WalkOptions, } from './utils/fk-chain-walker';
package/dist/index.js CHANGED
@@ -14,14 +14,15 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.userHasPermission = exports.resolveUserPermissions = exports.resolveUserRoleSlug = exports.matchesPermission = exports.coercePermissions = exports.PermittedAttrs = exports.HidableColumns = exports.ExceptActions = exports.HasSoftDeletes = exports.HasUuid = exports.HasAuditTrail = exports.BelongsToOrganization = exports.getRhinoModelMetadata = exports.RhinoModel = exports.withUuid = exports.withSoftDelete = exports.describeRoutes = exports.applyRhinoRouting = exports.ScopeService = exports.OrganizationService = exports.NestedService = exports.InvitationService = exports.AuthService = exports.AuditService = exports.ValidationService = exports.BASE_HIDDEN_COLUMNS = exports.SerializerService = exports.QueryBuilderService = exports.ResourceService = exports.PrismaService = exports.NestedController = exports.InvitationController = exports.AuthController = exports.GlobalController = exports.createTenantRouteRewrite = exports.RouteGroupMiddleware = exports.ResolveOrganizationMiddleware = exports.HiddenColumnsInterceptor = exports.paginated = exports.ResponseInterceptor = exports.ResourcePolicyGuard = exports.JwtAuthGuard = exports.ResourcePolicy = exports.RHINO_MODULE_OPTIONS = exports.RHINO_PRISMA_CLIENT = exports.RHINO_CONFIG = exports.normalizeConfig = exports.RhinoConfigService = exports.RhinoException = exports.RhinoModule = void 0;
18
- exports.SeederGenerator = exports.TestGenerator = exports.PolicyGenerator = exports.ResourceDefinitionGenerator = exports.PrismaSchemaGenerator = exports.BlueprintRunner = exports.ManifestManager = exports.BlueprintValidator = exports.BlueprintParser = exports.appendIfMissing = exports.writeFileSafely = exports.fileExists = exports.createReadlineInterface = exports.selectFromList = exports.confirm = exports.ask = exports.runExportTypes = exports.runExportPostman = exports.runBlueprint = exports.runGenerate = exports.runInstall = exports.printHelp = exports.extractCommand = exports.parseFlags = exports.generateTypeScriptTypes = exports.generatePostmanCollection = exports.findOrganizationFkChain = exports.defineModel = exports.autoDiscoverScopes = exports.formatPrice = void 0;
17
+ exports.PermittedAttrs = exports.HidableColumns = exports.ExceptActions = exports.HasSoftDeletes = exports.HasUuid = exports.HasAuditTrail = exports.BelongsToOrganization = exports.getRhinoModelMetadata = exports.RhinoModel = exports.withUuid = exports.withSoftDelete = exports.describeRoutes = exports.applyRhinoRouting = exports.ScopeService = exports.OrganizationService = exports.NestedService = exports.MembershipService = exports.AuthHooksService = exports.InvitationService = exports.AuthService = exports.AuditService = exports.ValidationService = exports.BASE_HIDDEN_COLUMNS = exports.SerializerService = exports.QueryBuilderService = exports.ResourceService = exports.PrismaService = exports.NestedController = exports.InvitationController = exports.AuthController = exports.GlobalController = exports.createDomainRouteResolver = exports.createTenantRouteRewrite = exports.RouteGroupMiddleware = exports.ResolveOrganizationMiddleware = exports.HiddenColumnsInterceptor = exports.paginated = exports.ResponseInterceptor = exports.GroupMembershipGuard = exports.ResourcePolicyGuard = exports.JwtAuthGuard = exports.ResourcePolicy = exports.RHINO_MODULE_OPTIONS = exports.RHINO_PRISMA_CLIENT = exports.RHINO_CONFIG = exports.normalizeConfig = exports.RhinoConfigService = exports.RhinoAuthRejected = exports.RhinoException = exports.RhinoModule = void 0;
18
+ exports.SeederGenerator = exports.TestGenerator = exports.PolicyGenerator = exports.ResourceDefinitionGenerator = exports.PrismaSchemaGenerator = exports.BlueprintRunner = exports.ManifestManager = exports.BlueprintValidator = exports.BlueprintParser = exports.appendIfMissing = exports.writeFileSafely = exports.fileExists = exports.createReadlineInterface = exports.selectFromList = exports.confirm = exports.ask = exports.runExportTypes = exports.runExportPostman = exports.runBlueprint = exports.runGenerate = exports.runInstall = exports.printHelp = exports.extractCommand = exports.parseFlags = exports.generateTypeScriptTypes = exports.generatePostmanCollection = exports.findOrganizationFkChain = exports.defineModel = exports.autoDiscoverScopes = exports.RouteGroupConflictError = exports.validateRouteGroups = exports.matchDomain = exports.compileDomain = exports.formatPrice = exports.userHasPermission = exports.resolveUserPermissions = exports.resolveUserRoleSlug = exports.matchesPermission = exports.coercePermissions = void 0;
19
19
  // Module
20
20
  var rhino_module_1 = require("./rhino.module");
21
21
  Object.defineProperty(exports, "RhinoModule", { enumerable: true, get: function () { return rhino_module_1.RhinoModule; } });
22
22
  // Errors
23
23
  var rhino_exception_1 = require("./errors/rhino-exception");
24
24
  Object.defineProperty(exports, "RhinoException", { enumerable: true, get: function () { return rhino_exception_1.RhinoException; } });
25
+ Object.defineProperty(exports, "RhinoAuthRejected", { enumerable: true, get: function () { return rhino_exception_1.RhinoAuthRejected; } });
25
26
  var rhino_config_1 = require("./rhino.config");
26
27
  Object.defineProperty(exports, "RhinoConfigService", { enumerable: true, get: function () { return rhino_config_1.RhinoConfigService; } });
27
28
  Object.defineProperty(exports, "normalizeConfig", { enumerable: true, get: function () { return rhino_config_1.normalizeConfig; } });
@@ -40,6 +41,8 @@ var jwt_auth_guard_1 = require("./guards/jwt-auth.guard");
40
41
  Object.defineProperty(exports, "JwtAuthGuard", { enumerable: true, get: function () { return jwt_auth_guard_1.JwtAuthGuard; } });
41
42
  var resource_policy_guard_1 = require("./guards/resource-policy.guard");
42
43
  Object.defineProperty(exports, "ResourcePolicyGuard", { enumerable: true, get: function () { return resource_policy_guard_1.ResourcePolicyGuard; } });
44
+ var group_membership_guard_1 = require("./guards/group-membership.guard");
45
+ Object.defineProperty(exports, "GroupMembershipGuard", { enumerable: true, get: function () { return group_membership_guard_1.GroupMembershipGuard; } });
43
46
  // Interceptors
44
47
  var response_interceptor_1 = require("./interceptors/response.interceptor");
45
48
  Object.defineProperty(exports, "ResponseInterceptor", { enumerable: true, get: function () { return response_interceptor_1.ResponseInterceptor; } });
@@ -53,6 +56,8 @@ var route_group_middleware_1 = require("./middleware/route-group.middleware");
53
56
  Object.defineProperty(exports, "RouteGroupMiddleware", { enumerable: true, get: function () { return route_group_middleware_1.RouteGroupMiddleware; } });
54
57
  var tenant_route_rewrite_1 = require("./middleware/tenant-route-rewrite");
55
58
  Object.defineProperty(exports, "createTenantRouteRewrite", { enumerable: true, get: function () { return tenant_route_rewrite_1.createTenantRouteRewrite; } });
59
+ var domain_route_resolver_1 = require("./middleware/domain-route-resolver");
60
+ Object.defineProperty(exports, "createDomainRouteResolver", { enumerable: true, get: function () { return domain_route_resolver_1.createDomainRouteResolver; } });
56
61
  // Controllers (consuming apps can re-export under their own routes)
57
62
  var global_controller_1 = require("./controllers/global.controller");
58
63
  Object.defineProperty(exports, "GlobalController", { enumerable: true, get: function () { return global_controller_1.GlobalController; } });
@@ -80,6 +85,10 @@ var auth_service_1 = require("./services/auth.service");
80
85
  Object.defineProperty(exports, "AuthService", { enumerable: true, get: function () { return auth_service_1.AuthService; } });
81
86
  var invitation_service_1 = require("./services/invitation.service");
82
87
  Object.defineProperty(exports, "InvitationService", { enumerable: true, get: function () { return invitation_service_1.InvitationService; } });
88
+ var auth_hooks_service_1 = require("./services/auth-hooks.service");
89
+ Object.defineProperty(exports, "AuthHooksService", { enumerable: true, get: function () { return auth_hooks_service_1.AuthHooksService; } });
90
+ var membership_service_1 = require("./services/membership.service");
91
+ Object.defineProperty(exports, "MembershipService", { enumerable: true, get: function () { return membership_service_1.MembershipService; } });
83
92
  var nested_service_1 = require("./services/nested.service");
84
93
  Object.defineProperty(exports, "NestedService", { enumerable: true, get: function () { return nested_service_1.NestedService; } });
85
94
  var organization_service_1 = require("./services/organization.service");
@@ -114,6 +123,12 @@ Object.defineProperty(exports, "resolveUserPermissions", { enumerable: true, get
114
123
  Object.defineProperty(exports, "userHasPermission", { enumerable: true, get: function () { return permission_matcher_1.userHasPermission; } });
115
124
  var format_1 = require("./utils/format");
116
125
  Object.defineProperty(exports, "formatPrice", { enumerable: true, get: function () { return format_1.formatPrice; } });
126
+ var domain_pattern_1 = require("./utils/domain-pattern");
127
+ Object.defineProperty(exports, "compileDomain", { enumerable: true, get: function () { return domain_pattern_1.compileDomain; } });
128
+ Object.defineProperty(exports, "matchDomain", { enumerable: true, get: function () { return domain_pattern_1.matchDomain; } });
129
+ var route_group_validator_1 = require("./utils/route-group-validator");
130
+ Object.defineProperty(exports, "validateRouteGroups", { enumerable: true, get: function () { return route_group_validator_1.validateRouteGroups; } });
131
+ Object.defineProperty(exports, "RouteGroupConflictError", { enumerable: true, get: function () { return route_group_validator_1.RouteGroupConflictError; } });
117
132
  var scope_discovery_1 = require("./utils/scope-discovery");
118
133
  Object.defineProperty(exports, "autoDiscoverScopes", { enumerable: true, get: function () { return scope_discovery_1.autoDiscoverScopes; } });
119
134
  var model_builder_1 = require("./utils/model-builder");
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -36,13 +36,74 @@ export interface ModelRegistration {
36
36
  model: string;
37
37
  }>;
38
38
  }
39
+ /**
40
+ * Context handed to every lifecycle hook. `routeGroup` is the resolved group
41
+ * name (or `null`/`undefined` for the legacy/global auth path), `organization`
42
+ * is present only for tenant groups, `token` is the just-issued JWT for
43
+ * token-issuing actions (login/register), and `request` is the raw request.
44
+ */
45
+ export interface AuthHookContext {
46
+ user: any;
47
+ routeGroup?: string | null;
48
+ organization?: any;
49
+ token?: string;
50
+ request?: any;
51
+ }
52
+ /**
53
+ * Per-group lifecycle hooks. Each method runs AFTER the corresponding auth
54
+ * action succeeds. A method may reject by throwing `RhinoAuthRejected` (or any
55
+ * error) — for token-issuing actions the controller revokes the issued token
56
+ * and returns the rejection's status (default 403). All methods are optional;
57
+ * an absent method is a no-op. Implementations are registered per group via
58
+ * `RouteGroupConfig.hooks` and resolved from the Nest DI container.
59
+ */
60
+ export interface AuthLifecycleHooks {
61
+ afterLogin?(ctx: AuthHookContext): void | Promise<void>;
62
+ afterLogout?(ctx: AuthHookContext): void | Promise<void>;
63
+ afterRegister?(ctx: AuthHookContext): void | Promise<void>;
64
+ afterPasswordRecover?(ctx: AuthHookContext): void | Promise<void>;
65
+ afterPasswordReset?(ctx: AuthHookContext): void | Promise<void>;
66
+ }
39
67
  export interface RouteGroupConfig {
40
68
  prefix?: string;
69
+ /**
70
+ * Constrain this group to a specific host. Two groups can then share the
71
+ * same URL prefix and be selected by host.
72
+ *
73
+ * - Omitted → the group matches any host (default; backward compatible).
74
+ * - Literal host, e.g. `'admin.example.com'` → requests to that host
75
+ * resolve to this group; requests from a non-matching host are rejected.
76
+ * - Parameterized host, e.g. `'{organization}.example.com'` → the captured
77
+ * `{organization}` subdomain feeds organization resolution, exactly like
78
+ * a path-prefix tenant param. Matches Laravel's `Route::domain(...)`.
79
+ */
80
+ domain?: string;
41
81
  middleware?: Type<NestMiddleware>[];
42
82
  /** '*' = all registered models, array = subset by slug */
43
83
  models: '*' | string[];
44
84
  /** Skip the default JWT guard for this group (for public routes) */
45
85
  skipAuth?: boolean;
86
+ /**
87
+ * Register the full auth route set (login/logout/password/register) for this
88
+ * group, tagged with the group's name (Decision 9.A). The legacy unprefixed
89
+ * `/auth/*` set always remains for the default/global path. Opt-in; default
90
+ * `false`. The `public` group is never auth-enabled.
91
+ */
92
+ auth?: boolean;
93
+ /**
94
+ * Optional per-group lifecycle hooks. A class (resolved via Nest DI) or a
95
+ * plain object implementing {@link AuthLifecycleHooks}. Runs after each auth
96
+ * action for requests resolved to this group; may reject to revoke the token.
97
+ */
98
+ hooks?: Type<AuthLifecycleHooks> | AuthLifecycleHooks;
99
+ /**
100
+ * Whether this group is org-scoped (a tenant group). Tenant-group membership
101
+ * rows require an organization; non-tenant groups (e.g. `admin`, `driver`)
102
+ * store a NULL org. When omitted, a group is treated as a tenant group iff
103
+ * multi-tenancy is enabled. Set `tenant: false` for org-less groups even when
104
+ * multi-tenancy is on.
105
+ */
106
+ tenant?: boolean;
46
107
  }
47
108
  export interface MultiTenantConfig {
48
109
  enabled?: boolean;
@@ -66,6 +127,16 @@ export interface AuthConfig {
66
127
  userModel?: string;
67
128
  passwordField?: string;
68
129
  emailField?: string;
130
+ /**
131
+ * Master flag (default `false`) gating group-membership enforcement. When
132
+ * off, behavior is byte-for-byte unchanged: no membership check, permissions
133
+ * resolve from the org-presence heuristic. When on, an authenticated user
134
+ * must hold a `user_roles` membership row matching the request's
135
+ * `route_group` (a NULL row is a wildcard — Decision 9.B) and, for tenant
136
+ * groups, the resolved org; no match → 403 (Decision 9.C). Permissions then
137
+ * resolve from the matched row.
138
+ */
139
+ enforceGroupMembership?: boolean;
69
140
  }
70
141
  export interface PostmanConfig {
71
142
  roleModel?: string;
@@ -0,0 +1,54 @@
1
+ import type { Request, Response, NextFunction } from 'express';
2
+ import type { RhinoConfig } from '../interfaces/rhino-config.interface';
3
+ import type { PrismaClientLike } from '../prisma/prisma.service';
4
+ export interface DomainRouteResolverOptions {
5
+ /**
6
+ * When `true` (default), a request whose host matches a parameterized
7
+ * domain group but whose captured subdomain resolves to no known
8
+ * organization → HTTP 404. When `false`, the request passes through
9
+ * unchanged (org simply isn't attached).
10
+ */
11
+ strict?: boolean;
12
+ /**
13
+ * When `true` (default), after resolving the org the resolver verifies the
14
+ * authenticated user (if any) is a member. Non-members → 404 (matches the
15
+ * tenant-route-rewrite contract: cross-tenant = "not found", no
16
+ * enumeration). When `req.user` is absent the check is skipped (auth runs
17
+ * later).
18
+ */
19
+ enforceMembership?: boolean;
20
+ }
21
+ /**
22
+ * Factory for an Express-level middleware that implements host-based route
23
+ * group resolution — the subdomain-multitenancy analogue of
24
+ * `createTenantRouteRewrite`.
25
+ *
26
+ * Context: NestJS serves every model through a single GlobalController, and a
27
+ * route group is resolved at request time. For a parameterized domain group
28
+ * (`{organization}.example.com`), the captured subdomain must be turned into a
29
+ * resolved `req.organization` BEFORE the controller runs — exactly like the
30
+ * path-based `/api/<slug>/...` rewrite does for the `:organization` prefix.
31
+ *
32
+ * Behavior:
33
+ *
34
+ * 1. For each route group with a `domain`, compile its pattern.
35
+ * 2. On each request, match `req.hostname` against those patterns.
36
+ * - No domain group matches → next() (plain prefix routing applies).
37
+ * 3. When a parameterized domain group matches, take the captured
38
+ * `{organization}` (or `{org}`) subdomain and:
39
+ * - expose it on `req.params.organization`,
40
+ * - resolve the org by `organizationIdentifierColumn` (cached),
41
+ * strict-404 when unknown,
42
+ * - enforce membership when `req.user` is present,
43
+ * - attach `req.organization`.
44
+ * A literal domain group matches but captures no params, so it only marks
45
+ * the host as recognized (no org resolution).
46
+ *
47
+ * Intended for raw Express `app.use(createDomainRouteResolver(...))` BEFORE
48
+ * `applyRhinoRouting(...)`, alongside / instead of `createTenantRouteRewrite`.
49
+ */
50
+ export declare function createDomainRouteResolver(args: {
51
+ prisma: PrismaClientLike;
52
+ config: RhinoConfig;
53
+ options?: DomainRouteResolverOptions;
54
+ }): (req: Request, res: Response, next: NextFunction) => Promise<void>;
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDomainRouteResolver = createDomainRouteResolver;
4
+ const domain_pattern_1 = require("../utils/domain-pattern");
5
+ /**
6
+ * Factory for an Express-level middleware that implements host-based route
7
+ * group resolution — the subdomain-multitenancy analogue of
8
+ * `createTenantRouteRewrite`.
9
+ *
10
+ * Context: NestJS serves every model through a single GlobalController, and a
11
+ * route group is resolved at request time. For a parameterized domain group
12
+ * (`{organization}.example.com`), the captured subdomain must be turned into a
13
+ * resolved `req.organization` BEFORE the controller runs — exactly like the
14
+ * path-based `/api/<slug>/...` rewrite does for the `:organization` prefix.
15
+ *
16
+ * Behavior:
17
+ *
18
+ * 1. For each route group with a `domain`, compile its pattern.
19
+ * 2. On each request, match `req.hostname` against those patterns.
20
+ * - No domain group matches → next() (plain prefix routing applies).
21
+ * 3. When a parameterized domain group matches, take the captured
22
+ * `{organization}` (or `{org}`) subdomain and:
23
+ * - expose it on `req.params.organization`,
24
+ * - resolve the org by `organizationIdentifierColumn` (cached),
25
+ * strict-404 when unknown,
26
+ * - enforce membership when `req.user` is present,
27
+ * - attach `req.organization`.
28
+ * A literal domain group matches but captures no params, so it only marks
29
+ * the host as recognized (no org resolution).
30
+ *
31
+ * Intended for raw Express `app.use(createDomainRouteResolver(...))` BEFORE
32
+ * `applyRhinoRouting(...)`, alongside / instead of `createTenantRouteRewrite`.
33
+ */
34
+ function createDomainRouteResolver(args) {
35
+ const { strict = true, enforceMembership = true } = args.options ?? {};
36
+ // FIX 11.2: when group-membership enforcement is ON, an authenticated
37
+ // non-member must get a 403 from GroupMembershipGuard, which takes precedence
38
+ // over this resolver's cross-tenant 404. So we resolve & attach the org but
39
+ // skip the membership-404 here, deferring to the downstream guard. A
40
+ // genuinely unknown subdomain still 404s (strict). When enforcement is OFF
41
+ // (default), behavior is byte-for-byte unchanged.
42
+ const groupMembershipEnforced = args.config.auth?.enforceGroupMembership === true;
43
+ const orgModel = args.config.multiTenant?.organizationModel ?? 'organization';
44
+ const userOrgModel = args.config.multiTenant?.userOrganizationModel ?? 'userRole';
45
+ const idColumn = args.config.multiTenant?.organizationIdentifierColumn ?? 'slug';
46
+ const compiledGroups = [];
47
+ for (const [name, group] of Object.entries(args.config.routeGroups ?? {})) {
48
+ const compiled = (0, domain_pattern_1.compileDomain)(group.domain);
49
+ if (compiled)
50
+ compiledGroups.push({ name, group, compiled });
51
+ }
52
+ // Cache org lookups by captured subdomain value. `null` = probed, not found.
53
+ const cache = new Map();
54
+ const notFound = (res) => {
55
+ res.status(404).json({ code: 'NOT_FOUND', message: 'Organization not found' });
56
+ };
57
+ return async function domainRouteResolver(req, res, next) {
58
+ if (compiledGroups.length === 0)
59
+ return next();
60
+ try {
61
+ const host = resolveHost(req);
62
+ if (!host)
63
+ return next();
64
+ let matched = null;
65
+ for (const cg of compiledGroups) {
66
+ const result = (0, domain_pattern_1.matchDomain)(cg.compiled, host);
67
+ if (result) {
68
+ matched = { group: cg, params: result.params };
69
+ break;
70
+ }
71
+ }
72
+ if (!matched)
73
+ return next();
74
+ // Mark which group the host resolved to (parity with RouteGroupMiddleware).
75
+ req.__routeGroup = matched.group.name;
76
+ if (matched.group.group.skipAuth)
77
+ req.__skipAuth = true;
78
+ const rawSubdomain = matched.params.organization ?? matched.params.org;
79
+ if (rawSubdomain == null) {
80
+ // Literal domain group — no tenant to resolve.
81
+ return next();
82
+ }
83
+ // DNS hostnames are case-insensitive; orgs are slugged lowercase. Normalize
84
+ // the captured subdomain before the lookup, the cache key, and the param so
85
+ // `ACME.example.com` resolves the `acme` org consistently.
86
+ const subdomain = rawSubdomain.toLowerCase();
87
+ // Expose the captured subdomain like a path param.
88
+ req.params = req.params ?? {};
89
+ if (req.params.organization == null) {
90
+ req.params.organization = subdomain;
91
+ }
92
+ // Resolve the org from the subdomain (cached).
93
+ if (!cache.has(subdomain)) {
94
+ const delegate = args.prisma[camel(orgModel)] ?? args.prisma[orgModel];
95
+ if (!delegate?.findFirst) {
96
+ // No org delegate — can't resolve. Pass through.
97
+ return next();
98
+ }
99
+ const org = await delegate
100
+ .findFirst({ where: { [idColumn]: subdomain } })
101
+ .catch(() => null);
102
+ cache.set(subdomain, org ?? null);
103
+ }
104
+ const org = cache.get(subdomain);
105
+ if (!org) {
106
+ if (strict)
107
+ return notFound(res);
108
+ return next();
109
+ }
110
+ // Membership check (cross-tenant → 404, not 403). Skipped when group
111
+ // membership enforcement is ON — the GroupMembershipGuard returns 403 for
112
+ // an authenticated non-member, which takes precedence (FIX 11.2).
113
+ if (enforceMembership && !groupMembershipEnforced && req.user) {
114
+ const userId = req.user.id;
115
+ const urDelegate = args.prisma[camel(userOrgModel)] ?? args.prisma[userOrgModel];
116
+ if (urDelegate?.findFirst) {
117
+ const member = await urDelegate
118
+ .findFirst({ where: { userId, organizationId: org.id } })
119
+ .catch(() => null);
120
+ if (!member)
121
+ return notFound(res);
122
+ }
123
+ }
124
+ req.organization = org;
125
+ req.__orgSubdomain = subdomain;
126
+ return next();
127
+ }
128
+ catch (err) {
129
+ return next(err);
130
+ }
131
+ };
132
+ }
133
+ function resolveHost(req) {
134
+ const fromExpress = req.hostname;
135
+ if (fromExpress)
136
+ return String(fromExpress).split(':')[0];
137
+ const header = req.headers?.host ?? req.host;
138
+ return header ? String(header).split(':')[0] : undefined;
139
+ }
140
+ function camel(name) {
141
+ if (!name)
142
+ return name;
143
+ return name.charAt(0).toLowerCase() + name.slice(1);
144
+ }
145
+ //# sourceMappingURL=domain-route-resolver.js.map