@dumanarge/xssrv-shared-lib 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/auth/audit.interceptor.d.ts +32 -0
- package/dist/auth/audit.interceptor.js +147 -0
- package/dist/auth/audit.interceptor.js.map +1 -0
- package/dist/auth/tenant.middleware.d.ts +28 -0
- package/dist/auth/tenant.middleware.js +129 -0
- package/dist/auth/tenant.middleware.js.map +1 -0
- package/dist/config/feature-flags.d.ts +26 -0
- package/dist/config/feature-flags.js +111 -0
- package/dist/config/feature-flags.js.map +1 -0
- package/dist/config/graceful-shutdown.d.ts +16 -0
- package/dist/config/graceful-shutdown.js +62 -0
- package/dist/config/graceful-shutdown.js.map +1 -0
- package/dist/health/health.module.d.ts +19 -0
- package/dist/health/health.module.js +93 -0
- package/dist/health/health.module.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +36 -0
- package/dist/index.js.map +1 -0
- package/dist/logging/logging.module.d.ts +45 -0
- package/dist/logging/logging.module.js +74 -0
- package/dist/logging/logging.module.js.map +1 -0
- package/dist/outbox/outbox.module.d.ts +44 -0
- package/dist/outbox/outbox.module.js +170 -0
- package/dist/outbox/outbox.module.js.map +1 -0
- package/dist/tracing/span.decorator.d.ts +3 -0
- package/dist/tracing/span.decorator.js +39 -0
- package/dist/tracing/span.decorator.js.map +1 -0
- package/dist/tracing/tracing.d.ts +3 -0
- package/dist/tracing/tracing.js +61 -0
- package/dist/tracing/tracing.js.map +1 -0
- package/package.json +176 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0] - 2025-02-22
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Tracing**: OpenTelemetry bootstrap with OTLP gRPC exporter and `@Span()` decorator
|
|
13
|
+
- **Logging**: Pino structured logging with automatic traceId injection and sensitive field redaction
|
|
14
|
+
- **Health**: Kubernetes liveness/readiness probes with Redis and RabbitMQ health indicators
|
|
15
|
+
- **Outbox**: Transactional outbox pattern with RabbitMQ event publisher and background worker
|
|
16
|
+
- **Auth**: Multi-tenant isolation via Prisma middleware, NestJS guard, and gRPC metadata extraction
|
|
17
|
+
- **Audit**: Tamper-proof audit interceptor for HTTP and gRPC mutations (KVKK compliant)
|
|
18
|
+
- **Config**: Consul KV-based feature flags with rollout percentage and tenant targeting
|
|
19
|
+
- **Shutdown**: Graceful shutdown with prioritized hook system and per-hook timeout
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 DumanArge / Oguzhan Karahan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @dumanarge/xssrv-shared-lib
|
|
2
|
+
|
|
3
|
+
Shared infrastructure library for **DumanArge IoT Platform** NestJS microservices.
|
|
4
|
+
|
|
5
|
+
Provides production-ready modules for tracing, structured logging, health checks, transactional outbox pattern, multi-tenant isolation, audit trails, feature flags, and graceful shutdown.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @dumanarge/xssrv-shared-lib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Peer Dependencies
|
|
14
|
+
|
|
15
|
+
This library uses peer dependencies to avoid version conflicts. Install the modules you need:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Core (required)
|
|
19
|
+
npm install @nestjs/common @nestjs/core rxjs reflect-metadata
|
|
20
|
+
|
|
21
|
+
# Tracing (for OpenTelemetry support)
|
|
22
|
+
npm install @opentelemetry/sdk-node @opentelemetry/auto-instrumentations-node \
|
|
23
|
+
@opentelemetry/exporter-trace-otlp-grpc @opentelemetry/exporter-metrics-otlp-grpc \
|
|
24
|
+
@opentelemetry/resources @opentelemetry/sdk-metrics
|
|
25
|
+
|
|
26
|
+
# Logging
|
|
27
|
+
npm install nestjs-pino pino pino-http
|
|
28
|
+
|
|
29
|
+
# Health checks
|
|
30
|
+
npm install @nestjs/terminus
|
|
31
|
+
|
|
32
|
+
# Outbox & Tenant isolation
|
|
33
|
+
npm install @prisma/client
|
|
34
|
+
|
|
35
|
+
# Feature flags
|
|
36
|
+
npm install @nestjs/config
|
|
37
|
+
|
|
38
|
+
# Redis health indicator
|
|
39
|
+
npm install ioredis
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Modules
|
|
43
|
+
|
|
44
|
+
### Tracing (OpenTelemetry)
|
|
45
|
+
|
|
46
|
+
Bootstrap tracing before your NestJS app starts:
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// main.ts — must be the FIRST import
|
|
50
|
+
import '@dumanarge/xssrv-shared-lib/tracing';
|
|
51
|
+
|
|
52
|
+
import { NestFactory } from '@nestjs/core';
|
|
53
|
+
import { AppModule } from './app.module';
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use the `@Span()` decorator for custom business logic tracing:
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { Span } from '@dumanarge/xssrv-shared-lib';
|
|
60
|
+
|
|
61
|
+
@Injectable()
|
|
62
|
+
export class DeviceService {
|
|
63
|
+
@Span('device.provision')
|
|
64
|
+
async provisionDevice(dto: CreateDeviceDto) {
|
|
65
|
+
// Automatically traced with OpenTelemetry
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Environment variables:**
|
|
71
|
+
|
|
72
|
+
| Variable | Default | Description |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| `SERVICE_NAME` | `unknown-service` | OTel service name |
|
|
75
|
+
| `SERVICE_VERSION` | `1.0.0` | OTel service version |
|
|
76
|
+
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTel Collector endpoint |
|
|
77
|
+
| `OTEL_DEBUG` | `false` | Enable OTel debug logging |
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
### Logging (Pino + OTel)
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { LoggingModule } from '@dumanarge/xssrv-shared-lib';
|
|
85
|
+
|
|
86
|
+
@Module({
|
|
87
|
+
imports: [LoggingModule],
|
|
88
|
+
})
|
|
89
|
+
export class AppModule {}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Features: JSON structured logs, automatic `traceId`/`spanId` injection, request correlation via `X-Request-Id`, sensitive field redaction, health endpoint filtering.
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### Health Checks
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { HealthModule } from '@dumanarge/xssrv-shared-lib';
|
|
100
|
+
|
|
101
|
+
@Module({
|
|
102
|
+
imports: [HealthModule],
|
|
103
|
+
})
|
|
104
|
+
export class AppModule {}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Exposes `GET /health/live` (liveness) and `GET /health/ready` (readiness) endpoints for Kubernetes probes.
|
|
108
|
+
|
|
109
|
+
Custom health indicators included: `RedisHealthIndicator`, `RabbitMQHealthIndicator`.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
### Multi-Tenant Isolation
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import {
|
|
117
|
+
TenantMiddleware,
|
|
118
|
+
TenantGuard,
|
|
119
|
+
TenantContext,
|
|
120
|
+
createPrismaTenantMiddleware,
|
|
121
|
+
} from '@dumanarge/xssrv-shared-lib';
|
|
122
|
+
|
|
123
|
+
// In AppModule
|
|
124
|
+
export class AppModule implements NestModule {
|
|
125
|
+
configure(consumer: MiddlewareConsumer) {
|
|
126
|
+
consumer.apply(TenantMiddleware).forRoutes('*');
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// In service — automatic tenant scoping for Prisma
|
|
131
|
+
prisma.$use(createPrismaTenantMiddleware(() => tenantContext.tenantId));
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
### Outbox Pattern
|
|
137
|
+
|
|
138
|
+
Guaranteed event delivery via transactional outbox:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { OutboxRepository, RabbitMQEventPublisher, OutboxWorker } from '@dumanarge/xssrv-shared-lib';
|
|
142
|
+
|
|
143
|
+
// Write event in the same DB transaction as business data
|
|
144
|
+
await prisma.$transaction(async (tx) => {
|
|
145
|
+
await tx.tenant.create({ data: tenantData });
|
|
146
|
+
await outboxRepo.createInTransaction(tx, {
|
|
147
|
+
aggregateType: 'tenant',
|
|
148
|
+
aggregateId: tenant.id,
|
|
149
|
+
eventType: 'tenant.created',
|
|
150
|
+
payload: tenantData,
|
|
151
|
+
});
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
### Audit Interceptor
|
|
158
|
+
|
|
159
|
+
Tamper-proof audit trail for all mutation operations (POST/PUT/PATCH/DELETE):
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
import { AuditInterceptor } from '@dumanarge/xssrv-shared-lib';
|
|
163
|
+
|
|
164
|
+
@Module({
|
|
165
|
+
providers: [
|
|
166
|
+
{ provide: APP_INTERCEPTOR, useClass: AuditInterceptor },
|
|
167
|
+
],
|
|
168
|
+
})
|
|
169
|
+
export class AppModule {}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Feature Flags
|
|
175
|
+
|
|
176
|
+
Dynamic feature toggles via Consul KV with local cache fallback:
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
import { FeatureFlagService } from '@dumanarge/xssrv-shared-lib';
|
|
180
|
+
|
|
181
|
+
const isEnabled = await featureFlags.isEnabled('device_twin_enabled', tenantId);
|
|
182
|
+
const config = await featureFlags.getConfig('batch_signal_processing');
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
### Graceful Shutdown
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { GracefulShutdownModule, enableGracefulShutdown } from '@dumanarge/xssrv-shared-lib';
|
|
191
|
+
|
|
192
|
+
// In AppModule
|
|
193
|
+
@Module({
|
|
194
|
+
imports: [GracefulShutdownModule],
|
|
195
|
+
})
|
|
196
|
+
export class AppModule {}
|
|
197
|
+
|
|
198
|
+
// In main.ts
|
|
199
|
+
const app = await NestFactory.create(AppModule);
|
|
200
|
+
enableGracefulShutdown(app);
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Sub-path Imports
|
|
204
|
+
|
|
205
|
+
You can import individual modules directly to minimize bundle:
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import '@dumanarge/xssrv-shared-lib/tracing';
|
|
209
|
+
import { LoggingModule } from '@dumanarge/xssrv-shared-lib/logging';
|
|
210
|
+
import { HealthModule } from '@dumanarge/xssrv-shared-lib/health';
|
|
211
|
+
import { OutboxRepository } from '@dumanarge/xssrv-shared-lib/outbox';
|
|
212
|
+
import { TenantMiddleware } from '@dumanarge/xssrv-shared-lib/auth';
|
|
213
|
+
import { GracefulShutdownModule } from '@dumanarge/xssrv-shared-lib/config';
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## Requirements
|
|
217
|
+
|
|
218
|
+
- Node.js >= 20
|
|
219
|
+
- NestJS >= 11
|
|
220
|
+
- TypeScript >= 5.7
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
export interface AuditEntry {
|
|
4
|
+
id?: string;
|
|
5
|
+
serviceName: string;
|
|
6
|
+
action: string;
|
|
7
|
+
resource: string;
|
|
8
|
+
resourceId?: string;
|
|
9
|
+
tenantId?: string;
|
|
10
|
+
userId?: string;
|
|
11
|
+
requestBody?: any;
|
|
12
|
+
responseStatus: number;
|
|
13
|
+
responseBody?: any;
|
|
14
|
+
ipAddress?: string;
|
|
15
|
+
userAgent?: string;
|
|
16
|
+
duration: number;
|
|
17
|
+
timestamp: Date;
|
|
18
|
+
metadata?: Record<string, any>;
|
|
19
|
+
}
|
|
20
|
+
export declare class AuditInterceptor implements NestInterceptor {
|
|
21
|
+
private readonly outboxRepository?;
|
|
22
|
+
private readonly logger;
|
|
23
|
+
private readonly serviceName;
|
|
24
|
+
private readonly auditableMethods;
|
|
25
|
+
constructor(outboxRepository?: any);
|
|
26
|
+
intercept(context: ExecutionContext, next: CallHandler): Observable<any>;
|
|
27
|
+
private handleHttp;
|
|
28
|
+
private handleGrpc;
|
|
29
|
+
private saveAuditEntry;
|
|
30
|
+
private extractResourceId;
|
|
31
|
+
private sanitizeBody;
|
|
32
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
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
|
+
var AuditInterceptor_1;
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.AuditInterceptor = void 0;
|
|
17
|
+
const common_1 = require("@nestjs/common");
|
|
18
|
+
const rxjs_1 = require("rxjs");
|
|
19
|
+
let AuditInterceptor = AuditInterceptor_1 = class AuditInterceptor {
|
|
20
|
+
outboxRepository;
|
|
21
|
+
logger = new common_1.Logger(AuditInterceptor_1.name);
|
|
22
|
+
serviceName;
|
|
23
|
+
auditableMethods = ['POST', 'PUT', 'PATCH', 'DELETE'];
|
|
24
|
+
constructor(outboxRepository) {
|
|
25
|
+
this.outboxRepository = outboxRepository;
|
|
26
|
+
this.serviceName = process.env.SERVICE_NAME || 'unknown';
|
|
27
|
+
}
|
|
28
|
+
intercept(context, next) {
|
|
29
|
+
const contextType = context.getType();
|
|
30
|
+
if (contextType === 'http') {
|
|
31
|
+
return this.handleHttp(context, next);
|
|
32
|
+
}
|
|
33
|
+
else if (contextType === 'rpc') {
|
|
34
|
+
return this.handleGrpc(context, next);
|
|
35
|
+
}
|
|
36
|
+
return next.handle();
|
|
37
|
+
}
|
|
38
|
+
handleHttp(context, next) {
|
|
39
|
+
const request = context.switchToHttp().getRequest();
|
|
40
|
+
const { method, url, body, headers, ip } = request;
|
|
41
|
+
if (!this.auditableMethods.includes(method)) {
|
|
42
|
+
return next.handle();
|
|
43
|
+
}
|
|
44
|
+
const startTime = Date.now();
|
|
45
|
+
const tenantId = headers['x-tenant-id'];
|
|
46
|
+
const userId = headers['x-user-id'];
|
|
47
|
+
return next.handle().pipe((0, rxjs_1.tap)(async (responseBody) => {
|
|
48
|
+
const duration = Date.now() - startTime;
|
|
49
|
+
const response = context.switchToHttp().getResponse();
|
|
50
|
+
await this.saveAuditEntry({
|
|
51
|
+
serviceName: this.serviceName,
|
|
52
|
+
action: method,
|
|
53
|
+
resource: url,
|
|
54
|
+
resourceId: this.extractResourceId(url),
|
|
55
|
+
tenantId,
|
|
56
|
+
userId,
|
|
57
|
+
requestBody: this.sanitizeBody(body),
|
|
58
|
+
responseStatus: response.statusCode,
|
|
59
|
+
responseBody: this.sanitizeBody(responseBody),
|
|
60
|
+
ipAddress: ip,
|
|
61
|
+
userAgent: headers['user-agent'],
|
|
62
|
+
duration,
|
|
63
|
+
timestamp: new Date(),
|
|
64
|
+
});
|
|
65
|
+
}), (0, rxjs_1.catchError)((error) => {
|
|
66
|
+
const duration = Date.now() - startTime;
|
|
67
|
+
this.saveAuditEntry({
|
|
68
|
+
serviceName: this.serviceName,
|
|
69
|
+
action: method,
|
|
70
|
+
resource: url,
|
|
71
|
+
resourceId: this.extractResourceId(url),
|
|
72
|
+
tenantId,
|
|
73
|
+
userId,
|
|
74
|
+
requestBody: this.sanitizeBody(body),
|
|
75
|
+
responseStatus: error.status || 500,
|
|
76
|
+
responseBody: { error: error.message },
|
|
77
|
+
ipAddress: ip,
|
|
78
|
+
userAgent: headers['user-agent'],
|
|
79
|
+
duration,
|
|
80
|
+
timestamp: new Date(),
|
|
81
|
+
metadata: { errorStack: error.stack },
|
|
82
|
+
}).catch(() => { });
|
|
83
|
+
throw error;
|
|
84
|
+
}));
|
|
85
|
+
}
|
|
86
|
+
handleGrpc(context, next) {
|
|
87
|
+
const startTime = Date.now();
|
|
88
|
+
const rpcContext = context.switchToRpc();
|
|
89
|
+
const handler = context.getHandler();
|
|
90
|
+
const className = context.getClass().name;
|
|
91
|
+
const methodName = `${className}.${handler.name}`;
|
|
92
|
+
return next.handle().pipe((0, rxjs_1.tap)(async () => {
|
|
93
|
+
const duration = Date.now() - startTime;
|
|
94
|
+
await this.saveAuditEntry({
|
|
95
|
+
serviceName: this.serviceName,
|
|
96
|
+
action: 'gRPC',
|
|
97
|
+
resource: methodName,
|
|
98
|
+
responseStatus: 0,
|
|
99
|
+
duration,
|
|
100
|
+
timestamp: new Date(),
|
|
101
|
+
});
|
|
102
|
+
}));
|
|
103
|
+
}
|
|
104
|
+
async saveAuditEntry(entry) {
|
|
105
|
+
try {
|
|
106
|
+
if (this.outboxRepository) {
|
|
107
|
+
await this.outboxRepository.createInTransaction(this.outboxRepository.prisma, {
|
|
108
|
+
aggregateType: 'audit',
|
|
109
|
+
aggregateId: entry.resourceId || 'system',
|
|
110
|
+
eventType: 'audit.entry',
|
|
111
|
+
payload: entry,
|
|
112
|
+
metadata: { serviceName: this.serviceName },
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
this.logger.log({ audit: true, ...entry });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
this.logger.error(`Failed to save audit entry: ${error.message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
extractResourceId(url) {
|
|
124
|
+
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/i;
|
|
125
|
+
const match = url.match(uuidRegex);
|
|
126
|
+
return match?.[0];
|
|
127
|
+
}
|
|
128
|
+
sanitizeBody(body) {
|
|
129
|
+
if (!body)
|
|
130
|
+
return undefined;
|
|
131
|
+
const sanitized = { ...body };
|
|
132
|
+
const sensitiveFields = ['password', 'secret', 'token', 'apiKey', 'creditCard'];
|
|
133
|
+
for (const field of sensitiveFields) {
|
|
134
|
+
if (sanitized[field]) {
|
|
135
|
+
sanitized[field] = '[REDACTED]';
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return sanitized;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
exports.AuditInterceptor = AuditInterceptor;
|
|
142
|
+
exports.AuditInterceptor = AuditInterceptor = AuditInterceptor_1 = __decorate([
|
|
143
|
+
(0, common_1.Injectable)(),
|
|
144
|
+
__param(0, (0, common_1.Optional)()),
|
|
145
|
+
__metadata("design:paramtypes", [Object])
|
|
146
|
+
], AuditInterceptor);
|
|
147
|
+
//# sourceMappingURL=audit.interceptor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.interceptor.js","sourceRoot":"","sources":["../../src/auth/audit.interceptor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NestMiddleware, CanActivate, ExecutionContext } from '@nestjs/common';
|
|
2
|
+
import { Request, Response, NextFunction } from 'express';
|
|
3
|
+
export declare class TenantContext {
|
|
4
|
+
private _tenantId;
|
|
5
|
+
private _userId;
|
|
6
|
+
private _roles;
|
|
7
|
+
get tenantId(): string;
|
|
8
|
+
get userId(): string | null;
|
|
9
|
+
get roles(): string[];
|
|
10
|
+
set(tenantId: string, userId: string | null, roles: string[]): void;
|
|
11
|
+
isSuperAdmin(): boolean;
|
|
12
|
+
hasRole(role: string): boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class TenantMiddleware implements NestMiddleware {
|
|
15
|
+
private readonly tenantContext;
|
|
16
|
+
private readonly logger;
|
|
17
|
+
constructor(tenantContext: TenantContext);
|
|
18
|
+
use(req: Request, _res: Response, next: NextFunction): void;
|
|
19
|
+
}
|
|
20
|
+
export declare class TenantGuard implements CanActivate {
|
|
21
|
+
canActivate(context: ExecutionContext): boolean;
|
|
22
|
+
}
|
|
23
|
+
export declare function createPrismaTenantMiddleware(getTenantId: () => string): (params: any, next: (params: any) => Promise<any>) => Promise<any>;
|
|
24
|
+
export declare function extractTenantFromGrpcMetadata(metadata: any): {
|
|
25
|
+
tenantId: string;
|
|
26
|
+
userId: string | null;
|
|
27
|
+
roles: string[];
|
|
28
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
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 TenantMiddleware_1;
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.TenantGuard = exports.TenantMiddleware = exports.TenantContext = void 0;
|
|
14
|
+
exports.createPrismaTenantMiddleware = createPrismaTenantMiddleware;
|
|
15
|
+
exports.extractTenantFromGrpcMetadata = extractTenantFromGrpcMetadata;
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
let TenantContext = class TenantContext {
|
|
18
|
+
_tenantId = null;
|
|
19
|
+
_userId = null;
|
|
20
|
+
_roles = [];
|
|
21
|
+
get tenantId() {
|
|
22
|
+
if (!this._tenantId) {
|
|
23
|
+
throw new common_1.ForbiddenException('Tenant context not initialized');
|
|
24
|
+
}
|
|
25
|
+
return this._tenantId;
|
|
26
|
+
}
|
|
27
|
+
get userId() {
|
|
28
|
+
return this._userId;
|
|
29
|
+
}
|
|
30
|
+
get roles() {
|
|
31
|
+
return this._roles;
|
|
32
|
+
}
|
|
33
|
+
set(tenantId, userId, roles) {
|
|
34
|
+
this._tenantId = tenantId;
|
|
35
|
+
this._userId = userId;
|
|
36
|
+
this._roles = roles;
|
|
37
|
+
}
|
|
38
|
+
isSuperAdmin() {
|
|
39
|
+
return this._roles.includes('super_admin');
|
|
40
|
+
}
|
|
41
|
+
hasRole(role) {
|
|
42
|
+
return this._roles.includes(role);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
exports.TenantContext = TenantContext;
|
|
46
|
+
exports.TenantContext = TenantContext = __decorate([
|
|
47
|
+
(0, common_1.Injectable)({ scope: common_1.Scope.REQUEST })
|
|
48
|
+
], TenantContext);
|
|
49
|
+
let TenantMiddleware = TenantMiddleware_1 = class TenantMiddleware {
|
|
50
|
+
tenantContext;
|
|
51
|
+
logger = new common_1.Logger(TenantMiddleware_1.name);
|
|
52
|
+
constructor(tenantContext) {
|
|
53
|
+
this.tenantContext = tenantContext;
|
|
54
|
+
}
|
|
55
|
+
use(req, _res, next) {
|
|
56
|
+
const tenantId = req.headers['x-tenant-id'];
|
|
57
|
+
const userId = req.headers['x-user-id'];
|
|
58
|
+
const roles = req.headers['x-roles']?.split(',') || [];
|
|
59
|
+
if (tenantId) {
|
|
60
|
+
this.tenantContext.set(tenantId, userId, roles);
|
|
61
|
+
}
|
|
62
|
+
next();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
exports.TenantMiddleware = TenantMiddleware;
|
|
66
|
+
exports.TenantMiddleware = TenantMiddleware = TenantMiddleware_1 = __decorate([
|
|
67
|
+
(0, common_1.Injectable)(),
|
|
68
|
+
__metadata("design:paramtypes", [TenantContext])
|
|
69
|
+
], TenantMiddleware);
|
|
70
|
+
let TenantGuard = class TenantGuard {
|
|
71
|
+
canActivate(context) {
|
|
72
|
+
const request = context.switchToHttp().getRequest();
|
|
73
|
+
const tenantId = request.headers['x-tenant-id'];
|
|
74
|
+
if (!tenantId) {
|
|
75
|
+
throw new common_1.ForbiddenException('Missing tenant context');
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
exports.TenantGuard = TenantGuard;
|
|
81
|
+
exports.TenantGuard = TenantGuard = __decorate([
|
|
82
|
+
(0, common_1.Injectable)()
|
|
83
|
+
], TenantGuard);
|
|
84
|
+
function createPrismaTenantMiddleware(getTenantId) {
|
|
85
|
+
return async (params, next) => {
|
|
86
|
+
const tenantModels = [
|
|
87
|
+
'Tenant', 'User', 'Role', 'Permission', 'Space', 'Zone',
|
|
88
|
+
'Device', 'Subscription', 'Scenario', 'Notification',
|
|
89
|
+
];
|
|
90
|
+
if (!tenantModels.includes(params.model || '')) {
|
|
91
|
+
return next(params);
|
|
92
|
+
}
|
|
93
|
+
const tenantId = getTenantId();
|
|
94
|
+
const readActions = ['findUnique', 'findFirst', 'findMany', 'count', 'aggregate', 'groupBy'];
|
|
95
|
+
if (readActions.includes(params.action)) {
|
|
96
|
+
params.args = params.args || {};
|
|
97
|
+
params.args.where = {
|
|
98
|
+
...params.args.where,
|
|
99
|
+
tenantId,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (params.action === 'create') {
|
|
103
|
+
params.args = params.args || {};
|
|
104
|
+
params.args.data = {
|
|
105
|
+
...params.args.data,
|
|
106
|
+
tenantId,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const writeActions = ['update', 'updateMany', 'delete', 'deleteMany', 'upsert'];
|
|
110
|
+
if (writeActions.includes(params.action)) {
|
|
111
|
+
params.args = params.args || {};
|
|
112
|
+
if (params.args.where) {
|
|
113
|
+
params.args.where = {
|
|
114
|
+
...params.args.where,
|
|
115
|
+
tenantId,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return next(params);
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function extractTenantFromGrpcMetadata(metadata) {
|
|
123
|
+
return {
|
|
124
|
+
tenantId: metadata.get('x-tenant-id')?.[0] || '',
|
|
125
|
+
userId: metadata.get('x-user-id')?.[0] || null,
|
|
126
|
+
roles: (metadata.get('x-roles')?.[0] || '').split(',').filter(Boolean),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=tenant.middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenant.middleware.js","sourceRoot":"","sources":["../../src/auth/tenant.middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OnModuleInit, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { ConfigService } from '@nestjs/config';
|
|
3
|
+
export interface FeatureFlag {
|
|
4
|
+
value: boolean;
|
|
5
|
+
description?: string;
|
|
6
|
+
rollout?: number;
|
|
7
|
+
tenants?: string | string[];
|
|
8
|
+
config?: Record<string, any>;
|
|
9
|
+
}
|
|
10
|
+
export declare class FeatureFlagService implements OnModuleInit, OnModuleDestroy {
|
|
11
|
+
private readonly config;
|
|
12
|
+
private readonly logger;
|
|
13
|
+
private readonly flags;
|
|
14
|
+
private pollTimer;
|
|
15
|
+
private readonly POLL_INTERVAL;
|
|
16
|
+
private readonly KV_PREFIX;
|
|
17
|
+
constructor(config: ConfigService);
|
|
18
|
+
onModuleInit(): Promise<void>;
|
|
19
|
+
onModuleDestroy(): void;
|
|
20
|
+
isEnabled(flagName: string, tenantId?: string): Promise<boolean>;
|
|
21
|
+
getConfig<T = Record<string, any>>(flagName: string): Promise<T | null>;
|
|
22
|
+
getAllFlags(): Map<string, FeatureFlag>;
|
|
23
|
+
private loadFlags;
|
|
24
|
+
private loadDefaults;
|
|
25
|
+
private simpleHash;
|
|
26
|
+
}
|