@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,36 @@
1
+ /**
2
+ * Domain pattern matching for route-group `domain` constraints.
3
+ *
4
+ * A route group's `domain` can be:
5
+ * - a literal host, e.g. `admin.example.com` (exact, case-insensitive match)
6
+ * - a parameterized host, e.g. `{organization}.example.com`, where the
7
+ * `{name}` segment is captured and exposed so that organization resolution
8
+ * can use it (subdomain multitenancy).
9
+ *
10
+ * Mirrors Laravel's `Route::domain(...)`, where a `{param}` in the domain is
11
+ * exposed as a route parameter that flows into ResolveOrganizationFromRoute.
12
+ */
13
+ export interface CompiledDomain {
14
+ /** The raw pattern (as configured). */
15
+ pattern: string;
16
+ /** Whether the pattern contains at least one `{param}` placeholder. */
17
+ parameterized: boolean;
18
+ /** Ordered list of parameter names captured by the pattern. */
19
+ params: string[];
20
+ /** Anchored, case-insensitive regex used to test a host. */
21
+ regex: RegExp;
22
+ }
23
+ /**
24
+ * Compile a `domain` pattern into a matcher. Returns `null` for an empty /
25
+ * missing pattern (meaning "match any host").
26
+ */
27
+ export declare function compileDomain(pattern: string | undefined | null): CompiledDomain | null;
28
+ export interface DomainMatch {
29
+ /** Captured `{param}` values keyed by name (empty for literal domains). */
30
+ params: Record<string, string>;
31
+ }
32
+ /**
33
+ * Test a host against a compiled domain pattern. Returns the captured params
34
+ * on success, or `null` when the host does not match.
35
+ */
36
+ export declare function matchDomain(compiled: CompiledDomain, host: string | undefined | null): DomainMatch | null;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ /**
3
+ * Domain pattern matching for route-group `domain` constraints.
4
+ *
5
+ * A route group's `domain` can be:
6
+ * - a literal host, e.g. `admin.example.com` (exact, case-insensitive match)
7
+ * - a parameterized host, e.g. `{organization}.example.com`, where the
8
+ * `{name}` segment is captured and exposed so that organization resolution
9
+ * can use it (subdomain multitenancy).
10
+ *
11
+ * Mirrors Laravel's `Route::domain(...)`, where a `{param}` in the domain is
12
+ * exposed as a route parameter that flows into ResolveOrganizationFromRoute.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.compileDomain = compileDomain;
16
+ exports.matchDomain = matchDomain;
17
+ const PARAM_RE = /\{([A-Za-z_][A-Za-z0-9_]*)\}/g;
18
+ function escapeRegex(s) {
19
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
20
+ }
21
+ /**
22
+ * Compile a `domain` pattern into a matcher. Returns `null` for an empty /
23
+ * missing pattern (meaning "match any host").
24
+ */
25
+ function compileDomain(pattern) {
26
+ if (pattern == null)
27
+ return null;
28
+ const trimmed = String(pattern).trim();
29
+ if (trimmed === '')
30
+ return null;
31
+ const params = [];
32
+ let lastIndex = 0;
33
+ let source = '';
34
+ PARAM_RE.lastIndex = 0;
35
+ let m;
36
+ while ((m = PARAM_RE.exec(trimmed)) !== null) {
37
+ // literal text before the placeholder
38
+ source += escapeRegex(trimmed.slice(lastIndex, m.index));
39
+ const name = m[1];
40
+ params.push(name);
41
+ // a host label cannot contain a dot — capture a single label segment
42
+ source += `(?<${name}>[^.]+)`;
43
+ lastIndex = m.index + m[0].length;
44
+ }
45
+ source += escapeRegex(trimmed.slice(lastIndex));
46
+ return {
47
+ pattern: trimmed,
48
+ parameterized: params.length > 0,
49
+ params,
50
+ regex: new RegExp(`^${source}$`, 'i'),
51
+ };
52
+ }
53
+ /**
54
+ * Test a host against a compiled domain pattern. Returns the captured params
55
+ * on success, or `null` when the host does not match.
56
+ */
57
+ function matchDomain(compiled, host) {
58
+ if (host == null)
59
+ return null;
60
+ // Strip an optional port (Express `req.hostname` excludes it, but be safe).
61
+ const cleaned = String(host).split(':')[0];
62
+ if (cleaned === '')
63
+ return null;
64
+ const result = compiled.regex.exec(cleaned);
65
+ if (!result)
66
+ return null;
67
+ const params = {};
68
+ if (result.groups) {
69
+ for (const name of compiled.params) {
70
+ const value = result.groups[name];
71
+ if (value !== undefined)
72
+ params[name] = value;
73
+ }
74
+ }
75
+ return { params };
76
+ }
77
+ //# sourceMappingURL=domain-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain-pattern.js","sourceRoot":"","sources":["../../src/utils/domain-pattern.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;AAuBH,sCA2BC;AAWD,kCAeC;AA/DD,MAAM,QAAQ,GAAG,+BAA+B,CAAC;AAEjD,SAAS,WAAW,CAAC,CAAS;IAC5B,OAAO,CAAC,CAAC,OAAO,CAAC,qBAAqB,EAAE,MAAM,CAAC,CAAC;AAClD,CAAC;AAED;;;GAGG;AACH,SAAgB,aAAa,CAAC,OAAkC;IAC9D,IAAI,OAAO,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IACjC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACvC,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,QAAQ,CAAC,SAAS,GAAG,CAAC,CAAC;IACvB,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,sCAAsC;QACtC,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACzD,MAAM,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;QAClB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,qEAAqE;QACrE,MAAM,IAAI,MAAM,IAAI,SAAS,CAAC;QAC9B,SAAS,GAAG,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;IACpC,CAAC;IACD,MAAM,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;IAEhD,OAAO;QACL,OAAO,EAAE,OAAO;QAChB,aAAa,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC;QAChC,MAAM;QACN,KAAK,EAAE,IAAI,MAAM,CAAC,IAAI,MAAM,GAAG,EAAE,GAAG,CAAC;KACtC,CAAC;AACJ,CAAC;AAOD;;;GAGG;AACH,SAAgB,WAAW,CAAC,QAAwB,EAAE,IAA+B;IACnF,IAAI,IAAI,IAAI,IAAI;QAAE,OAAO,IAAI,CAAC;IAC9B,4EAA4E;IAC5E,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3C,IAAI,OAAO,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IAChC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC5C,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;QAClB,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;YACnC,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;YAClC,IAAI,KAAK,KAAK,SAAS;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;QAChD,CAAC;IACH,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,CAAC;AACpB,CAAC"}
@@ -91,6 +91,14 @@ function resolveUserRoleSlug(user, organizationId) {
91
91
  function resolveUserPermissions(user, organizationId) {
92
92
  if (!user)
93
93
  return [];
94
+ // Group-membership enforcement (design §6): when the membership layer has
95
+ // resolved the matching row(s), the permission source switches to that row
96
+ // only. The guard attaches the resolved list as `__membershipPermissions`.
97
+ // Absent (enforcement off) → fall through to the legacy heuristic, so
98
+ // existing behavior is byte-for-byte unchanged.
99
+ if (Array.isArray(user.__membershipPermissions)) {
100
+ return coercePermissions(user.__membershipPermissions);
101
+ }
94
102
  if (organizationId != null) {
95
103
  const userRoles = user.userRoles ?? user.user_roles ?? [];
96
104
  const all = [];
@@ -1 +1 @@
1
- {"version":3,"file":"permission-matcher.js","sourceRoot":"","sources":["../../src/utils/permission-matcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permission-matcher.js","sourceRoot":"","sources":["../../src/utils/permission-matcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { RhinoConfig } from '../interfaces/rhino-config.interface';
2
+ /**
3
+ * Thrown at config-normalization (boot) time when two route groups would
4
+ * resolve to the same routes and silently shadow one another.
5
+ *
6
+ * This happens when two groups share the same effective prefix, have
7
+ * intersecting host-sets (no `domain` = matches every host; identical domain
8
+ * pattern), and register overlapping models. Failing fast prevents a dangerous
9
+ * misconfiguration (e.g. a `skipAuth` group shadowing an authenticated one).
10
+ */
11
+ export declare class RouteGroupConflictError extends Error {
12
+ constructor(message: string);
13
+ }
14
+ export declare function validateRouteGroups(config: RhinoConfig): void;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RouteGroupConflictError = void 0;
4
+ exports.validateRouteGroups = validateRouteGroups;
5
+ /**
6
+ * Thrown at config-normalization (boot) time when two route groups would
7
+ * resolve to the same routes and silently shadow one another.
8
+ *
9
+ * This happens when two groups share the same effective prefix, have
10
+ * intersecting host-sets (no `domain` = matches every host; identical domain
11
+ * pattern), and register overlapping models. Failing fast prevents a dangerous
12
+ * misconfiguration (e.g. a `skipAuth` group shadowing an authenticated one).
13
+ */
14
+ class RouteGroupConflictError extends Error {
15
+ constructor(message) {
16
+ super(message);
17
+ this.name = 'RouteGroupConflictError';
18
+ }
19
+ }
20
+ exports.RouteGroupConflictError = RouteGroupConflictError;
21
+ function normalizePrefix(group) {
22
+ return String(group.prefix ?? '');
23
+ }
24
+ /** An undefined/blank domain means "any host". */
25
+ function normalizeDomain(group) {
26
+ const domain = group.domain;
27
+ if (domain == null || String(domain).trim() === '')
28
+ return null;
29
+ return String(domain);
30
+ }
31
+ function hostSetsIntersect(a, b) {
32
+ const da = normalizeDomain(a);
33
+ const db = normalizeDomain(b);
34
+ // A wildcard host (no domain) intersects with any other host-set.
35
+ if (da === null || db === null)
36
+ return true;
37
+ // Two explicit domain patterns intersect only when identical.
38
+ return da === db;
39
+ }
40
+ function resolveModels(group, allModels) {
41
+ if (group.models === '*')
42
+ return Object.keys(allModels ?? {});
43
+ return group.models ?? [];
44
+ }
45
+ function overlappingModels(a, b, allModels) {
46
+ const ma = new Set(resolveModels(a, allModels));
47
+ return resolveModels(b, allModels).filter((slug) => ma.has(slug));
48
+ }
49
+ function buildMessage(aName, bName, a, b, shared) {
50
+ const prefix = normalizePrefix(a);
51
+ const prefixLabel = prefix === '' ? '(root)' : `'${prefix}'`;
52
+ const da = normalizeDomain(a);
53
+ const db = normalizeDomain(b);
54
+ const domainLabel = da === null && db === null
55
+ ? 'no domain'
56
+ : `domains [${da ?? 'any'}, ${db ?? 'any'}]`;
57
+ return (`Route groups '${aName}' and '${bName}' conflict: they share prefix ` +
58
+ `${prefixLabel} with ${domainLabel} and overlapping models (${shared.join(', ')}), ` +
59
+ `so one would silently shadow the other. Give them distinct prefixes, or ` +
60
+ `distinguish them with different 'domain' values, or make their 'models' disjoint.`);
61
+ }
62
+ /**
63
+ * Validate the configured route groups and throw RouteGroupConflictError when
64
+ * two groups would silently shadow each other.
65
+ *
66
+ * A route group's routing identity is the pair (host-set, prefix), per model.
67
+ * Two groups conflict when ALL of the following hold:
68
+ *
69
+ * 1. Their host-sets intersect (a group with no `domain` is a wildcard host).
70
+ * 2. They share the same effective prefix (undefined/'' is the same root).
71
+ * 3. Their model sets overlap ('*' expands to every registered model).
72
+ *
73
+ * With a distinguishing domain, the prefix is optional (the host disambiguates);
74
+ * without a domain, the prefix is the only disambiguator, so two or more
75
+ * overlapping groups must use distinct prefixes.
76
+ *
77
+ * Note: conservative static check — exotic cross-pattern overlaps (a literal
78
+ * host that also satisfies another group's `{param}.example.com`) are not
79
+ * statically detected.
80
+ */
81
+ /**
82
+ * Whether a group registers the legacy/global (empty-prefix, no-domain) auth
83
+ * route set. Per design §11.1, such an auth-enabled group IS the default auth
84
+ * path. Two or more of them are genuinely indistinguishable — their auth routes
85
+ * (and hooks/membership) would collide with no way to tell them apart — so the
86
+ * validator must reject the config at boot.
87
+ */
88
+ function isIndistinguishableAuthGroup(group) {
89
+ return (group.auth === true &&
90
+ normalizePrefix(group) === '' &&
91
+ normalizeDomain(group) === null);
92
+ }
93
+ function validateRouteGroups(config) {
94
+ const groups = config.routeGroups ?? {};
95
+ const models = config.models ?? {};
96
+ const names = Object.keys(groups);
97
+ // FIX 11.1: two+ auth-enabled groups with empty prefix AND no domain are
98
+ // genuinely indistinguishable — they'd register the same legacy `/auth/*`
99
+ // routes. The `public` group is never auth-enabled, so it can't collide here.
100
+ const indistinguishableAuth = names.filter((n) => n !== 'public' && isIndistinguishableAuthGroup(groups[n]));
101
+ if (indistinguishableAuth.length >= 2) {
102
+ const [a, b] = indistinguishableAuth;
103
+ throw new RouteGroupConflictError(`Auth-enabled route groups '${a}' and '${b}' are indistinguishable: ` +
104
+ `both have an empty prefix and no domain, so they would register the ` +
105
+ `same legacy auth routes and their hooks/membership could not be told ` +
106
+ `apart. Give one a distinct 'prefix' or a 'domain', or disable 'auth' ` +
107
+ `on all but one.`);
108
+ }
109
+ for (let i = 0; i < names.length; i++) {
110
+ for (let j = i + 1; j < names.length; j++) {
111
+ const a = groups[names[i]];
112
+ const b = groups[names[j]];
113
+ if (!hostSetsIntersect(a, b))
114
+ continue;
115
+ if (normalizePrefix(a) !== normalizePrefix(b))
116
+ continue;
117
+ const shared = overlappingModels(a, b, models);
118
+ if (shared.length === 0)
119
+ continue;
120
+ throw new RouteGroupConflictError(buildMessage(names[i], names[j], a, b, shared));
121
+ }
122
+ }
123
+ }
124
+ //# sourceMappingURL=route-group-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-group-validator.js","sourceRoot":"","sources":["../../src/utils/route-group-validator.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rhino-dev/rhino-nestjs",
3
- "version": "4.0.0",
3
+ "version": "4.2.1",
4
4
  "description": "Rhino for NestJS — auto-generated REST APIs from model definitions.",
5
5
  "author": "Bruno Cipolla <bruno@codalio.com>",
6
6
  "license": "MIT",