ts-prorm-orm 1.1.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 +1111 -0
- package/LICENSE +21 -0
- package/README.md +573 -0
- package/dist/audit/history-query.js +180 -0
- package/dist/audit/index.js +23 -0
- package/dist/audit/logger.js +236 -0
- package/dist/cache/cache-manager.js +84 -0
- package/dist/cache/index.js +16 -0
- package/dist/cache/redis-cluster-cache.js +557 -0
- package/dist/cli.js +2200 -0
- package/dist/compliance/audit-trail.js +68 -0
- package/dist/compliance/backup-verification.js +685 -0
- package/dist/compliance/breach-detector.js +505 -0
- package/dist/compliance/consent-record.js +227 -0
- package/dist/compliance/consent-versioning.js +331 -0
- package/dist/compliance/cross-border-log.js +530 -0
- package/dist/compliance/data-classifier.js +258 -0
- package/dist/compliance/data-lineage.js +565 -0
- package/dist/compliance/data-masker.js +303 -0
- package/dist/compliance/data-portability.js +265 -0
- package/dist/compliance/data-retention.js +195 -0
- package/dist/compliance/dsar-workflow.js +342 -0
- package/dist/compliance/field-encryption.js +223 -0
- package/dist/compliance/immutable-record.js +148 -0
- package/dist/compliance/index.js +260 -0
- package/dist/compliance/privacy-impact-assessment.js +364 -0
- package/dist/compliance/pseudonymization.js +269 -0
- package/dist/compliance/query-firewall.js +1182 -0
- package/dist/compliance/rate-limiter.js +580 -0
- package/dist/compliance/right-to-erasure.js +117 -0
- package/dist/compliance/row-level-security.js +246 -0
- package/dist/compliance/security-decorator.js +574 -0
- package/dist/compliance/security-monitor.js +525 -0
- package/dist/compliance/sensitive-data-discovery.js +476 -0
- package/dist/compliance/session-isolation.js +472 -0
- package/dist/compliance/tls-enforcer.js +108 -0
- package/dist/compliance/worm-storage.js +712 -0
- package/dist/connection-manager.js +198 -0
- package/dist/connection-pool.js +519 -0
- package/dist/decorators/audit.js +136 -0
- package/dist/decorators/belongs-to-many.js +115 -0
- package/dist/decorators/belongs-to.js +115 -0
- package/dist/decorators/check.js +435 -0
- package/dist/decorators/collate.js +329 -0
- package/dist/decorators/comment.js +205 -0
- package/dist/decorators/database-settings.js +236 -0
- package/dist/decorators/default.js +244 -0
- package/dist/decorators/encryption.js +235 -0
- package/dist/decorators/engine.js +97 -0
- package/dist/decorators/fk-constraints.js +594 -0
- package/dist/decorators/foreign-table.js +136 -0
- package/dist/decorators/generated.js +274 -0
- package/dist/decorators/has-many.js +127 -0
- package/dist/decorators/has-one.js +116 -0
- package/dist/decorators/hstore.js +129 -0
- package/dist/decorators/index.js +355 -0
- package/dist/decorators/json-column.js +83 -0
- package/dist/decorators/jsonb.js +101 -0
- package/dist/decorators/orm-decorators.js +425 -0
- package/dist/decorators/permissions.js +294 -0
- package/dist/decorators/procedure.js +210 -0
- package/dist/decorators/query-options.js +556 -0
- package/dist/decorators/range.js +167 -0
- package/dist/decorators/set-column.js +83 -0
- package/dist/decorators/spatial.js +114 -0
- package/dist/decorators/storage.js +580 -0
- package/dist/decorators/timezone.js +512 -0
- package/dist/decorators/trigger.js +90 -0
- package/dist/decorators/uuid.js +135 -0
- package/dist/decorators/view.js +258 -0
- package/dist/diagrams/chen-diagram.js +354 -0
- package/dist/diagrams/class-diagram.js +384 -0
- package/dist/diagrams/dependency-diagram.js +432 -0
- package/dist/diagrams/er-diagram.js +605 -0
- package/dist/diagrams/flow-diagram.js +394 -0
- package/dist/diagrams/gantt-diagram.js +411 -0
- package/dist/diagrams/index-diagram.js +353 -0
- package/dist/diagrams/index.js +184 -0
- package/dist/diagrams/migration-diagram.js +316 -0
- package/dist/diagrams/model-diagram.js +616 -0
- package/dist/diagrams/package-diagram.js +376 -0
- package/dist/diagrams/palette.js +85 -0
- package/dist/diagrams/relational-diagram.js +455 -0
- package/dist/diagrams/schemadoc-diagram.js +309 -0
- package/dist/diagrams/sequence-diagram.js +307 -0
- package/dist/diagrams/state-diagram.js +344 -0
- package/dist/diagrams/svg-dom.js +111 -0
- package/dist/diagrams/tree-diagram.js +250 -0
- package/dist/dialects/clickhouse/index.js +1742 -0
- package/dist/dialects/cockroachdb/index.js +4677 -0
- package/dist/dialects/cratedb/index.js +441 -0
- package/dist/dialects/databricks/index.js +517 -0
- package/dist/dialects/db2/index.js +2225 -0
- package/dist/dialects/dialect.js +720 -0
- package/dist/dialects/duckdb/index.js +2014 -0
- package/dist/dialects/exasol/index.js +357 -0
- package/dist/dialects/firebird/index.js +495 -0
- package/dist/dialects/greenplum/index.js +457 -0
- package/dist/dialects/hana/index.js +1741 -0
- package/dist/dialects/mariadb/index.js +3590 -0
- package/dist/dialects/mssql/index.js +2296 -0
- package/dist/dialects/mysql/index.js +4058 -0
- package/dist/dialects/oracle/index.js +2999 -0
- package/dist/dialects/postgres/index.js +5319 -0
- package/dist/dialects/query-stream-helper.js +152 -0
- package/dist/dialects/questdb/index.js +433 -0
- package/dist/dialects/redshift/index.js +2277 -0
- package/dist/dialects/singlestore/index.js +354 -0
- package/dist/dialects/snowflake/index.js +2082 -0
- package/dist/dialects/spanner/index.js +1768 -0
- package/dist/dialects/sqlite/index.js +3382 -0
- package/dist/dialects/tidb/index.js +377 -0
- package/dist/dialects/timescaledb/index.js +164 -0
- package/dist/dialects/trino/index.js +420 -0
- package/dist/dialects/turso/index.js +227 -0
- package/dist/dialects/vertica/index.js +317 -0
- package/dist/dialects/yugabytedb/index.js +424 -0
- package/dist/errors/index.js +472 -0
- package/dist/errors/utils.js +340 -0
- package/dist/errors.js +21 -0
- package/dist/extensions/catalog/cloud-warehouse-features.js +207 -0
- package/dist/extensions/catalog/mssql-features.js +147 -0
- package/dist/extensions/catalog/mysql-mariadb-plugins.js +229 -0
- package/dist/extensions/catalog/oracle-db2-features.js +196 -0
- package/dist/extensions/catalog/postgres-extensions.js +516 -0
- package/dist/extensions/index.js +71 -0
- package/dist/extensions/types.js +13 -0
- package/dist/foreign-data.js +173 -0
- package/dist/hooks/hooks-manager.js +350 -0
- package/dist/hooks/index.js +37 -0
- package/dist/index.js +463 -0
- package/dist/logging.js +335 -0
- package/dist/migrations/index.js +40 -0
- package/dist/migrations/migration.js +196 -0
- package/dist/migrations/migrator.js +411 -0
- package/dist/migrations/prormmigration.js +275 -0
- package/dist/migrations/query-interface.js +435 -0
- package/dist/migrations/seeder.js +353 -0
- package/dist/models/associations.js +852 -0
- package/dist/models/constraints.js +288 -0
- package/dist/models/data-types.js +2518 -0
- package/dist/models/decorators.js +445 -0
- package/dist/models/index.js +33 -0
- package/dist/models/indexes.js +531 -0
- package/dist/models/methods.js +382 -0
- package/dist/models/model-manager.js +103 -0
- package/dist/models/model.js +5349 -0
- package/dist/models/operators.js +67 -0
- package/dist/models/scopes.js +189 -0
- package/dist/models/typescript-types.js +26 -0
- package/dist/nosql/aerospike/index.js +205 -0
- package/dist/nosql/allegrograph/index.js +169 -0
- package/dist/nosql/arangodb/index.js +364 -0
- package/dist/nosql/azure-blob/index.js +206 -0
- package/dist/nosql/beanstalkd/index.js +231 -0
- package/dist/nosql/beequeue/index.js +210 -0
- package/dist/nosql/bigchaindb/index.js +195 -0
- package/dist/nosql/bigtable/index.js +224 -0
- package/dist/nosql/blazegraph/index.js +173 -0
- package/dist/nosql/bullmq/index.js +191 -0
- package/dist/nosql/cassandra/index.js +174 -0
- package/dist/nosql/chroma/index.js +190 -0
- package/dist/nosql/cloudflare-kv/index.js +220 -0
- package/dist/nosql/coherence/index.js +200 -0
- package/dist/nosql/cosmosdb/index.js +157 -0
- package/dist/nosql/couchbase/index.js +213 -0
- package/dist/nosql/dax/index.js +212 -0
- package/dist/nosql/deno-kv/index.js +206 -0
- package/dist/nosql/dgraph/index.js +171 -0
- package/dist/nosql/doris/index.js +169 -0
- package/dist/nosql/druid/index.js +162 -0
- package/dist/nosql/dynamodb/index.js +937 -0
- package/dist/nosql/elasticsearch/index.js +377 -0
- package/dist/nosql/etcd/index.js +502 -0
- package/dist/nosql/eventhubs/index.js +213 -0
- package/dist/nosql/eventstore/index.js +254 -0
- package/dist/nosql/faunadb/index.js +188 -0
- package/dist/nosql/firestore/index.js +177 -0
- package/dist/nosql/fluree/index.js +148 -0
- package/dist/nosql/fuseki/index.js +170 -0
- package/dist/nosql/gcs/index.js +172 -0
- package/dist/nosql/gearman/index.js +160 -0
- package/dist/nosql/geode/index.js +196 -0
- package/dist/nosql/graphdb/index.js +169 -0
- package/dist/nosql/graylog/index.js +188 -0
- package/dist/nosql/gridgain/index.js +171 -0
- package/dist/nosql/hazelcast/index.js +162 -0
- package/dist/nosql/hbase/index.js +230 -0
- package/dist/nosql/ignite/index.js +173 -0
- package/dist/nosql/immudb/index.js +184 -0
- package/dist/nosql/index.js +232 -0
- package/dist/nosql/infinispan/index.js +200 -0
- package/dist/nosql/influxdb/index.js +0 -0
- package/dist/nosql/kafka/index.js +234 -0
- package/dist/nosql/keyspaces/index.js +189 -0
- package/dist/nosql/kinesis/index.js +253 -0
- package/dist/nosql/leveldb/index.js +153 -0
- package/dist/nosql/lmdb/index.js +160 -0
- package/dist/nosql/loki/index.js +201 -0
- package/dist/nosql/marklogic/index.js +204 -0
- package/dist/nosql/materialize/index.js +145 -0
- package/dist/nosql/meilisearch/index.js +154 -0
- package/dist/nosql/memcached/index.js +223 -0
- package/dist/nosql/milvus/index.js +410 -0
- package/dist/nosql/minio/index.js +265 -0
- package/dist/nosql/momento/index.js +179 -0
- package/dist/nosql/mongodb/index.js +461 -0
- package/dist/nosql/nats/index.js +247 -0
- package/dist/nosql/nedb/index.js +164 -0
- package/dist/nosql/neo4j/index.js +450 -0
- package/dist/nosql/neptune/index.js +470 -0
- package/dist/nosql/nsq/index.js +200 -0
- package/dist/nosql/opensearch/index.js +186 -0
- package/dist/nosql/orientdb/index.js +175 -0
- package/dist/nosql/papertrail/index.js +200 -0
- package/dist/nosql/pinecone/index.js +0 -0
- package/dist/nosql/pinot/index.js +133 -0
- package/dist/nosql/pouchdb/index.js +172 -0
- package/dist/nosql/prometheus/index.js +174 -0
- package/dist/nosql/provendb/index.js +147 -0
- package/dist/nosql/pubsub/index.js +187 -0
- package/dist/nosql/pulsar/index.js +232 -0
- package/dist/nosql/qdrant/index.js +295 -0
- package/dist/nosql/qldb/index.js +192 -0
- package/dist/nosql/r2/index.js +281 -0
- package/dist/nosql/rabbitmq/index.js +237 -0
- package/dist/nosql/ravendb/index.js +175 -0
- package/dist/nosql/redis/index.js +607 -0
- package/dist/nosql/redpanda/index.js +237 -0
- package/dist/nosql/resque/index.js +203 -0
- package/dist/nosql/rethinkdb/index.js +232 -0
- package/dist/nosql/rocksdb/index.js +152 -0
- package/dist/nosql/rockset/index.js +126 -0
- package/dist/nosql/s3/index.js +298 -0
- package/dist/nosql/scylladb/index.js +178 -0
- package/dist/nosql/signoz/index.js +227 -0
- package/dist/nosql/sns/index.js +201 -0
- package/dist/nosql/solr/index.js +231 -0
- package/dist/nosql/splunk/index.js +227 -0
- package/dist/nosql/sqs/index.js +246 -0
- package/dist/nosql/stardog/index.js +169 -0
- package/dist/nosql/starrocks/index.js +170 -0
- package/dist/nosql/store.js +2 -0
- package/dist/nosql/sumologic/index.js +213 -0
- package/dist/nosql/surrealdb/index.js +179 -0
- package/dist/nosql/terminusdb/index.js +151 -0
- package/dist/nosql/tigergraph/index.js +469 -0
- package/dist/nosql/typesense/index.js +148 -0
- package/dist/nosql/unqlite/index.js +157 -0
- package/dist/nosql/upstash/index.js +167 -0
- package/dist/nosql/vercel-kv/index.js +166 -0
- package/dist/nosql/victoriametrics/index.js +213 -0
- package/dist/nosql/virtuoso/index.js +169 -0
- package/dist/nosql/weaviate/index.js +276 -0
- package/dist/operators/index.js +108 -0
- package/dist/operators.js +2690 -0
- package/dist/prisma-migrate/index.js +38 -0
- package/dist/prisma-migrate/migration-generator.js +125 -0
- package/dist/prisma-migrate/model-generator.js +172 -0
- package/dist/prisma-migrate/relations.js +100 -0
- package/dist/prisma-migrate/schema-parser.js +167 -0
- package/dist/prisma-migrate/type-mapper.js +40 -0
- package/dist/prorm.js +6832 -0
- package/dist/query-builders/cte-builder.js +80 -0
- package/dist/query-builders/functions/aggregate.js +390 -0
- package/dist/query-builders/functions/conditional.js +503 -0
- package/dist/query-builders/functions/datetime.js +695 -0
- package/dist/query-builders/functions/fulltext.js +439 -0
- package/dist/query-builders/functions/index.js +93 -0
- package/dist/query-builders/functions/json.js +427 -0
- package/dist/query-builders/functions/math.js +399 -0
- package/dist/query-builders/functions/string.js +518 -0
- package/dist/query-builders/functions/window.js +328 -0
- package/dist/query-builders/include-builder.js +323 -0
- package/dist/query-builders/index-expression-builder.js +242 -0
- package/dist/query-builders/index.js +161 -0
- package/dist/query-builders/insert-builder.js +164 -0
- package/dist/query-builders/model-helpers.js +38 -0
- package/dist/query-builders/order-limit-builder.js +239 -0
- package/dist/query-builders/sql-compiler.js +2040 -0
- package/dist/query-builders/subquery-builder.js +152 -0
- package/dist/query-builders/update-builder.js +182 -0
- package/dist/query-builders/view-builder.js +218 -0
- package/dist/query-builders/where-builder.js +1532 -0
- package/dist/query-interface.js +622 -0
- package/dist/query-optimizers/batch-optimizer.js +426 -0
- package/dist/query-optimizers/explain-plans.js +415 -0
- package/dist/query-optimizers/index.js +51 -0
- package/dist/query-optimizers/prepared-statement-cache.js +423 -0
- package/dist/query-optimizers/query-hints.js +438 -0
- package/dist/query-optimizers/query-optimizer.js +278 -0
- package/dist/query-optimizers/slow-query-logger.js +271 -0
- package/dist/replica-manager.js +507 -0
- package/dist/schema/index.js +15 -0
- package/dist/schema/migration-generator.js +374 -0
- package/dist/schema/schema-differ.js +549 -0
- package/dist/schema/types.js +6 -0
- package/dist/sql-constants.js +300 -0
- package/dist/sqlite-advanced.js +1047 -0
- package/dist/streams/index.js +12 -0
- package/dist/streams/transforms.js +178 -0
- package/dist/transaction.js +456 -0
- package/dist/types/index.js +180 -0
- package/dist/user-management.js +127 -0
- package/dist/utils/date.js +300 -0
- package/dist/utils/index.js +1079 -0
- package/dist/utils/string.js +161 -0
- package/dist/validators/index.js +19 -0
- package/dist/validators/validator.js +911 -0
- package/package.json +190 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SessionIsolation
|
|
4
|
+
*
|
|
5
|
+
* Provides multi-tenant data isolation using AsyncLocalStorage for automatic
|
|
6
|
+
* tenant context propagation. Implements tenant-specific encryption keys,
|
|
7
|
+
* session management, and tenant-aware audit logging.
|
|
8
|
+
*
|
|
9
|
+
* Covers compliance frameworks including:
|
|
10
|
+
* ISO 27017, FedRAMP, CPRA, GDPR, HIPAA, SOC 2, NIST SP 800-53, and more.
|
|
11
|
+
*
|
|
12
|
+
* Usage:
|
|
13
|
+
* // At app startup, initialize session isolation:
|
|
14
|
+
* SessionIsolation.init({ sessionTimeout: 30 * 60 * 1000 });
|
|
15
|
+
*
|
|
16
|
+
* // Register tenant encryption keys:
|
|
17
|
+
* SessionIsolation.registerTenantKey('tenant-123', 'encryption-key-32-bytes');
|
|
18
|
+
*
|
|
19
|
+
* // In your authentication middleware:
|
|
20
|
+
* SessionIsolation.setTenant({
|
|
21
|
+
* tenantId: 'tenant-123',
|
|
22
|
+
* sessionId: 'session-abc',
|
|
23
|
+
* userId: 'user-456',
|
|
24
|
+
* roles: ['admin']
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // Run operations in a specific tenant context:
|
|
28
|
+
* const result = SessionIsolation.runInTenant('tenant-123', () => {
|
|
29
|
+
* return db.User.findAll();
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* // Use the decorator on models:
|
|
33
|
+
* @TenantIsolation({ tenantId: 'tenantId', auditEnabled: true })
|
|
34
|
+
* class Document extends Model { ... }
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.TenantIsolationManager = exports.SessionIsolation = void 0;
|
|
38
|
+
exports.TenantIsolation = TenantIsolation;
|
|
39
|
+
exports.getTenantIsolationOptions = getTenantIsolationOptions;
|
|
40
|
+
exports.sessionIsolationMiddleware = sessionIsolationMiddleware;
|
|
41
|
+
exports.getTenantAuditMetadata = getTenantAuditMetadata;
|
|
42
|
+
const async_hooks_1 = require("async_hooks");
|
|
43
|
+
const operators_1 = require("../models/operators");
|
|
44
|
+
// ==================== Symbol Keys for Metadata ====================
|
|
45
|
+
const TENANT_CONTEXT_KEY = Symbol('tenantContext');
|
|
46
|
+
const SESSION_ISOLATION_DATA = Symbol('sessionIsolationData');
|
|
47
|
+
// ==================== In-Memory Storage ====================
|
|
48
|
+
class InMemorySessionStore {
|
|
49
|
+
constructor() {
|
|
50
|
+
this.sessions = new Map();
|
|
51
|
+
}
|
|
52
|
+
async get(sessionId) {
|
|
53
|
+
return this.sessions.get(sessionId) ?? null;
|
|
54
|
+
}
|
|
55
|
+
async set(sessionId, info) {
|
|
56
|
+
this.sessions.set(sessionId, info);
|
|
57
|
+
}
|
|
58
|
+
async delete(sessionId) {
|
|
59
|
+
this.sessions.delete(sessionId);
|
|
60
|
+
}
|
|
61
|
+
async getByTenant(tenantId) {
|
|
62
|
+
return [...this.sessions.values()].filter((s) => s.tenantId === tenantId);
|
|
63
|
+
}
|
|
64
|
+
async getByUser(userId) {
|
|
65
|
+
return [...this.sessions.values()].filter((s) => s.userId === userId);
|
|
66
|
+
}
|
|
67
|
+
async cleanup(expiredBefore) {
|
|
68
|
+
let count = 0;
|
|
69
|
+
for (const [id, session] of this.sessions) {
|
|
70
|
+
if (session.expiresAt < expiredBefore) {
|
|
71
|
+
this.sessions.delete(id);
|
|
72
|
+
count++;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return count;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
class InMemoryKeyStore {
|
|
79
|
+
constructor() {
|
|
80
|
+
this.keys = new Map();
|
|
81
|
+
}
|
|
82
|
+
async get(tenantId) {
|
|
83
|
+
return this.keys.get(tenantId) ?? null;
|
|
84
|
+
}
|
|
85
|
+
async set(tenantId, key) {
|
|
86
|
+
this.keys.set(tenantId, key);
|
|
87
|
+
}
|
|
88
|
+
async delete(tenantId) {
|
|
89
|
+
this.keys.delete(tenantId);
|
|
90
|
+
}
|
|
91
|
+
async getAll() {
|
|
92
|
+
return [...this.keys.values()];
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// ==================== AsyncLocalStorage ====================
|
|
96
|
+
const tenantContextStore = new async_hooks_1.AsyncLocalStorage();
|
|
97
|
+
// Global state
|
|
98
|
+
let isInitialized = false;
|
|
99
|
+
let options = {};
|
|
100
|
+
let sessionStore;
|
|
101
|
+
let keyStore;
|
|
102
|
+
let cleanupTimer = null;
|
|
103
|
+
// ==================== SessionIsolation Class ====================
|
|
104
|
+
class SessionIsolation {
|
|
105
|
+
/**
|
|
106
|
+
* Initialize session isolation. Call once at application startup.
|
|
107
|
+
*
|
|
108
|
+
* @param opts - Configuration options
|
|
109
|
+
*/
|
|
110
|
+
static init(opts = {}) {
|
|
111
|
+
if (isInitialized) {
|
|
112
|
+
console.warn('[SessionIsolation] Already initialized. Call reset() first to reinitialize.');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
options = {
|
|
116
|
+
sessionTimeout: 30 * 60 * 1000, // 30 minutes
|
|
117
|
+
maxSessionsPerTenant: undefined,
|
|
118
|
+
maxSessionsPerUser: 5,
|
|
119
|
+
autoCleanup: true,
|
|
120
|
+
cleanupInterval: 5 * 60 * 1000, // 5 minutes
|
|
121
|
+
auditEnabled: true,
|
|
122
|
+
...opts,
|
|
123
|
+
};
|
|
124
|
+
// Initialize stores
|
|
125
|
+
sessionStore = options.sessionStore ?? new InMemorySessionStore();
|
|
126
|
+
keyStore = options.keyStore ?? new InMemoryKeyStore();
|
|
127
|
+
// Start cleanup timer
|
|
128
|
+
if (options.autoCleanup) {
|
|
129
|
+
cleanupTimer = setInterval(() => {
|
|
130
|
+
SessionIsolation.cleanupExpiredSessions().catch((err) => {
|
|
131
|
+
console.error('[SessionIsolation] Cleanup error:', err);
|
|
132
|
+
});
|
|
133
|
+
}, options.cleanupInterval);
|
|
134
|
+
}
|
|
135
|
+
isInitialized = true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Reset the session isolation system. Useful for testing.
|
|
139
|
+
*/
|
|
140
|
+
static reset() {
|
|
141
|
+
if (cleanupTimer) {
|
|
142
|
+
clearInterval(cleanupTimer);
|
|
143
|
+
cleanupTimer = null;
|
|
144
|
+
}
|
|
145
|
+
isInitialized = false;
|
|
146
|
+
options = {};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Set the current tenant context for this request/operation.
|
|
150
|
+
*
|
|
151
|
+
* @param ctx - The tenant context
|
|
152
|
+
*/
|
|
153
|
+
static setTenant(ctx) {
|
|
154
|
+
if (!isInitialized) {
|
|
155
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
156
|
+
}
|
|
157
|
+
// enterWith() propagates the context for the remainder of the current
|
|
158
|
+
// synchronous execution and any asynchronous operations chained from
|
|
159
|
+
// it, without requiring the caller to wrap downstream work in a
|
|
160
|
+
// callback. For strict per-request isolation under concurrent load,
|
|
161
|
+
// prefer runInTenant() or sessionIsolationMiddleware(), which wrap the
|
|
162
|
+
// actual downstream work in tenantContextStore.run() directly.
|
|
163
|
+
tenantContextStore.enterWith(ctx);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Get the current tenant context.
|
|
167
|
+
*
|
|
168
|
+
* @returns The current tenant context, or undefined if not set
|
|
169
|
+
*/
|
|
170
|
+
static getTenant() {
|
|
171
|
+
return tenantContextStore.getStore();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Clear the current tenant context.
|
|
175
|
+
*/
|
|
176
|
+
static clearTenant() {
|
|
177
|
+
tenantContextStore.enterWith(undefined);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Run a function within a specific tenant context.
|
|
181
|
+
*
|
|
182
|
+
* @param tenantId - The tenant ID to run in
|
|
183
|
+
* @param fn - The function to execute
|
|
184
|
+
* @returns The result of the function
|
|
185
|
+
*/
|
|
186
|
+
static runInTenant(tenantId, fn) {
|
|
187
|
+
const ctx = {
|
|
188
|
+
tenantId,
|
|
189
|
+
sessionId: `isolated-${Date.now()}`,
|
|
190
|
+
};
|
|
191
|
+
return tenantContextStore.run(ctx, fn);
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Register an encryption key for a tenant.
|
|
195
|
+
*
|
|
196
|
+
* @param tenantId - The tenant ID
|
|
197
|
+
* @param key - The encryption key (32 bytes recommended for AES-256)
|
|
198
|
+
* @param keyVersion - Optional key version for rotation
|
|
199
|
+
*/
|
|
200
|
+
static async registerTenantKey(tenantId, key, keyVersion) {
|
|
201
|
+
if (!isInitialized) {
|
|
202
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
203
|
+
}
|
|
204
|
+
const tenantKey = {
|
|
205
|
+
tenantId,
|
|
206
|
+
key,
|
|
207
|
+
keyVersion,
|
|
208
|
+
createdAt: new Date(),
|
|
209
|
+
};
|
|
210
|
+
await keyStore.set(tenantId, tenantKey);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get the encryption key for a tenant.
|
|
214
|
+
*
|
|
215
|
+
* @param tenantId - The tenant ID
|
|
216
|
+
* @returns The tenant key, or undefined if not found
|
|
217
|
+
*/
|
|
218
|
+
static async getTenantKey(tenantId) {
|
|
219
|
+
if (!isInitialized) {
|
|
220
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
221
|
+
}
|
|
222
|
+
return (await keyStore.get(tenantId)) ?? undefined;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Validate a session.
|
|
226
|
+
*
|
|
227
|
+
* @param sessionId - The session ID to validate
|
|
228
|
+
* @returns SessionInfo if valid, null if invalid/expired
|
|
229
|
+
*/
|
|
230
|
+
static async validateSession(sessionId) {
|
|
231
|
+
if (!isInitialized) {
|
|
232
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
233
|
+
}
|
|
234
|
+
const session = await sessionStore.get(sessionId);
|
|
235
|
+
if (!session) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
// Check if expired
|
|
239
|
+
if (new Date() > session.expiresAt) {
|
|
240
|
+
await sessionStore.delete(sessionId);
|
|
241
|
+
return null;
|
|
242
|
+
}
|
|
243
|
+
// Check if inactive
|
|
244
|
+
if (!session.isActive) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
// Update last activity
|
|
248
|
+
session.lastActivityAt = new Date();
|
|
249
|
+
await sessionStore.set(sessionId, session);
|
|
250
|
+
return session;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Manually expire a session.
|
|
254
|
+
*
|
|
255
|
+
* @param sessionId - The session ID to expire
|
|
256
|
+
*/
|
|
257
|
+
static async expireSession(sessionId) {
|
|
258
|
+
if (!isInitialized) {
|
|
259
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
260
|
+
}
|
|
261
|
+
await sessionStore.delete(sessionId);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Create a new session for a tenant.
|
|
265
|
+
*
|
|
266
|
+
* @param tenantId - The tenant ID
|
|
267
|
+
* @param userId - The user ID
|
|
268
|
+
* @param options - Additional session options
|
|
269
|
+
* @returns The created session info
|
|
270
|
+
*/
|
|
271
|
+
static async createSession(tenantId, userId, options) {
|
|
272
|
+
if (!isInitialized) {
|
|
273
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
274
|
+
}
|
|
275
|
+
const sessionId = `session-${tenantId}-${userId}-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
|
|
276
|
+
const now = new Date();
|
|
277
|
+
const expiresAt = options?.customExpiresAt ?? new Date(now.getTime() + SessionIsolation.getSessionTimeout());
|
|
278
|
+
const sessionInfo = {
|
|
279
|
+
sessionId,
|
|
280
|
+
tenantId,
|
|
281
|
+
userId,
|
|
282
|
+
createdAt: now,
|
|
283
|
+
expiresAt,
|
|
284
|
+
lastActivityAt: now,
|
|
285
|
+
isActive: true,
|
|
286
|
+
ipAddress: options?.ipAddress,
|
|
287
|
+
userAgent: options?.userAgent,
|
|
288
|
+
};
|
|
289
|
+
await sessionStore.set(sessionId, sessionInfo);
|
|
290
|
+
return sessionInfo;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get all active sessions for a tenant.
|
|
294
|
+
*
|
|
295
|
+
* @param tenantId - The tenant ID
|
|
296
|
+
* @returns Array of session info
|
|
297
|
+
*/
|
|
298
|
+
static async getTenantSessions(tenantId) {
|
|
299
|
+
if (!isInitialized) {
|
|
300
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
301
|
+
}
|
|
302
|
+
return sessionStore.getByTenant(tenantId);
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Get all active sessions for a user.
|
|
306
|
+
*
|
|
307
|
+
* @param userId - The user ID
|
|
308
|
+
* @returns Array of session info
|
|
309
|
+
*/
|
|
310
|
+
static async getUserSessions(userId) {
|
|
311
|
+
if (!isInitialized) {
|
|
312
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
313
|
+
}
|
|
314
|
+
return sessionStore.getByUser(userId);
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Clean up all expired sessions.
|
|
318
|
+
*
|
|
319
|
+
* @returns Number of sessions cleaned up
|
|
320
|
+
*/
|
|
321
|
+
static async cleanupExpiredSessions() {
|
|
322
|
+
if (!isInitialized) {
|
|
323
|
+
throw new Error('[SessionIsolation] Not initialized. Call init() first.');
|
|
324
|
+
}
|
|
325
|
+
const expiredBefore = new Date();
|
|
326
|
+
return sessionStore.cleanup(expiredBefore);
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Get the current session timeout value.
|
|
330
|
+
*
|
|
331
|
+
* @returns Timeout in milliseconds
|
|
332
|
+
*/
|
|
333
|
+
static getSessionTimeout() {
|
|
334
|
+
return options.sessionTimeout ?? 30 * 60 * 1000;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Check if session isolation is initialized.
|
|
338
|
+
*
|
|
339
|
+
* @returns True if initialized
|
|
340
|
+
*/
|
|
341
|
+
static isReady() {
|
|
342
|
+
return isInitialized;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
exports.SessionIsolation = SessionIsolation;
|
|
346
|
+
// ==================== Decorator ====================
|
|
347
|
+
/**
|
|
348
|
+
* Decorator to apply tenant isolation to a model.
|
|
349
|
+
*
|
|
350
|
+
* @TenantIsolation({ tenantId: 'tenantId', auditEnabled: true })
|
|
351
|
+
* class Document extends Model {
|
|
352
|
+
* tenantId: string;
|
|
353
|
+
* title: string;
|
|
354
|
+
* }
|
|
355
|
+
*
|
|
356
|
+
* @param options - Tenant isolation options
|
|
357
|
+
*/
|
|
358
|
+
function TenantIsolation(options) {
|
|
359
|
+
return function (target) {
|
|
360
|
+
// Store decorator metadata on the model
|
|
361
|
+
Reflect.defineMetadata(TENANT_CONTEXT_KEY, options, target);
|
|
362
|
+
// Add tenant context to query hooks if model has beforeFind
|
|
363
|
+
if (target.prototype?.beforeFind) {
|
|
364
|
+
target.addHook('beforeFind', 'orm:tenantFilter', (opts) => {
|
|
365
|
+
const ctx = SessionIsolation.getTenant();
|
|
366
|
+
if (!ctx)
|
|
367
|
+
return;
|
|
368
|
+
// Add tenant filter to where clause
|
|
369
|
+
const tenantField = options.tenantId;
|
|
370
|
+
if (tenantField && ctx.tenantId) {
|
|
371
|
+
opts.where = opts.where
|
|
372
|
+
? { [operators_1.Op.and]: [opts.where, { [tenantField]: ctx.tenantId }] }
|
|
373
|
+
: { [tenantField]: ctx.tenantId };
|
|
374
|
+
}
|
|
375
|
+
// Add role check if required
|
|
376
|
+
if (options.requiredRoles?.length) {
|
|
377
|
+
const userRoles = ctx.roles ?? [];
|
|
378
|
+
const hasRequiredRole = options.requiredRoles.some((r) => userRoles.includes(r));
|
|
379
|
+
if (!hasRequiredRole) {
|
|
380
|
+
throw new Error(`Access denied. Required roles: ${options.requiredRoles.join(', ')}`);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Get tenant isolation options from a decorated model.
|
|
389
|
+
*
|
|
390
|
+
* @param model - The model class
|
|
391
|
+
* @returns The decorator options, or undefined if not decorated
|
|
392
|
+
*/
|
|
393
|
+
function getTenantIsolationOptions(model) {
|
|
394
|
+
return Reflect.getMetadata(TENANT_CONTEXT_KEY, model);
|
|
395
|
+
}
|
|
396
|
+
// ==================== Middleware Integration ====================
|
|
397
|
+
/**
|
|
398
|
+
* Express/Koa middleware to automatically set tenant context from request.
|
|
399
|
+
*
|
|
400
|
+
* app.use(authMiddleware); // sets req.user
|
|
401
|
+
* app.use(sessionIsolationMiddleware);
|
|
402
|
+
*
|
|
403
|
+
* @param options - Middleware options
|
|
404
|
+
*/
|
|
405
|
+
function sessionIsolationMiddleware(req, res, next) {
|
|
406
|
+
if (!isInitialized) {
|
|
407
|
+
console.warn('[SessionIsolation] Not initialized. Call init() first.');
|
|
408
|
+
next();
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
// Try to get tenant info from request (e.g., from auth middleware)
|
|
412
|
+
const tenantId = req.tenantId ?? req.user?.tenantId ?? req.headers['x-tenant-id'];
|
|
413
|
+
const sessionId = req.sessionId ?? req.headers['x-session-id'];
|
|
414
|
+
const userId = req.user?.id ?? req.userId;
|
|
415
|
+
if (tenantId) {
|
|
416
|
+
const ctx = {
|
|
417
|
+
tenantId,
|
|
418
|
+
sessionId,
|
|
419
|
+
userId,
|
|
420
|
+
roles: req.user?.roles ?? [],
|
|
421
|
+
metadata: {
|
|
422
|
+
ipAddress: req.ip ?? req.connection?.remoteAddress,
|
|
423
|
+
userAgent: req.headers['user-agent'],
|
|
424
|
+
},
|
|
425
|
+
};
|
|
426
|
+
// Wrap the downstream middleware/handler chain in the ALS context so
|
|
427
|
+
// the tenant context actually propagates to whatever `next()`
|
|
428
|
+
// triggers (including further async work), rather than being
|
|
429
|
+
// set-and-discarded before the real request handling runs.
|
|
430
|
+
tenantContextStore.run(ctx, next);
|
|
431
|
+
return;
|
|
432
|
+
}
|
|
433
|
+
next();
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Get tenant-aware audit metadata for the current context.
|
|
437
|
+
* Use this to include tenant information in audit logs.
|
|
438
|
+
*
|
|
439
|
+
* @returns Audit metadata object
|
|
440
|
+
*/
|
|
441
|
+
function getTenantAuditMetadata() {
|
|
442
|
+
const ctx = SessionIsolation.getTenant();
|
|
443
|
+
if (!ctx) {
|
|
444
|
+
return {};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
tenantId: ctx.tenantId,
|
|
448
|
+
sessionId: ctx.sessionId,
|
|
449
|
+
userId: ctx.userId,
|
|
450
|
+
roles: ctx.roles,
|
|
451
|
+
timestamp: new Date().toISOString(),
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// ==================== Class Export ====================
|
|
455
|
+
exports.TenantIsolationManager = {
|
|
456
|
+
init: SessionIsolation.init,
|
|
457
|
+
reset: SessionIsolation.reset,
|
|
458
|
+
setTenant: SessionIsolation.setTenant,
|
|
459
|
+
getTenant: SessionIsolation.getTenant,
|
|
460
|
+
clearTenant: SessionIsolation.clearTenant,
|
|
461
|
+
runInTenant: SessionIsolation.runInTenant,
|
|
462
|
+
registerTenantKey: SessionIsolation.registerTenantKey,
|
|
463
|
+
getTenantKey: SessionIsolation.getTenantKey,
|
|
464
|
+
validateSession: SessionIsolation.validateSession,
|
|
465
|
+
expireSession: SessionIsolation.expireSession,
|
|
466
|
+
createSession: SessionIsolation.createSession,
|
|
467
|
+
getTenantSessions: SessionIsolation.getTenantSessions,
|
|
468
|
+
getUserSessions: SessionIsolation.getUserSessions,
|
|
469
|
+
cleanupExpiredSessions: SessionIsolation.cleanupExpiredSessions,
|
|
470
|
+
getSessionTimeout: SessionIsolation.getSessionTimeout,
|
|
471
|
+
isReady: SessionIsolation.isReady,
|
|
472
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* TLSEnforcer
|
|
4
|
+
*
|
|
5
|
+
* Validates and enforces TLS/SSL on database connections at startup.
|
|
6
|
+
* Throws a descriptive error if the connection is not encrypted,
|
|
7
|
+
* preventing insecure deployments from reaching production.
|
|
8
|
+
*
|
|
9
|
+
* Covers ~54 compliance frameworks including:
|
|
10
|
+
* GDPR Art 32, HIPAA, PCI DSS, NIST SP 800-53, FedRAMP, SOC 2,
|
|
11
|
+
* ISO 27001, NYDFS 23 NYCRR 500, HITRUST, NIS2, DORA, and more.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* const prorm = new Prorm({ ... });
|
|
15
|
+
* TLSEnforcer.enforce(prorm); // strict
|
|
16
|
+
* TLSEnforcer.enforce(prorm, { mode: 'warn' }); // log only
|
|
17
|
+
* TLSEnforcer.enforce(prorm, { allowLocal: true }); // skip for localhost
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.TLSEnforcer = void 0;
|
|
21
|
+
const LOCAL_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '0.0.0.0']);
|
|
22
|
+
function isLocalHost(host) {
|
|
23
|
+
return LOCAL_HOSTS.has((host ?? '').toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
function extractConnectionOptions(prorm) {
|
|
26
|
+
const opts = prorm?.options ?? prorm?.config ?? {};
|
|
27
|
+
return {
|
|
28
|
+
dialect: opts.dialect ?? 'unknown',
|
|
29
|
+
host: opts.host,
|
|
30
|
+
dialectOptions: opts.dialectOptions ?? {},
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Determine whether the connection options specify TLS/SSL.
|
|
35
|
+
*/
|
|
36
|
+
function hasTLSConfigured(dialect, dialectOptions) {
|
|
37
|
+
// PostgreSQL / MariaDB / MySQL: ssl option in dialectOptions
|
|
38
|
+
if (dialectOptions?.ssl)
|
|
39
|
+
return true;
|
|
40
|
+
// MSSQL: encrypt in dialectOptions.options
|
|
41
|
+
if (dialectOptions?.options?.encrypt === true)
|
|
42
|
+
return true;
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
class TLSEnforcer {
|
|
46
|
+
/**
|
|
47
|
+
* Enforce TLS on the given Prorm instance.
|
|
48
|
+
* Call immediately after constructing the Prorm instance.
|
|
49
|
+
*
|
|
50
|
+
* @param prorm - The Prorm instance to inspect
|
|
51
|
+
* @param options - Enforcement mode and options
|
|
52
|
+
*/
|
|
53
|
+
static enforce(prorm, options = {}) {
|
|
54
|
+
const { mode = 'strict', allowLocal = false, logger = console.warn } = options;
|
|
55
|
+
if (mode === 'off')
|
|
56
|
+
return;
|
|
57
|
+
const { dialect, host, dialectOptions } = extractConnectionOptions(prorm);
|
|
58
|
+
if (allowLocal && isLocalHost(host))
|
|
59
|
+
return;
|
|
60
|
+
const tlsOk = hasTLSConfigured(dialect, dialectOptions);
|
|
61
|
+
if (!tlsOk) {
|
|
62
|
+
const message = `[TLSEnforcer] Database connection to "${host ?? 'unknown'}" (dialect: ${dialect}) ` +
|
|
63
|
+
`does not have SSL/TLS configured. ` +
|
|
64
|
+
`Set dialectOptions.ssl (PostgreSQL/MySQL/MariaDB) or dialectOptions.options.encrypt=true (MSSQL). ` +
|
|
65
|
+
`This is required by GDPR Art 32, HIPAA §164.312(e), PCI DSS Req 4, and many other frameworks.`;
|
|
66
|
+
if (mode === 'strict') {
|
|
67
|
+
throw new Error(message);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
logger(message);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Return recommended SSL dialectOptions for a given dialect.
|
|
76
|
+
* Use this as a starting point when configuring your connection.
|
|
77
|
+
*/
|
|
78
|
+
static recommendedOptions(dialect, options = {}) {
|
|
79
|
+
const { rejectUnauthorized = true, ca } = options;
|
|
80
|
+
switch (dialect) {
|
|
81
|
+
case 'postgres':
|
|
82
|
+
return {
|
|
83
|
+
ssl: {
|
|
84
|
+
require: true,
|
|
85
|
+
rejectUnauthorized,
|
|
86
|
+
...(ca ? { ca } : {}),
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
case 'mysql':
|
|
90
|
+
case 'mariadb':
|
|
91
|
+
return {
|
|
92
|
+
ssl: {
|
|
93
|
+
require: true,
|
|
94
|
+
rejectUnauthorized,
|
|
95
|
+
...(ca ? { ca } : {}),
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
case 'mssql':
|
|
99
|
+
return {
|
|
100
|
+
options: {
|
|
101
|
+
encrypt: true,
|
|
102
|
+
trustServerCertificate: !rejectUnauthorized,
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.TLSEnforcer = TLSEnforcer;
|