@neohm/nestend 4.1.0 → 4.1.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 +10 -8
- package/dist/tenant/middlewares/tenant.context.middleware.d.ts +2 -3
- package/dist/tenant/middlewares/tenant.context.middleware.d.ts.map +1 -1
- package/dist/tenant/middlewares/tenant.context.middleware.js +12 -23
- package/dist/tenant/middlewares/tenant.context.middleware.js.map +1 -1
- package/dist/tenant/services/tenant.service.d.ts +0 -2
- package/dist/tenant/services/tenant.service.d.ts.map +1 -1
- package/dist/tenant/services/tenant.service.js +0 -22
- package/dist/tenant/services/tenant.service.js.map +1 -1
- package/dist/tenant/types/tenant.types.d.ts +0 -5
- package/dist/tenant/types/tenant.types.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/tenant/enums/tenant.default.enum.d.ts +0 -4
- package/dist/tenant/enums/tenant.default.enum.d.ts.map +0 -1
- package/dist/tenant/enums/tenant.default.enum.js +0 -8
- package/dist/tenant/enums/tenant.default.enum.js.map +0 -1
package/README.md
CHANGED
|
@@ -111,7 +111,7 @@ these are free to change without a breaking release.
|
|
|
111
111
|
| Admin auth | `AdminAuthService.validateRole()`, `AdminAuth`, `AdminJwtMiddleware` (endpoints at `/admin/auth`) | fully separate stack for back-office admins — own users table (`sys_admin_users`), own roles (`sys_admin_roles`/`sys_auser_roles`), own sessions/refresh tokens (shared tables, `is_admin` flag) |
|
|
112
112
|
| API keys | `ApiKeyService`, `ApiKeyGuard` / `ApiKeyMiddleware`, `@ApiKeyScopes()`, `ApiKeyContext` | hashed keys in `sys_api_keys` (admin-owned), one tenant per key, expiry/revocation, `x-api-key` header; `validateAccess(scope)` returns the tenant the key acts for |
|
|
113
113
|
| Scopes | `ScopeService`, `AuthService.validateAccess()`, `ScopeEntity`, `TenantScopeEntity`, `ApiScopeEntity` | one feature named once in `sys_scopes`, optionally with child scopes under it, then granted per organization (`sys_tenant_scopes`) or per API key (`sys_api_scopes`) — never an array on the principal |
|
|
114
|
-
| Multi-tenancy | `TenantModule`, `TenantEntity`, `CommonTenantEntity`, `TenantContextService`, `TenantContextMiddleware` | AsyncLocalStorage tenant context
|
|
114
|
+
| Multi-tenancy | `TenantModule`, `TenantEntity`, `CommonTenantEntity`, `TenantContextService`, `TenantContextMiddleware` | AsyncLocalStorage tenant context bound from the authenticated session (or API key), never a client-supplied header; tenants are onboarded by admins (`TenantEntity.creator` → `AdminUserEntity`) |
|
|
115
115
|
| Admin | `AdminModule` (its controllers, DTOs and processors are internal — import the module and hit the endpoints), `GlobalAdminRoles` | back-office APIs: tenant onboarding, client/admin/system user registration (single + bulk), the `has_global_access` grant that enables impersonation, tenant + scheduled-task listing, the email/PDF template catalogues (read at content-reader, written at content-manager, deleted at global admin); 4-tier `GlobalAdminRoles` gate over `sys_admin_roles`; Swagger at `/docs` (hidden in production) |
|
|
116
116
|
| Notifications | `NotificationModule`, `NotificationService`, `FcmPushNotificationService` | in-app rows + FCM push (optional `firebase-admin`); device token registry |
|
|
117
117
|
| Email (SES) | `EmailNotificationService`, `ProcessEmailNotification`, `EmailTemplateEntity`, `EmailNotificationLogEntity` | templates in `sys_email_templates` (managed from the admin surface) rendered by a remote service, dispatched via SES per recipient (to/cc/bcc); logged to `sys_email_notification_logs` outside production; dispatch skipped when `AWS_SES_FROM_ADDRESS` unset |
|
|
@@ -302,13 +302,16 @@ export class ReportController {
|
|
|
302
302
|
}
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
-
Wire the middleware once in your app so the user is resolved per request
|
|
306
|
-
|
|
305
|
+
Wire the middleware once in your app so the user is resolved per request.
|
|
306
|
+
`TenantContextMiddleware` goes after it — it binds the session's organization
|
|
307
|
+
(or the API key's tenant), never a client-supplied header or hostname:
|
|
307
308
|
|
|
308
309
|
```ts
|
|
309
310
|
export class AppModule implements NestModule {
|
|
310
311
|
configure(consumer: MiddlewareConsumer) {
|
|
311
|
-
consumer
|
|
312
|
+
consumer
|
|
313
|
+
.apply(JwtMiddleware, ApiKeyMiddleware, TenantContextMiddleware)
|
|
314
|
+
.forRoutes('*'); // Express 5: e.g. '{*splat}'
|
|
312
315
|
}
|
|
313
316
|
}
|
|
314
317
|
```
|
|
@@ -667,10 +670,9 @@ Requires the optional peer dependency `puppeteer`.
|
|
|
667
670
|
|
|
668
671
|
### Multi-tenancy
|
|
669
672
|
|
|
670
|
-
| Variable
|
|
671
|
-
|
|
|
672
|
-
| `
|
|
673
|
-
| `TENANT_INVITATION_EXPIRY` | `7d` | How long an organization invitation stays answerable |
|
|
673
|
+
| Variable | Default | Description |
|
|
674
|
+
| -------------------------- | ------- | ---------------------------------------------------- |
|
|
675
|
+
| `TENANT_INVITATION_EXPIRY` | `7d` | How long an organization invitation stays answerable |
|
|
674
676
|
|
|
675
677
|
### API Keys
|
|
676
678
|
|
|
@@ -5,9 +5,8 @@ import { TenantService } from '../services/tenant.service';
|
|
|
5
5
|
export declare class TenantContextMiddleware implements NestMiddleware {
|
|
6
6
|
private readonly tenantService;
|
|
7
7
|
private readonly tenantContextService;
|
|
8
|
-
private static readonly UUID_PATTERN;
|
|
9
8
|
constructor(tenantService: TenantService, tenantContextService: TenantContextService);
|
|
10
|
-
use(
|
|
11
|
-
private
|
|
9
|
+
use(_request: Request, _response: Response, next: NextFunction): Promise<void>;
|
|
10
|
+
private resolveTenant;
|
|
12
11
|
}
|
|
13
12
|
//# sourceMappingURL=tenant.context.middleware.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.context.middleware.d.ts","sourceRoot":"","sources":["../../../src/tenant/middlewares/tenant.context.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant.context.middleware.d.ts","sourceRoot":"","sources":["../../../src/tenant/middlewares/tenant.context.middleware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAI1D,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAmB3D,qBACa,uBAAwB,YAAW,cAAc;IAE1D,OAAO,CAAC,QAAQ,CAAC,aAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,oBAAoB;gBADpB,aAAa,EAAE,aAAa,EAC5B,oBAAoB,EAAE,oBAAoB;IAGvD,GAAG,CACP,QAAQ,EAAE,OAAO,EACjB,SAAS,EAAE,QAAQ,EACnB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC;YAWF,aAAa;CAQ5B"}
|
|
@@ -8,45 +8,34 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
9
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
10
|
};
|
|
11
|
-
var TenantContextMiddleware_1;
|
|
12
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
12
|
exports.TenantContextMiddleware = void 0;
|
|
14
13
|
const common_1 = require("@nestjs/common");
|
|
15
|
-
const
|
|
14
|
+
const api_key_context_1 = require("../../auth/helpers/api.key.context");
|
|
15
|
+
const auth_1 = require("../../auth/helpers/auth");
|
|
16
16
|
const tenant_context_service_1 = require("../services/tenant.context.service");
|
|
17
17
|
const tenant_service_1 = require("../services/tenant.service");
|
|
18
|
-
let TenantContextMiddleware =
|
|
18
|
+
let TenantContextMiddleware = class TenantContextMiddleware {
|
|
19
19
|
constructor(tenantService, tenantContextService) {
|
|
20
20
|
this.tenantService = tenantService;
|
|
21
21
|
this.tenantContextService = tenantContextService;
|
|
22
22
|
}
|
|
23
|
-
async use(
|
|
24
|
-
const tenant = await this.
|
|
23
|
+
async use(_request, _response, next) {
|
|
24
|
+
const tenant = await this.resolveTenant();
|
|
25
25
|
if (!tenant)
|
|
26
26
|
return next();
|
|
27
27
|
this.tenantContextService.runWithTenant({ tenantUuid: tenant.id, tenant }, () => next());
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
const hints = { domain: request.hostname };
|
|
36
|
-
if (identifier) {
|
|
37
|
-
if (TenantContextMiddleware_1.UUID_PATTERN.test(identifier)) {
|
|
38
|
-
hints.tenantUuid = identifier;
|
|
39
|
-
}
|
|
40
|
-
else {
|
|
41
|
-
hints.slug = identifier;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return hints;
|
|
29
|
+
async resolveTenant() {
|
|
30
|
+
const sessionTenantId = auth_1.Auth.user()?.organization_id;
|
|
31
|
+
if (sessionTenantId)
|
|
32
|
+
return this.tenantService.findByUuid(sessionTenantId);
|
|
33
|
+
const keyTenant = api_key_context_1.ApiKeyContext.tenant();
|
|
34
|
+
return keyTenant?.is_active ? keyTenant : null;
|
|
45
35
|
}
|
|
46
36
|
};
|
|
47
37
|
exports.TenantContextMiddleware = TenantContextMiddleware;
|
|
48
|
-
TenantContextMiddleware
|
|
49
|
-
exports.TenantContextMiddleware = TenantContextMiddleware = TenantContextMiddleware_1 = __decorate([
|
|
38
|
+
exports.TenantContextMiddleware = TenantContextMiddleware = __decorate([
|
|
50
39
|
(0, common_1.Injectable)(),
|
|
51
40
|
__metadata("design:paramtypes", [tenant_service_1.TenantService,
|
|
52
41
|
tenant_context_service_1.TenantContextService])
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.context.middleware.js","sourceRoot":"","sources":["../../../src/tenant/middlewares/tenant.context.middleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"tenant.context.middleware.js","sourceRoot":"","sources":["../../../src/tenant/middlewares/tenant.context.middleware.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4D;AAE5D,wEAAmE;AACnE,kDAA+C;AAE/C,+EAA0E;AAC1E,+DAA2D;AAoBpD,IAAM,uBAAuB,GAA7B,MAAM,uBAAuB;IAClC,YACmB,aAA4B,EAC5B,oBAA0C;QAD1C,kBAAa,GAAb,aAAa,CAAe;QAC5B,yBAAoB,GAApB,oBAAoB,CAAsB;IAC1D,CAAC;IAEJ,KAAK,CAAC,GAAG,CACP,QAAiB,EACjB,SAAmB,EACnB,IAAkB;QAElB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,CAAC;QAC1C,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,EAAE,CAAC;QAE3B,IAAI,CAAC,oBAAoB,CAAC,aAAa,CACrC,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EACjC,GAAG,EAAE,CAAC,IAAI,EAAE,CACb,CAAC;IACJ,CAAC;IAGO,KAAK,CAAC,aAAa;QAEzB,MAAM,eAAe,GAAG,WAAI,CAAC,IAAI,EAAE,EAAE,eAAqC,CAAC;QAC3E,IAAI,eAAe;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;QAE3E,MAAM,SAAS,GAAG,+BAAa,CAAC,MAAM,EAAE,CAAC;QACzC,OAAO,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC;IACjD,CAAC;CACF,CAAA;AA7BY,0DAAuB;kCAAvB,uBAAuB;IADnC,IAAA,mBAAU,GAAE;qCAGuB,8BAAa;QACN,6CAAoB;GAHlD,uBAAuB,CA6BnC"}
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { DataSource } from 'typeorm';
|
|
2
2
|
import { TenantEntity } from '../entities/tenant.entity';
|
|
3
|
-
import { TenantResolutionHints } from '../types/tenant.types';
|
|
4
3
|
export declare class TenantService {
|
|
5
4
|
private readonly dataSource;
|
|
6
5
|
constructor(dataSource: DataSource);
|
|
7
6
|
findByUuid(tenantUuid: string): Promise<TenantEntity | null>;
|
|
8
7
|
findBySlug(slug: string): Promise<TenantEntity | null>;
|
|
9
8
|
findByDomain(domain: string): Promise<TenantEntity | null>;
|
|
10
|
-
resolveTenant(hints: TenantResolutionHints): Promise<TenantEntity | null>;
|
|
11
9
|
listActiveTenants(): Promise<TenantEntity[]>;
|
|
12
10
|
private get tenantRepository();
|
|
13
11
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.service.d.ts","sourceRoot":"","sources":["../../../src/tenant/services/tenant.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"tenant.service.d.ts","sourceRoot":"","sources":["../../../src/tenant/services/tenant.service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAc,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,qBACa,aAAa;IACZ,OAAO,CAAC,QAAQ,CAAC,UAAU;gBAAV,UAAU,EAAE,UAAU;IAE7C,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAM5D,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAItD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAM1D,iBAAiB,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAOlD,OAAO,KAAK,gBAAgB,GAE3B;CACF"}
|
|
@@ -30,28 +30,6 @@ let TenantService = class TenantService {
|
|
|
30
30
|
where: { domain, is_active: true },
|
|
31
31
|
});
|
|
32
32
|
}
|
|
33
|
-
async resolveTenant(hints) {
|
|
34
|
-
if (hints.tenantUuid) {
|
|
35
|
-
const tenant = await this.findByUuid(hints.tenantUuid);
|
|
36
|
-
if (tenant)
|
|
37
|
-
return tenant;
|
|
38
|
-
}
|
|
39
|
-
if (hints.slug) {
|
|
40
|
-
const tenant = await this.findBySlug(hints.slug);
|
|
41
|
-
if (tenant)
|
|
42
|
-
return tenant;
|
|
43
|
-
}
|
|
44
|
-
if (hints.domain) {
|
|
45
|
-
const tenant = await this.findByDomain(hints.domain);
|
|
46
|
-
if (tenant)
|
|
47
|
-
return tenant;
|
|
48
|
-
const subdomain = hints.domain.split('.')[0];
|
|
49
|
-
if (subdomain && subdomain !== hints.domain) {
|
|
50
|
-
return this.findBySlug(subdomain);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
33
|
async listActiveTenants() {
|
|
56
34
|
return this.tenantRepository.find({
|
|
57
35
|
where: { is_active: true },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.service.js","sourceRoot":"","sources":["../../../src/tenant/services/tenant.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,qCAAiD;AACjD,6DAAyD;
|
|
1
|
+
{"version":3,"file":"tenant.service.js","sourceRoot":"","sources":["../../../src/tenant/services/tenant.service.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,2CAA4C;AAC5C,qCAAiD;AACjD,6DAAyD;AAGlD,IAAM,aAAa,GAAnB,MAAM,aAAa;IACxB,YAA6B,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAG,CAAC;IAEvD,KAAK,CAAC,UAAU,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;YACnC,KAAK,EAAE,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,IAAI,EAAE;SAC3C,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,IAAY;QAC3B,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,MAAc;QAC/B,OAAO,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC;YACnC,KAAK,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE;SACnC,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,iBAAiB;QACrB,OAAO,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC;YAChC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE;YAC1B,KAAK,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE;SACvB,CAAC,CAAC;IACL,CAAC;IAED,IAAY,gBAAgB;QAC1B,OAAO,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC,4BAAY,CAAC,CAAC;IACrD,CAAC;CACF,CAAA;AA7BY,sCAAa;wBAAb,aAAa;IADzB,IAAA,mBAAU,GAAE;qCAE8B,oBAAU;GADxC,aAAa,CA6BzB"}
|
|
@@ -3,11 +3,6 @@ export interface TenantContext {
|
|
|
3
3
|
tenantUuid: string;
|
|
4
4
|
tenant?: TenantEntity;
|
|
5
5
|
}
|
|
6
|
-
export interface TenantResolutionHints {
|
|
7
|
-
tenantUuid?: string;
|
|
8
|
-
slug?: string;
|
|
9
|
-
domain?: string;
|
|
10
|
-
}
|
|
11
6
|
export interface TenantModuleOptions {
|
|
12
7
|
dataSourceName?: string;
|
|
13
8
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.types.d.ts","sourceRoot":"","sources":["../../../src/tenant/types/tenant.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;
|
|
1
|
+
{"version":3,"file":"tenant.types.d.ts","sourceRoot":"","sources":["../../../src/tenant/types/tenant.types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAEzD,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,YAAY,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB"}
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.default.enum.d.ts","sourceRoot":"","sources":["../../../src/tenant/enums/tenant.default.enum.ts"],"names":[],"mappings":"AAAA,oBAAY,aAAa;IACvB,WAAW,gBAAgB;CAC5B"}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TenantDefault = void 0;
|
|
4
|
-
var TenantDefault;
|
|
5
|
-
(function (TenantDefault) {
|
|
6
|
-
TenantDefault["HEADER_NAME"] = "x-tenant-id";
|
|
7
|
-
})(TenantDefault || (exports.TenantDefault = TenantDefault = {}));
|
|
8
|
-
//# sourceMappingURL=tenant.default.enum.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"tenant.default.enum.js","sourceRoot":"","sources":["../../../src/tenant/enums/tenant.default.enum.ts"],"names":[],"mappings":";;;AAAA,IAAY,aAEX;AAFD,WAAY,aAAa;IACvB,4CAA2B,CAAA;AAC7B,CAAC,EAFW,aAAa,6BAAb,aAAa,QAExB"}
|