@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.
- package/README.md +52 -1
- package/dist/blueprint/blueprint-runner.js +32 -1
- package/dist/blueprint/blueprint-runner.js.map +1 -1
- package/dist/blueprint/blueprint-sorter.d.ts +54 -0
- package/dist/blueprint/blueprint-sorter.js +107 -0
- package/dist/blueprint/blueprint-sorter.js.map +1 -0
- package/dist/controllers/auth.controller.d.ts +34 -5
- package/dist/controllers/auth.controller.js +132 -12
- package/dist/controllers/auth.controller.js.map +1 -1
- package/dist/controllers/invitation.controller.d.ts +41 -2
- package/dist/controllers/invitation.controller.js +97 -12
- package/dist/controllers/invitation.controller.js.map +1 -1
- package/dist/errors/rhino-exception.d.ts +12 -1
- package/dist/errors/rhino-exception.js +19 -1
- package/dist/errors/rhino-exception.js.map +1 -1
- package/dist/guards/group-membership.guard.d.ts +25 -0
- package/dist/guards/group-membership.guard.js +70 -0
- package/dist/guards/group-membership.guard.js.map +1 -0
- package/dist/guards/jwt-auth.guard.js +23 -4
- package/dist/guards/jwt-auth.guard.js.map +1 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/interfaces/rhino-config.interface.d.ts +71 -0
- package/dist/middleware/domain-route-resolver.d.ts +54 -0
- package/dist/middleware/domain-route-resolver.js +145 -0
- package/dist/middleware/domain-route-resolver.js.map +1 -0
- package/dist/middleware/route-group.middleware.d.ts +48 -4
- package/dist/middleware/route-group.middleware.js +274 -13
- package/dist/middleware/route-group.middleware.js.map +1 -1
- package/dist/rhino.config.d.ts +26 -1
- package/dist/rhino.config.js +56 -1
- package/dist/rhino.config.js.map +1 -1
- package/dist/rhino.module.d.ts +7 -0
- package/dist/rhino.module.js +23 -1
- package/dist/rhino.module.js.map +1 -1
- package/dist/services/auth-hooks.service.d.ts +27 -0
- package/dist/services/auth-hooks.service.js +85 -0
- package/dist/services/auth-hooks.service.js.map +1 -0
- package/dist/services/auth.service.d.ts +19 -0
- package/dist/services/auth.service.js +80 -5
- package/dist/services/auth.service.js.map +1 -1
- package/dist/services/invitation.service.d.ts +6 -0
- package/dist/services/invitation.service.js +45 -3
- package/dist/services/invitation.service.js.map +1 -1
- package/dist/services/membership.service.d.ts +56 -0
- package/dist/services/membership.service.js +113 -0
- package/dist/services/membership.service.js.map +1 -0
- package/dist/services/organization.service.js +8 -1
- package/dist/services/organization.service.js.map +1 -1
- package/dist/services/route-registration.service.d.ts +1 -0
- package/dist/services/route-registration.service.js +2 -0
- package/dist/services/route-registration.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/utils/domain-pattern.d.ts +36 -0
- package/dist/utils/domain-pattern.js +77 -0
- package/dist/utils/domain-pattern.js.map +1 -0
- package/dist/utils/permission-matcher.js +8 -0
- package/dist/utils/permission-matcher.js.map +1 -1
- package/dist/utils/route-group-validator.d.ts +14 -0
- package/dist/utils/route-group-validator.js +124 -0
- package/dist/utils/route-group-validator.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ That's it. You now have a full REST API for posts:
|
|
|
169
169
|
| 13 | **Eager Loading** | `?include=author,comments` with nested dot-notation. |
|
|
170
170
|
| 14 | **Multi-Tenancy** | Organization-based data isolation, auto-set `organizationId`, request scoping. |
|
|
171
171
|
| 15 | **Nested Ownership** | Auto-scopes by `organizationId` on the registered model. |
|
|
172
|
-
| 16 | **Route Groups** | Multiple URL prefixes with different middleware/auth (`tenant`, `public`, custom). |
|
|
172
|
+
| 16 | **Route Groups** | Multiple URL prefixes with different middleware/auth (`tenant`, `public`, custom). Optional per-group `domain` constrains a group to a host (literal or `{organization}.example.com` subdomain). |
|
|
173
173
|
| 17 | **Soft Deletes** | Trash, restore, force-delete endpoints with individual permissions. |
|
|
174
174
|
| 18 | **Audit Trail** | Logs all CRUD events with old/new values, user, IP, and org context. |
|
|
175
175
|
| 19 | **Nested Operations** | `POST /nested` for atomic multi-model transactions with `$N.field` references. |
|
|
@@ -182,6 +182,37 @@ That's it. You now have a full REST API for posts:
|
|
|
182
182
|
| 26 | **Generator CLI** | `rhino install`, `rhino generate`, `rhino blueprint`. |
|
|
183
183
|
| 27 | **Postman Export** | Auto-generated Postman Collection v2.1 with all endpoints. |
|
|
184
184
|
| 28 | **Blueprint System** | YAML-to-code generation for models, migrations, policies, tests, and seeders. |
|
|
185
|
+
| 29 | **Group Membership** | Opt-in `auth.enforceGroupMembership` makes a route group an access boundary. Memberships are keyed by `(user, route_group, organization, role)` on `user_roles`; a NULL `route_group` is a wildcard. No match → 403, enforced both at `/auth/login` (non-members can't sign in) and on every resource request. Permissions then resolve from the matched membership row. |
|
|
186
|
+
| 30 | **Group-Aware Auth & Lifecycle Hooks** | Per-group `auth: true` registers the auth route set under the group; per-group `hooks` (a provider implementing `AuthLifecycleHooks`) run `afterLogin/afterLogout/afterRegister/afterPasswordRecover/afterPasswordReset` and may reject (revoking the issued token). Invitations carry the `route_group`; accept populates the membership. See [Group-auth hooks & token revocation](#group-auth-hooks--token-revocation). |
|
|
187
|
+
|
|
188
|
+
### Group-auth hooks & token revocation
|
|
189
|
+
|
|
190
|
+
When a lifecycle hook **rejects** a token-issuing action (`afterLogin` /
|
|
191
|
+
`afterRegister`), Rhino does two things:
|
|
192
|
+
|
|
193
|
+
1. It **drops the issued token from the response** — the client never receives
|
|
194
|
+
it. This is the bounded, always-on guarantee.
|
|
195
|
+
2. It **attempts to denylist the token** so a copy that already leaked can no
|
|
196
|
+
longer be used. This step requires a `RevokedToken` Prisma model
|
|
197
|
+
(`token`, `createdAt`). **If no `RevokedToken` model is configured, revoke is
|
|
198
|
+
advisory only** — Rhino logs a clear WARNING and the token simply isn't
|
|
199
|
+
denylisted. Because JWTs are stateless, you should therefore:
|
|
200
|
+
|
|
201
|
+
- Provision a `RevokedToken` model if you rely on hook rejection, **and**
|
|
202
|
+
- Use **short-TTL JWTs** (set `auth.jwtExpiresIn` to a small value) so an
|
|
203
|
+
un-denylisted token expires quickly.
|
|
204
|
+
|
|
205
|
+
**Controlling the HTTP status from a hook:** a hook must throw
|
|
206
|
+
`RhinoAuthRejected` (or any NestJS `HttpException`) to control the response
|
|
207
|
+
status (default 403; the hook may set 401/409/etc.). A plain `Error` is **not**
|
|
208
|
+
an `HttpException` and surfaces as a **500** after the token is revoked — so
|
|
209
|
+
always throw `RhinoAuthRejected`/`HttpException`, never a bare `Error`, to reject.
|
|
210
|
+
|
|
211
|
+
**Password recovery is never an enumeration oracle:** `afterPasswordRecover`
|
|
212
|
+
runs for side effects only — a rejection it throws is swallowed so the recovery
|
|
213
|
+
endpoint always returns the same uniform `{ success: true }` regardless of
|
|
214
|
+
whether the email exists. (Reject semantics are kept for login/register/logout/
|
|
215
|
+
reset.)
|
|
185
216
|
|
|
186
217
|
## Configuration Reference
|
|
187
218
|
|
|
@@ -238,9 +269,29 @@ routeGroups: {
|
|
|
238
269
|
models: ['posts'],
|
|
239
270
|
skipAuth: true,
|
|
240
271
|
},
|
|
272
|
+
// Domain-aware groups: constrain a group to a specific host. Two groups can
|
|
273
|
+
// share the same prefix and be selected by host.
|
|
274
|
+
admin: {
|
|
275
|
+
domain: 'admin.example.com', // literal host — only this host serves this group
|
|
276
|
+
models: '*',
|
|
277
|
+
},
|
|
278
|
+
hostTenant: {
|
|
279
|
+
domain: '{organization}.example.com', // parameterized host — the captured
|
|
280
|
+
models: '*', // {organization} subdomain resolves the
|
|
281
|
+
}, // tenant, just like the :organization prefix
|
|
241
282
|
}
|
|
242
283
|
```
|
|
243
284
|
|
|
285
|
+
**`domain` (per-group, optional):**
|
|
286
|
+
|
|
287
|
+
- Omitted → the group matches any host (default; backward compatible).
|
|
288
|
+
- Literal host (`'admin.example.com'`) → only requests to that host resolve to the group; a wrong-host request to the group's models is rejected with `404`.
|
|
289
|
+
- Parameterized host (`'{organization}.example.com'`) → the captured `{organization}` subdomain feeds organization resolution (subdomain multitenancy), exactly like the `:organization` path prefix. Mirrors Laravel's `Route::domain(...)`.
|
|
290
|
+
|
|
291
|
+
Host-based matching is performed by `RouteGroupMiddleware`. For subdomain → organization resolution at the Express layer (analogous to `createTenantRouteRewrite`), use `createDomainRouteResolver({ prisma, config })` in `main.ts` before `applyRhinoRouting(...)`.
|
|
292
|
+
|
|
293
|
+
> **Conflicting groups fail fast.** Two groups that share the same prefix **and** overlapping models **and** an intersecting host-set would silently shadow each other. `normalizeConfig` (run by `RhinoModule.forRoot`) throws `RouteGroupConflictError` in that case. Fix it with distinct prefixes, different `domain` values, or disjoint `models`. A group without a `domain` matches every host, so it intersects with all others.
|
|
294
|
+
|
|
244
295
|
**Reserved group names:**
|
|
245
296
|
|
|
246
297
|
| Name | Behavior |
|
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const blueprint_parser_1 = require("./blueprint-parser");
|
|
41
41
|
const blueprint_validator_1 = require("./blueprint-validator");
|
|
42
|
+
const blueprint_sorter_1 = require("./blueprint-sorter");
|
|
42
43
|
const manifest_manager_1 = require("./manifest-manager");
|
|
43
44
|
const prisma_schema_generator_1 = require("./generators/prisma-schema-generator");
|
|
44
45
|
const resource_definition_generator_1 = require("./generators/resource-definition-generator");
|
|
@@ -95,7 +96,37 @@ class BlueprintRunner {
|
|
|
95
96
|
return result;
|
|
96
97
|
}
|
|
97
98
|
const manifest = new manifest_manager_1.ManifestManager(manifestDir);
|
|
98
|
-
|
|
99
|
+
// Order so a referenced model is processed before any model that
|
|
100
|
+
// foreign-keys to it (parents before children) — keeps order-sensitive
|
|
101
|
+
// output (e.g. seeders) runnable and matches the Laravel/Rails stacks, where
|
|
102
|
+
// migration timestamps follow this order.
|
|
103
|
+
const sorter = new blueprint_sorter_1.BlueprintSorter();
|
|
104
|
+
const parsedForOrder = [];
|
|
105
|
+
const unparseable = [];
|
|
106
|
+
for (const f of yamlFiles) {
|
|
107
|
+
try {
|
|
108
|
+
parsedForOrder.push({ file: f, blueprint: this.parser.parseModel(path.join(blueprintsDir, f)) });
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
unparseable.push(f);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const orderedBlueprints = sorter.sort(parsedForOrder.map((p) => p.blueprint));
|
|
115
|
+
const fileByModel = new Map();
|
|
116
|
+
for (const p of parsedForOrder) {
|
|
117
|
+
if (!fileByModel.has(p.blueprint.model)) {
|
|
118
|
+
fileByModel.set(p.blueprint.model, p.file);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const orderedYamlFiles = [
|
|
122
|
+
...orderedBlueprints.map((bp) => fileByModel.get(bp.model)).filter(Boolean),
|
|
123
|
+
...unparseable,
|
|
124
|
+
];
|
|
125
|
+
if (sorter.cycles.length > 0) {
|
|
126
|
+
this.log(` ⚠ Circular foreign-key dependency among: ${sorter.cycles.join(', ')}. ` +
|
|
127
|
+
'Order is best-effort — make one side nullable or add the FK in a later step.', options);
|
|
128
|
+
}
|
|
129
|
+
for (const filename of orderedYamlFiles) {
|
|
99
130
|
const filePath = path.join(blueprintsDir, filename);
|
|
100
131
|
try {
|
|
101
132
|
// Parse
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blueprint-runner.js","sourceRoot":"","sources":["../../src/blueprint/blueprint-runner.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"blueprint-runner.js","sourceRoot":"","sources":["../../src/blueprint/blueprint-runner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Orders blueprints so that a referenced model is processed before any model
|
|
3
|
+
* whose foreign keys point at it (parents before children).
|
|
4
|
+
*
|
|
5
|
+
* Foreign keys are taken from `foreignId` columns that carry a `foreignModel`
|
|
6
|
+
* mapping to another model in the same generation set. References that impose no
|
|
7
|
+
* ordering are ignored:
|
|
8
|
+
* - self-references (a model's FK to its own table),
|
|
9
|
+
* - references to models NOT in this set (e.g. `Organization`/`User`, created
|
|
10
|
+
* by `rhino install`, not the blueprint run).
|
|
11
|
+
*
|
|
12
|
+
* Prisma resolves relations regardless of model declaration order, but the
|
|
13
|
+
* processing order still matters for order-sensitive output (notably seeders: a
|
|
14
|
+
* child row referencing a parent must be inserted after it) and for parity with
|
|
15
|
+
* the Laravel/Rails stacks, where migration timestamps follow this order.
|
|
16
|
+
*
|
|
17
|
+
* Uses Kahn's algorithm with a stable tie-break: among models with no remaining
|
|
18
|
+
* unmet dependency, the one earliest in the input order wins. The input is
|
|
19
|
+
* already alphabetical, so the output stays alphabetical wherever relationships
|
|
20
|
+
* don't force a reorder.
|
|
21
|
+
*
|
|
22
|
+
* A circular FK dependency (A → B → A) has no linear order; such models are
|
|
23
|
+
* emitted in a deterministic best-effort order and reported via {@link cycles}
|
|
24
|
+
* so the caller can warn (one side should be a nullable / deferred FK).
|
|
25
|
+
*/
|
|
26
|
+
export interface SortableColumn {
|
|
27
|
+
type?: string;
|
|
28
|
+
foreignModel?: string | null;
|
|
29
|
+
}
|
|
30
|
+
export interface SortableBlueprint {
|
|
31
|
+
model: string;
|
|
32
|
+
columns?: SortableColumn[];
|
|
33
|
+
}
|
|
34
|
+
export declare class BlueprintSorter {
|
|
35
|
+
private _cycles;
|
|
36
|
+
/**
|
|
37
|
+
* Model names involved in a circular foreign-key dependency during the last
|
|
38
|
+
* {@link sort} (empty when the dependency graph is acyclic).
|
|
39
|
+
*/
|
|
40
|
+
get cycles(): string[];
|
|
41
|
+
/** Re-order blueprints into a valid sequence (parents first). */
|
|
42
|
+
sort<T extends SortableBlueprint>(blueprints: T[]): T[];
|
|
43
|
+
/**
|
|
44
|
+
* Whether `start` can reach itself by following dependency edges — i.e. it
|
|
45
|
+
* participates in a circular foreign-key dependency. `adj` maps a model to the
|
|
46
|
+
* models that reference it (its dependents).
|
|
47
|
+
*/
|
|
48
|
+
private reachableFromSelf;
|
|
49
|
+
/**
|
|
50
|
+
* The model names this blueprint's foreign keys point at, taken from its
|
|
51
|
+
* `foreignId` columns that carry a `foreignModel`.
|
|
52
|
+
*/
|
|
53
|
+
private dependencyModels;
|
|
54
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BlueprintSorter = void 0;
|
|
4
|
+
class BlueprintSorter {
|
|
5
|
+
_cycles = [];
|
|
6
|
+
/**
|
|
7
|
+
* Model names involved in a circular foreign-key dependency during the last
|
|
8
|
+
* {@link sort} (empty when the dependency graph is acyclic).
|
|
9
|
+
*/
|
|
10
|
+
get cycles() {
|
|
11
|
+
return this._cycles;
|
|
12
|
+
}
|
|
13
|
+
/** Re-order blueprints into a valid sequence (parents first). */
|
|
14
|
+
sort(blueprints) {
|
|
15
|
+
this._cycles = [];
|
|
16
|
+
if (blueprints.length < 2) {
|
|
17
|
+
return [...blueprints];
|
|
18
|
+
}
|
|
19
|
+
const byModel = new Map();
|
|
20
|
+
for (const bp of blueprints) {
|
|
21
|
+
if (bp.model && !byModel.has(bp.model)) {
|
|
22
|
+
byModel.set(bp.model, bp);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const dependents = new Map();
|
|
26
|
+
const indegree = new Map();
|
|
27
|
+
for (const m of byModel.keys()) {
|
|
28
|
+
dependents.set(m, []);
|
|
29
|
+
indegree.set(m, 0);
|
|
30
|
+
}
|
|
31
|
+
for (const [model, bp] of byModel) {
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
for (const ref of this.dependencyModels(bp)) {
|
|
34
|
+
if (ref === model || !byModel.has(ref) || seen.has(ref)) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
seen.add(ref);
|
|
38
|
+
dependents.get(ref).push(model);
|
|
39
|
+
indegree.set(model, (indegree.get(model) ?? 0) + 1);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const inputOrder = [...byModel.keys()];
|
|
43
|
+
// Record the models that actually participate in a cycle (reachable from
|
|
44
|
+
// themselves), in input order, so the caller can warn about the full cycle.
|
|
45
|
+
for (const model of inputOrder) {
|
|
46
|
+
if (this.reachableFromSelf(model, dependents)) {
|
|
47
|
+
this._cycles.push(model);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const ordered = [];
|
|
51
|
+
const resolved = new Set();
|
|
52
|
+
while (ordered.length < byModel.size) {
|
|
53
|
+
// Earliest-input model with all dependencies already emitted...
|
|
54
|
+
let pick = inputOrder.find((m) => !resolved.has(m) && indegree.get(m) === 0);
|
|
55
|
+
// ...or, when a cycle blocks the graph, the earliest unresolved model
|
|
56
|
+
// (deterministic cycle-break; the cycle itself is reported via cycles).
|
|
57
|
+
if (pick === undefined) {
|
|
58
|
+
pick = inputOrder.find((m) => !resolved.has(m));
|
|
59
|
+
}
|
|
60
|
+
const picked = pick;
|
|
61
|
+
ordered.push(byModel.get(picked));
|
|
62
|
+
resolved.add(picked);
|
|
63
|
+
for (const child of dependents.get(picked)) {
|
|
64
|
+
indegree.set(child, (indegree.get(child) ?? 0) - 1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return ordered;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Whether `start` can reach itself by following dependency edges — i.e. it
|
|
71
|
+
* participates in a circular foreign-key dependency. `adj` maps a model to the
|
|
72
|
+
* models that reference it (its dependents).
|
|
73
|
+
*/
|
|
74
|
+
reachableFromSelf(start, adj) {
|
|
75
|
+
const stack = [...(adj.get(start) ?? [])];
|
|
76
|
+
const visited = new Set();
|
|
77
|
+
while (stack.length > 0) {
|
|
78
|
+
const node = stack.pop();
|
|
79
|
+
if (node === start) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (visited.has(node)) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
visited.add(node);
|
|
86
|
+
for (const next of adj.get(node) ?? []) {
|
|
87
|
+
stack.push(next);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The model names this blueprint's foreign keys point at, taken from its
|
|
94
|
+
* `foreignId` columns that carry a `foreignModel`.
|
|
95
|
+
*/
|
|
96
|
+
dependencyModels(bp) {
|
|
97
|
+
const refs = [];
|
|
98
|
+
for (const col of bp.columns ?? []) {
|
|
99
|
+
if (col.type === 'foreignId' && col.foreignModel) {
|
|
100
|
+
refs.push(col.foreignModel);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return refs;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
exports.BlueprintSorter = BlueprintSorter;
|
|
107
|
+
//# sourceMappingURL=blueprint-sorter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blueprint-sorter.js","sourceRoot":"","sources":["../../src/blueprint/blueprint-sorter.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@ import { AuthService } from '../services/auth.service';
|
|
|
2
2
|
import { InvitationService } from '../services/invitation.service';
|
|
3
3
|
import { PrismaService } from '../prisma/prisma.service';
|
|
4
4
|
import { RhinoConfigService } from '../rhino.config';
|
|
5
|
+
import { AuthHooksService } from '../services/auth-hooks.service';
|
|
6
|
+
import { MembershipService } from '../services/membership.service';
|
|
5
7
|
export interface LoginDto {
|
|
6
8
|
email: string;
|
|
7
9
|
password: string;
|
|
@@ -25,31 +27,58 @@ export declare class AuthController {
|
|
|
25
27
|
private readonly invitationService;
|
|
26
28
|
private readonly prisma;
|
|
27
29
|
private readonly config;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
private readonly hooks?;
|
|
31
|
+
private readonly membership?;
|
|
32
|
+
constructor(authService: AuthService, invitationService: InvitationService, prisma: PrismaService, config: RhinoConfigService, hooks?: AuthHooksService, membership?: MembershipService);
|
|
33
|
+
/** Group resolved for this auth request (set by RouteGroupMiddleware). */
|
|
34
|
+
private routeGroup;
|
|
35
|
+
/**
|
|
36
|
+
* Run a lifecycle hook for the resolved group. `revokes` flags token-issuing
|
|
37
|
+
* actions: when the hook rejects, the issued token is dropped (revoked) and
|
|
38
|
+
* the rejection's status is re-thrown. When no hooks service is wired, this
|
|
39
|
+
* is a no-op.
|
|
40
|
+
*/
|
|
41
|
+
private runHook;
|
|
42
|
+
/**
|
|
43
|
+
* Enforce group membership at the auth boundary — parity with the
|
|
44
|
+
* GroupMembershipGuard (resource layer) and the Laravel/Rails AuthControllers.
|
|
45
|
+
*
|
|
46
|
+
* When `auth.enforceGroupMembership` is ON, a non-member must be rejected at
|
|
47
|
+
* `/auth/login` with **403** rather than being allowed to authenticate and
|
|
48
|
+
* only blocked later on the first resource request. The just-issued token is
|
|
49
|
+
* revoked (best-effort) and never returned. This is the COARSE gate, so it
|
|
50
|
+
* runs BEFORE the `afterLogin` hook.
|
|
51
|
+
*
|
|
52
|
+
* No-op when: enforcement is off (or the MembershipService isn't wired), the
|
|
53
|
+
* request skips auth, or the resolved group is `public`.
|
|
54
|
+
*/
|
|
55
|
+
private assertGroupMembership;
|
|
56
|
+
login(dto: LoginDto, req?: any): Promise<{
|
|
30
57
|
token: string;
|
|
31
58
|
organization_slug: string;
|
|
32
59
|
}>;
|
|
33
60
|
logout(req: any): Promise<{
|
|
34
61
|
success: boolean;
|
|
35
62
|
}>;
|
|
36
|
-
recover(dto: RecoverDto): Promise<{
|
|
63
|
+
recover(dto: RecoverDto, req?: any): Promise<{
|
|
37
64
|
success: boolean;
|
|
38
65
|
}>;
|
|
39
|
-
reset(dto: ResetDto): Promise<{
|
|
66
|
+
reset(dto: ResetDto, req?: any): Promise<{
|
|
40
67
|
success: boolean;
|
|
41
68
|
}>;
|
|
42
|
-
register(dto: RegisterDto): Promise<{
|
|
69
|
+
register(dto: RegisterDto, req?: any): Promise<{
|
|
43
70
|
requiresRegistration: boolean;
|
|
44
71
|
organization: any;
|
|
45
72
|
role: any;
|
|
46
73
|
email: any;
|
|
74
|
+
routeGroup: any;
|
|
47
75
|
accepted?: undefined;
|
|
48
76
|
token: string;
|
|
49
77
|
user: any;
|
|
50
78
|
} | {
|
|
51
79
|
accepted: boolean;
|
|
52
80
|
organization: any;
|
|
81
|
+
routeGroup: any;
|
|
53
82
|
requiresRegistration?: undefined;
|
|
54
83
|
role?: undefined;
|
|
55
84
|
email?: undefined;
|
|
@@ -18,42 +18,140 @@ const auth_service_1 = require("../services/auth.service");
|
|
|
18
18
|
const invitation_service_1 = require("../services/invitation.service");
|
|
19
19
|
const prisma_service_1 = require("../prisma/prisma.service");
|
|
20
20
|
const rhino_config_1 = require("../rhino.config");
|
|
21
|
+
const auth_hooks_service_1 = require("../services/auth-hooks.service");
|
|
22
|
+
const membership_service_1 = require("../services/membership.service");
|
|
23
|
+
const rhino_exception_1 = require("../errors/rhino-exception");
|
|
21
24
|
let AuthController = class AuthController {
|
|
22
25
|
authService;
|
|
23
26
|
invitationService;
|
|
24
27
|
prisma;
|
|
25
28
|
config;
|
|
26
|
-
|
|
29
|
+
hooks;
|
|
30
|
+
membership;
|
|
31
|
+
constructor(authService, invitationService, prisma, config,
|
|
32
|
+
// Optional so lightweight unit harnesses can omit it (no-op hooks then).
|
|
33
|
+
hooks,
|
|
34
|
+
// Optional for the same reason: when absent, membership enforcement is a
|
|
35
|
+
// no-op (equivalent to `auth.enforceGroupMembership` being off).
|
|
36
|
+
membership) {
|
|
27
37
|
this.authService = authService;
|
|
28
38
|
this.invitationService = invitationService;
|
|
29
39
|
this.prisma = prisma;
|
|
30
40
|
this.config = config;
|
|
41
|
+
this.hooks = hooks;
|
|
42
|
+
this.membership = membership;
|
|
31
43
|
}
|
|
32
|
-
|
|
44
|
+
/** Group resolved for this auth request (set by RouteGroupMiddleware). */
|
|
45
|
+
routeGroup(req) {
|
|
46
|
+
return req?.__routeGroup ?? null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Run a lifecycle hook for the resolved group. `revokes` flags token-issuing
|
|
50
|
+
* actions: when the hook rejects, the issued token is dropped (revoked) and
|
|
51
|
+
* the rejection's status is re-thrown. When no hooks service is wired, this
|
|
52
|
+
* is a no-op.
|
|
53
|
+
*/
|
|
54
|
+
async runHook(event, req, ctx) {
|
|
55
|
+
if (!this.hooks)
|
|
56
|
+
return;
|
|
57
|
+
await this.hooks.run(event, this.routeGroup(req), ctx);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Enforce group membership at the auth boundary — parity with the
|
|
61
|
+
* GroupMembershipGuard (resource layer) and the Laravel/Rails AuthControllers.
|
|
62
|
+
*
|
|
63
|
+
* When `auth.enforceGroupMembership` is ON, a non-member must be rejected at
|
|
64
|
+
* `/auth/login` with **403** rather than being allowed to authenticate and
|
|
65
|
+
* only blocked later on the first resource request. The just-issued token is
|
|
66
|
+
* revoked (best-effort) and never returned. This is the COARSE gate, so it
|
|
67
|
+
* runs BEFORE the `afterLogin` hook.
|
|
68
|
+
*
|
|
69
|
+
* No-op when: enforcement is off (or the MembershipService isn't wired), the
|
|
70
|
+
* request skips auth, or the resolved group is `public`.
|
|
71
|
+
*/
|
|
72
|
+
async assertGroupMembership(req, user, token) {
|
|
73
|
+
if (!this.membership?.enabled())
|
|
74
|
+
return;
|
|
75
|
+
// NOTE: we deliberately do NOT bail on `req.__skipAuth` here. Consumers set
|
|
76
|
+
// that flag on auth entrypoints (login/register) to bypass the JWT guard
|
|
77
|
+
// (there's no token yet) — it does NOT mean "skip membership". The genuine
|
|
78
|
+
// skip signal is the `public` group, handled below (matches the guard).
|
|
79
|
+
const routeGroup = this.routeGroup(req);
|
|
80
|
+
if (routeGroup === 'public')
|
|
81
|
+
return;
|
|
82
|
+
const org = req?.organization ?? null;
|
|
83
|
+
const isTenant = this.config.isTenantGroup(routeGroup);
|
|
84
|
+
if (this.membership.isMember(user, routeGroup, org, isTenant))
|
|
85
|
+
return;
|
|
86
|
+
await this.authService.revokeToken(token);
|
|
87
|
+
throw rhino_exception_1.RhinoException.membershipDenied();
|
|
88
|
+
}
|
|
89
|
+
async login(dto, req) {
|
|
33
90
|
if (!dto?.email || !dto?.password) {
|
|
34
91
|
throw new common_1.BadRequestException('Email and password required');
|
|
35
92
|
}
|
|
36
|
-
const { token, organizationSlug } = await this.authService.login(dto.email, dto.password);
|
|
93
|
+
const { token, organizationSlug, user } = await this.authService.login(dto.email, dto.password);
|
|
94
|
+
// Coarse membership gate (before the hook): a non-member is rejected here
|
|
95
|
+
// with 403, matching the resource-layer guard and the Laravel/Rails stacks.
|
|
96
|
+
await this.assertGroupMembership(req, user, token);
|
|
97
|
+
const routeGroup = this.routeGroup(req);
|
|
98
|
+
try {
|
|
99
|
+
await this.runHook('afterLogin', req, {
|
|
100
|
+
user,
|
|
101
|
+
routeGroup,
|
|
102
|
+
token,
|
|
103
|
+
request: req,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
// Reject → revoke the just-issued token (never returned to the client).
|
|
108
|
+
await this.authService.revokeToken(token);
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
37
111
|
return { token, organization_slug: organizationSlug };
|
|
38
112
|
}
|
|
39
113
|
async logout(req) {
|
|
40
114
|
await this.authService.logout(req.user);
|
|
115
|
+
await this.runHook('afterLogout', req, {
|
|
116
|
+
user: req.user,
|
|
117
|
+
routeGroup: this.routeGroup(req),
|
|
118
|
+
request: req,
|
|
119
|
+
});
|
|
41
120
|
return { success: true };
|
|
42
121
|
}
|
|
43
|
-
async recover(dto) {
|
|
122
|
+
async recover(dto, req) {
|
|
44
123
|
if (!dto?.email)
|
|
45
124
|
throw new common_1.BadRequestException('Email required');
|
|
46
|
-
await this.authService.requestPasswordRecovery(dto.email);
|
|
125
|
+
const result = await this.authService.requestPasswordRecovery(dto.email);
|
|
126
|
+
// FIX 6: password recovery MUST return a uniform response whether or not the
|
|
127
|
+
// email exists (no user-enumeration oracle). The hook runs for side effects
|
|
128
|
+
// only — a rejection is swallowed so it can never change the response and
|
|
129
|
+
// thereby reveal account existence. (Other actions keep reject semantics.)
|
|
130
|
+
try {
|
|
131
|
+
await this.runHook('afterPasswordRecover', req, {
|
|
132
|
+
user: result?.user ?? null,
|
|
133
|
+
routeGroup: this.routeGroup(req),
|
|
134
|
+
request: req,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
/* swallow: recovery response is always uniform */
|
|
139
|
+
}
|
|
47
140
|
return { success: true };
|
|
48
141
|
}
|
|
49
|
-
async reset(dto) {
|
|
142
|
+
async reset(dto, req) {
|
|
50
143
|
if (!dto?.email || !dto?.token || !dto?.password) {
|
|
51
144
|
throw new common_1.BadRequestException('Email, token and password required');
|
|
52
145
|
}
|
|
53
146
|
await this.authService.resetPassword(dto.email, dto.token, dto.password);
|
|
147
|
+
await this.runHook('afterPasswordReset', req, {
|
|
148
|
+
user: null,
|
|
149
|
+
routeGroup: this.routeGroup(req),
|
|
150
|
+
request: req,
|
|
151
|
+
});
|
|
54
152
|
return { success: true };
|
|
55
153
|
}
|
|
56
|
-
async register(dto) {
|
|
154
|
+
async register(dto, req) {
|
|
57
155
|
if (!dto?.email || !dto?.password || !dto?.invitationToken) {
|
|
58
156
|
throw new common_1.BadRequestException('Email, password and invitationToken required');
|
|
59
157
|
}
|
|
@@ -68,6 +166,22 @@ let AuthController = class AuthController {
|
|
|
68
166
|
});
|
|
69
167
|
const acceptance = await this.invitationService.accept(dto.invitationToken, user.id);
|
|
70
168
|
const token = this.authService.signToken({ sub: user.id, email: dto.email });
|
|
169
|
+
// Hooks see the group the membership was created for, falling back to the
|
|
170
|
+
// request's resolved group.
|
|
171
|
+
const routeGroup = acceptance?.routeGroup ?? this.routeGroup(req);
|
|
172
|
+
try {
|
|
173
|
+
await this.runHook('afterRegister', req, {
|
|
174
|
+
user,
|
|
175
|
+
routeGroup,
|
|
176
|
+
organization: acceptance?.organization ?? null,
|
|
177
|
+
token,
|
|
178
|
+
request: req,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
await this.authService.revokeToken(token);
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
71
185
|
return {
|
|
72
186
|
token,
|
|
73
187
|
user,
|
|
@@ -79,8 +193,9 @@ exports.AuthController = AuthController;
|
|
|
79
193
|
__decorate([
|
|
80
194
|
(0, common_1.Post)('login'),
|
|
81
195
|
__param(0, (0, common_1.Body)()),
|
|
196
|
+
__param(1, (0, common_1.Req)()),
|
|
82
197
|
__metadata("design:type", Function),
|
|
83
|
-
__metadata("design:paramtypes", [Object]),
|
|
198
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
84
199
|
__metadata("design:returntype", Promise)
|
|
85
200
|
], AuthController.prototype, "login", null);
|
|
86
201
|
__decorate([
|
|
@@ -93,22 +208,25 @@ __decorate([
|
|
|
93
208
|
__decorate([
|
|
94
209
|
(0, common_1.Post)('password/recover'),
|
|
95
210
|
__param(0, (0, common_1.Body)()),
|
|
211
|
+
__param(1, (0, common_1.Req)()),
|
|
96
212
|
__metadata("design:type", Function),
|
|
97
|
-
__metadata("design:paramtypes", [Object]),
|
|
213
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
98
214
|
__metadata("design:returntype", Promise)
|
|
99
215
|
], AuthController.prototype, "recover", null);
|
|
100
216
|
__decorate([
|
|
101
217
|
(0, common_1.Post)('password/reset'),
|
|
102
218
|
__param(0, (0, common_1.Body)()),
|
|
219
|
+
__param(1, (0, common_1.Req)()),
|
|
103
220
|
__metadata("design:type", Function),
|
|
104
|
-
__metadata("design:paramtypes", [Object]),
|
|
221
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
105
222
|
__metadata("design:returntype", Promise)
|
|
106
223
|
], AuthController.prototype, "reset", null);
|
|
107
224
|
__decorate([
|
|
108
225
|
(0, common_1.Post)('register'),
|
|
109
226
|
__param(0, (0, common_1.Body)()),
|
|
227
|
+
__param(1, (0, common_1.Req)()),
|
|
110
228
|
__metadata("design:type", Function),
|
|
111
|
-
__metadata("design:paramtypes", [Object]),
|
|
229
|
+
__metadata("design:paramtypes", [Object, Object]),
|
|
112
230
|
__metadata("design:returntype", Promise)
|
|
113
231
|
], AuthController.prototype, "register", null);
|
|
114
232
|
exports.AuthController = AuthController = __decorate([
|
|
@@ -116,6 +234,8 @@ exports.AuthController = AuthController = __decorate([
|
|
|
116
234
|
__metadata("design:paramtypes", [auth_service_1.AuthService,
|
|
117
235
|
invitation_service_1.InvitationService,
|
|
118
236
|
prisma_service_1.PrismaService,
|
|
119
|
-
rhino_config_1.RhinoConfigService
|
|
237
|
+
rhino_config_1.RhinoConfigService,
|
|
238
|
+
auth_hooks_service_1.AuthHooksService,
|
|
239
|
+
membership_service_1.MembershipService])
|
|
120
240
|
], AuthController);
|
|
121
241
|
//# sourceMappingURL=auth.controller.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/controllers/auth.controller.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,2CAMwB;AACxB,2DAAuD;AACvD,uEAAmE;AACnE,6DAAyD;AACzD,kDAAqD;
|
|
1
|
+
{"version":3,"file":"auth.controller.js","sourceRoot":"","sources":["../../src/controllers/auth.controller.ts"],"names":[],"mappings":"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"}
|