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