@zintrust/core 0.1.15 → 0.1.17
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 +2 -2
- package/package.json +1 -1
- package/public/index.html +1 -1
- package/src/cli/CLI.d.ts.map +1 -1
- package/src/cli/CLI.js +6 -0
- package/src/cli/commands/BroadcastWorkCommand.d.ts +10 -0
- package/src/cli/commands/BroadcastWorkCommand.d.ts.map +1 -0
- package/src/cli/commands/BroadcastWorkCommand.js +16 -0
- package/src/cli/commands/NotificationWorkCommand.d.ts +10 -0
- package/src/cli/commands/NotificationWorkCommand.d.ts.map +1 -0
- package/src/cli/commands/NotificationWorkCommand.js +16 -0
- package/src/cli/commands/QueueCommand.d.ts +10 -0
- package/src/cli/commands/QueueCommand.d.ts.map +1 -0
- package/src/cli/commands/QueueCommand.js +63 -0
- package/src/cli/commands/QueueWorkCommandUtils.d.ts +10 -0
- package/src/cli/commands/QueueWorkCommandUtils.d.ts.map +1 -0
- package/src/cli/commands/QueueWorkCommandUtils.js +43 -0
- package/src/cli/commands/createKindWorkCommand.d.ts +9 -0
- package/src/cli/commands/createKindWorkCommand.d.ts.map +1 -0
- package/src/cli/commands/createKindWorkCommand.js +33 -0
- package/src/cli/commands/index.d.ts +3 -0
- package/src/cli/commands/index.d.ts.map +1 -1
- package/src/cli/commands/index.js +3 -0
- package/src/cli/scaffolding/ModelGenerator.d.ts.map +1 -1
- package/src/cli/scaffolding/ModelGenerator.js +1 -0
- package/src/cli/scaffolding/ProjectScaffolder.d.ts.map +1 -1
- package/src/cli/scaffolding/ProjectScaffolder.js +2 -1
- package/src/cli/workers/QueueWorkRunner.d.ts +23 -0
- package/src/cli/workers/QueueWorkRunner.d.ts.map +1 -0
- package/src/cli/workers/QueueWorkRunner.js +142 -0
- package/src/collections/Collection.d.ts +30 -0
- package/src/collections/Collection.d.ts.map +1 -0
- package/src/collections/Collection.js +146 -0
- package/src/collections/index.d.ts +3 -0
- package/src/collections/index.d.ts.map +1 -0
- package/src/collections/index.js +1 -0
- package/src/config/env.d.ts +2 -0
- package/src/config/env.d.ts.map +1 -1
- package/src/config/env.js +4 -0
- package/src/config/index.d.ts +3 -0
- package/src/config/index.d.ts.map +1 -1
- package/src/config/security.d.ts +4 -1
- package/src/config/security.d.ts.map +1 -1
- package/src/config/security.js +9 -1
- package/src/events/EventDispatcher.d.ts +16 -0
- package/src/events/EventDispatcher.d.ts.map +1 -0
- package/src/events/EventDispatcher.js +90 -0
- package/src/events/index.d.ts +3 -0
- package/src/events/index.d.ts.map +1 -0
- package/src/events/index.js +1 -0
- package/src/features/Queue.d.ts +1 -1
- package/src/features/Queue.d.ts.map +1 -1
- package/src/features/Queue.js +2 -2
- package/src/http/Response.d.ts +2 -2
- package/src/http/Response.d.ts.map +1 -1
- package/src/index.d.ts +12 -0
- package/src/index.d.ts.map +1 -1
- package/src/index.js +12 -0
- package/src/middleware/CsrfMiddleware.d.ts.map +1 -1
- package/src/middleware/CsrfMiddleware.js +20 -25
- package/src/middleware/SessionMiddleware.d.ts +8 -0
- package/src/middleware/SessionMiddleware.d.ts.map +1 -0
- package/src/middleware/SessionMiddleware.js +15 -0
- package/src/node-singletons/crypto.d.ts +1 -1
- package/src/node-singletons/crypto.d.ts.map +1 -1
- package/src/node-singletons/crypto.js +1 -1
- package/src/orm/Model.d.ts +15 -0
- package/src/orm/Model.d.ts.map +1 -1
- package/src/orm/Model.js +57 -8
- package/src/orm/QueryBuilder.d.ts +9 -1
- package/src/orm/QueryBuilder.d.ts.map +1 -1
- package/src/orm/QueryBuilder.js +54 -2
- package/src/scripts/TemplateSync.js +23 -1
- package/src/security/EncryptedEnvelope.d.ts +77 -0
- package/src/security/EncryptedEnvelope.d.ts.map +1 -0
- package/src/security/EncryptedEnvelope.js +256 -0
- package/src/security/PasswordResetTokenBroker.d.ts +39 -0
- package/src/security/PasswordResetTokenBroker.d.ts.map +1 -0
- package/src/security/PasswordResetTokenBroker.js +131 -0
- package/src/security/StartupSecretValidation.d.ts.map +1 -1
- package/src/security/StartupSecretValidation.js +72 -0
- package/src/session/SessionManager.d.ts +39 -0
- package/src/session/SessionManager.d.ts.map +1 -0
- package/src/session/SessionManager.js +149 -0
- package/src/session/index.d.ts +3 -0
- package/src/session/index.d.ts.map +1 -0
- package/src/session/index.js +1 -0
- package/src/templates/features/Queue.ts.tpl +5 -4
- package/src/templates/project/basic/config/FileLogWriter.ts.tpl +4 -3
- package/src/templates/project/basic/config/SecretsManager.ts.tpl +1 -1
- package/src/templates/project/basic/config/broadcast.ts.tpl +2 -2
- package/src/templates/project/basic/config/cache.ts.tpl +2 -2
- package/src/templates/project/basic/config/database.ts.tpl +2 -2
- package/src/templates/project/basic/config/env.ts.tpl +5 -0
- package/src/templates/project/basic/config/features.ts.tpl +2 -2
- package/src/templates/project/basic/config/logger.ts.tpl +0 -2
- package/src/templates/project/basic/config/logging/HttpLogger.ts.tpl +1 -1
- package/src/templates/project/basic/config/logging/SlackLogger.ts.tpl +1 -1
- package/src/templates/project/basic/config/mail.ts.tpl +2 -2
- package/src/templates/project/basic/config/microservices.ts.tpl +1 -1
- package/src/templates/project/basic/config/middleware.ts.tpl +6 -9
- package/src/templates/project/basic/config/notification.ts.tpl +2 -2
- package/src/templates/project/basic/config/security.ts.tpl +12 -3
- package/src/templates/project/basic/config/storage.ts.tpl +2 -2
- package/src/templates/project/basic/config/type.ts.tpl +2 -2
- package/src/tools/broadcast/Broadcast.d.ts +8 -0
- package/src/tools/broadcast/Broadcast.d.ts.map +1 -1
- package/src/tools/broadcast/Broadcast.js +23 -0
- package/src/tools/notification/Notification.d.ts +10 -0
- package/src/tools/notification/Notification.d.ts.map +1 -1
- package/src/tools/notification/Notification.js +21 -0
- package/src/workers/BroadcastWorker.d.ts +22 -0
- package/src/workers/BroadcastWorker.d.ts.map +1 -0
- package/src/workers/BroadcastWorker.js +24 -0
- package/src/workers/NotificationWorker.d.ts +22 -0
- package/src/workers/NotificationWorker.d.ts.map +1 -0
- package/src/workers/NotificationWorker.js +23 -0
- package/src/workers/createQueueWorker.d.ts +24 -0
- package/src/workers/createQueueWorker.d.ts.map +1 -0
- package/src/workers/createQueueWorker.js +114 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { generateSecureJobId } from '../common/uuid.js';
|
|
2
|
+
const DEFAULT_OPTIONS = {
|
|
3
|
+
cookieName: 'ZIN_SESSION_ID',
|
|
4
|
+
headerName: 'x-session-id',
|
|
5
|
+
ttlMs: 7 * 24 * 60 * 60 * 1000,
|
|
6
|
+
};
|
|
7
|
+
function parseCookies(cookieHeader) {
|
|
8
|
+
const list = {};
|
|
9
|
+
if (cookieHeader.length === 0)
|
|
10
|
+
return list;
|
|
11
|
+
cookieHeader.split(';').forEach((cookie) => {
|
|
12
|
+
const parts = cookie.split('=');
|
|
13
|
+
const name = parts.shift()?.trim();
|
|
14
|
+
const value = parts.join('=');
|
|
15
|
+
if (name !== null && name !== undefined)
|
|
16
|
+
list[name] = decodeURIComponent(value);
|
|
17
|
+
});
|
|
18
|
+
return list;
|
|
19
|
+
}
|
|
20
|
+
function appendSetCookie(res, cookie) {
|
|
21
|
+
const existing = res.getHeader('Set-Cookie');
|
|
22
|
+
if (existing === undefined) {
|
|
23
|
+
res.setHeader('Set-Cookie', cookie);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (Array.isArray(existing)) {
|
|
27
|
+
const existingCookies = existing.map(String);
|
|
28
|
+
res.setHeader('Set-Cookie', [...existingCookies, cookie]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (typeof existing === 'string') {
|
|
32
|
+
res.setHeader('Set-Cookie', [existing, cookie]);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
res.setHeader('Set-Cookie', cookie);
|
|
36
|
+
}
|
|
37
|
+
function buildSessionCookie(cookieName, sessionId) {
|
|
38
|
+
// Keep this minimal; callers can override behavior later.
|
|
39
|
+
// HttpOnly prevents JS access; SameSite=Lax is a reasonable default for app sessions.
|
|
40
|
+
return `${cookieName}=${encodeURIComponent(sessionId)}; Path=/; HttpOnly; SameSite=Lax`;
|
|
41
|
+
}
|
|
42
|
+
function createSessionApi(sessions, sessionId, ttlMs) {
|
|
43
|
+
const withoutKey = (data, key) => {
|
|
44
|
+
if (!Object.prototype.hasOwnProperty.call(data, key))
|
|
45
|
+
return data;
|
|
46
|
+
const record = data;
|
|
47
|
+
const { [key]: _removed, ...rest } = record;
|
|
48
|
+
return rest;
|
|
49
|
+
};
|
|
50
|
+
const ensureStored = () => {
|
|
51
|
+
const existing = sessions.get(sessionId);
|
|
52
|
+
const now = Date.now();
|
|
53
|
+
if (existing !== undefined && existing.expiresAt > now) {
|
|
54
|
+
return existing;
|
|
55
|
+
}
|
|
56
|
+
const created = { data: {}, expiresAt: now + ttlMs };
|
|
57
|
+
sessions.set(sessionId, created);
|
|
58
|
+
return created;
|
|
59
|
+
};
|
|
60
|
+
return {
|
|
61
|
+
id: sessionId,
|
|
62
|
+
get(key) {
|
|
63
|
+
return ensureStored().data[key];
|
|
64
|
+
},
|
|
65
|
+
set(key, value) {
|
|
66
|
+
const stored = ensureStored();
|
|
67
|
+
stored.data[key] = value;
|
|
68
|
+
stored.expiresAt = Date.now() + ttlMs;
|
|
69
|
+
},
|
|
70
|
+
has(key) {
|
|
71
|
+
return Object.prototype.hasOwnProperty.call(ensureStored().data, key);
|
|
72
|
+
},
|
|
73
|
+
forget(key) {
|
|
74
|
+
const stored = ensureStored();
|
|
75
|
+
stored.data = withoutKey(stored.data, key);
|
|
76
|
+
stored.expiresAt = Date.now() + ttlMs;
|
|
77
|
+
},
|
|
78
|
+
all() {
|
|
79
|
+
return { ...ensureStored().data };
|
|
80
|
+
},
|
|
81
|
+
clear() {
|
|
82
|
+
const stored = ensureStored();
|
|
83
|
+
stored.data = {};
|
|
84
|
+
stored.expiresAt = Date.now() + ttlMs;
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export const SessionManager = Object.freeze({
|
|
89
|
+
create(options = {}) {
|
|
90
|
+
const config = { ...DEFAULT_OPTIONS, ...options };
|
|
91
|
+
const sessions = new Map();
|
|
92
|
+
return {
|
|
93
|
+
getIdFromCookieHeader(cookieHeader) {
|
|
94
|
+
if (cookieHeader === undefined || cookieHeader.length === 0)
|
|
95
|
+
return undefined;
|
|
96
|
+
const cookies = parseCookies(cookieHeader);
|
|
97
|
+
return cookies[config.cookieName];
|
|
98
|
+
},
|
|
99
|
+
getIdFromRequest(req) {
|
|
100
|
+
const cookieHeader = req.getHeader('cookie');
|
|
101
|
+
if (typeof cookieHeader === 'string') {
|
|
102
|
+
const fromCookie = this.getIdFromCookieHeader(cookieHeader);
|
|
103
|
+
if (fromCookie !== undefined)
|
|
104
|
+
return fromCookie;
|
|
105
|
+
}
|
|
106
|
+
const fromHeader = req.getHeader(config.headerName);
|
|
107
|
+
if (typeof fromHeader === 'string' && fromHeader.length > 0)
|
|
108
|
+
return fromHeader;
|
|
109
|
+
if (typeof req.sessionId === 'string' && req.sessionId.length > 0)
|
|
110
|
+
return req.sessionId;
|
|
111
|
+
const fromContext = req.context?.['sessionId'];
|
|
112
|
+
if (typeof fromContext === 'string' && fromContext.length > 0)
|
|
113
|
+
return fromContext;
|
|
114
|
+
return undefined;
|
|
115
|
+
},
|
|
116
|
+
async ensureSessionId(req, res) {
|
|
117
|
+
const existing = this.getIdFromRequest(req);
|
|
118
|
+
const sessionId = existing ??
|
|
119
|
+
(await Promise.resolve(generateSecureJobId('SessionManager: secure crypto API not available to generate a session id')));
|
|
120
|
+
req.context['sessionId'] = sessionId;
|
|
121
|
+
// If the cookie is missing, set it.
|
|
122
|
+
const cookieHeader = req.getHeader('cookie');
|
|
123
|
+
const fromCookie = typeof cookieHeader === 'string' ? this.getIdFromCookieHeader(cookieHeader) : undefined;
|
|
124
|
+
if (fromCookie === undefined) {
|
|
125
|
+
appendSetCookie(res, buildSessionCookie(config.cookieName, sessionId));
|
|
126
|
+
}
|
|
127
|
+
return sessionId;
|
|
128
|
+
},
|
|
129
|
+
get(sessionId) {
|
|
130
|
+
return createSessionApi(sessions, sessionId, config.ttlMs);
|
|
131
|
+
},
|
|
132
|
+
destroy(sessionId) {
|
|
133
|
+
sessions.delete(sessionId);
|
|
134
|
+
},
|
|
135
|
+
cleanup() {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
let removed = 0;
|
|
138
|
+
for (const [id, stored] of sessions.entries()) {
|
|
139
|
+
if (stored.expiresAt <= now) {
|
|
140
|
+
sessions.delete(id);
|
|
141
|
+
removed++;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return removed;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
export default SessionManager;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/session/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,WAAW,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SessionManager } from './SessionManager.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// TEMPLATE_START
|
|
2
2
|
|
|
3
|
-
import { generateSecureJobId
|
|
3
|
+
import { generateSecureJobId } from '@common/uuid';
|
|
4
|
+
import { Logger } from '@config/logger';
|
|
4
5
|
|
|
5
6
|
export interface QueueJob {
|
|
6
7
|
id: string;
|
|
@@ -18,8 +19,8 @@ export const Queue = Object.freeze({
|
|
|
18
19
|
/**
|
|
19
20
|
* Add a job to the queue
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
-
const id =
|
|
22
|
+
add<T>(data: T): string {
|
|
23
|
+
const id = generateSecureJobId();
|
|
23
24
|
const job: QueueJob = {
|
|
24
25
|
id,
|
|
25
26
|
data,
|
|
@@ -43,4 +44,4 @@ export const Queue = Object.freeze({
|
|
|
43
44
|
},
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
// TEMPLATE_END
|
|
47
|
+
// TEMPLATE_END
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FileLogWriter (Node.js only)
|
|
3
|
-
*
|
|
3
|
+
*
|
|
4
|
+
* Provides best-effort file logging with daily + size-based rotation.
|
|
5
|
+
* This module imports Node built-ins and should be loaded only in Node environments.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
import { ensureDirSafe } from '@zintrust/core';
|
|
9
|
+
import { Env } from './env';
|
|
7
10
|
import * as fs from 'node:fs';
|
|
8
11
|
import * as path from 'node:path';
|
|
9
12
|
|
|
10
|
-
import { Env } from './env';
|
|
11
|
-
|
|
12
13
|
const getCwdSafe = (): string => {
|
|
13
14
|
try {
|
|
14
15
|
if (typeof process === 'undefined' || typeof process.cwd !== 'function') return '';
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Driver selection must be dynamic (tests may mutate process.env).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Env } from '
|
|
8
|
+
import { Env } from './env';
|
|
9
9
|
import {
|
|
10
10
|
BroadcastConfigInput,
|
|
11
11
|
BroadcastDrivers,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
PusherBroadcastDriverConfig,
|
|
15
15
|
RedisBroadcastDriverConfig,
|
|
16
16
|
RedisHttpsBroadcastDriverConfig,
|
|
17
|
-
} from '
|
|
17
|
+
} from './type';
|
|
18
18
|
import { ErrorFactory } from '@zintrust/core';
|
|
19
19
|
|
|
20
20
|
const normalizeDriverName = (value: string): string => value.trim().toLowerCase();
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Sealed namespace for immutability
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Env } from '
|
|
8
|
-
import { CacheConfigInput, CacheDriverConfig } from '
|
|
7
|
+
import { Env } from './env';
|
|
8
|
+
import { CacheConfigInput, CacheDriverConfig } from './type';
|
|
9
9
|
import { ErrorFactory } from '@zintrust/core';
|
|
10
10
|
|
|
11
11
|
const getCacheDriver = (config: CacheConfigInput, name?: string): CacheDriverConfig => {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Sealed namespace for immutability
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Env } from '
|
|
8
|
-
import { DatabaseConfigShape, DatabaseConnectionConfig, DatabaseConnections } from '
|
|
7
|
+
import { Env } from './env';
|
|
8
|
+
import { DatabaseConfigShape, DatabaseConnectionConfig, DatabaseConnections } from './type';
|
|
9
9
|
import { ErrorFactory } from '@zintrust/core';
|
|
10
10
|
|
|
11
11
|
const hasOwn = (obj: Record<string, unknown>, key: string): boolean => {
|
|
@@ -64,6 +64,8 @@ export const Env = Object.freeze({
|
|
|
64
64
|
HOST: get('HOST', 'localhost'),
|
|
65
65
|
APP_NAME: get('APP_NAME', 'ZinTrust'),
|
|
66
66
|
APP_KEY: get('APP_KEY', ''),
|
|
67
|
+
// Optional key rotation support (comma-separated or JSON array of keys)
|
|
68
|
+
APP_PREVIOUS_KEYS: get('APP_PREVIOUS_KEYS', ''),
|
|
67
69
|
|
|
68
70
|
// Database
|
|
69
71
|
DB_CONNECTION: get('DB_CONNECTION', 'sqlite'),
|
|
@@ -124,6 +126,9 @@ export const Env = Object.freeze({
|
|
|
124
126
|
TOKEN_TTL: getInt('TOKEN_TTL', 3600000),
|
|
125
127
|
TOKEN_LENGTH: getInt('TOKEN_LENGTH', 32),
|
|
126
128
|
|
|
129
|
+
// Encryption interop
|
|
130
|
+
ENCRYPTION_CIPHER: get('ENCRYPTION_CIPHER', ''),
|
|
131
|
+
|
|
127
132
|
// Deployment
|
|
128
133
|
ENVIRONMENT: get('ENVIRONMENT', 'development'),
|
|
129
134
|
REQUEST_TIMEOUT: getInt('REQUEST_TIMEOUT', 30000),
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Sealed namespace for immutability
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Env } from '
|
|
8
|
-
import type { MailConfigInput, MailDriverConfig } from '
|
|
7
|
+
import { Env } from './env';
|
|
8
|
+
import type { MailConfigInput, MailDriverConfig } from './type';
|
|
9
9
|
import { ErrorFactory } from '@zintrust/core';
|
|
10
10
|
|
|
11
11
|
const isMailDriverConfig = (value: unknown): value is MailDriverConfig => {
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
CsrfMiddleware,
|
|
3
|
-
ErrorHandlerMiddleware,
|
|
4
|
-
LoggingMiddleware,
|
|
5
|
-
RateLimiter,
|
|
6
|
-
SecurityMiddleware,
|
|
7
|
-
type Middleware,
|
|
8
|
-
} from '@zintrust/core';
|
|
9
|
-
|
|
10
1
|
import { MiddlewareConfigType } from './type';
|
|
2
|
+
import { CsrfMiddleware } from '@zintrust/core';
|
|
3
|
+
import { ErrorHandlerMiddleware } from '@zintrust/core';
|
|
4
|
+
import { LoggingMiddleware } from '@zintrust/core';
|
|
5
|
+
import type { Middleware } from '@zintrust/core';
|
|
6
|
+
import { RateLimiter } from '@zintrust/core';
|
|
7
|
+
import { SecurityMiddleware } from '@zintrust/core';
|
|
11
8
|
|
|
12
9
|
const shared = Object.freeze({
|
|
13
10
|
log: LoggingMiddleware.create(),
|
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
* Driver selection must be dynamic (tests may mutate process.env).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Env } from '
|
|
8
|
+
import { Env } from './env';
|
|
9
9
|
import type {
|
|
10
10
|
KnownNotificationDriverConfig,
|
|
11
11
|
NotificationConfigInput,
|
|
12
12
|
NotificationDrivers,
|
|
13
13
|
NotificationProviders,
|
|
14
|
-
} from '
|
|
14
|
+
} from './type';
|
|
15
15
|
import { ErrorFactory } from '@zintrust/core';
|
|
16
16
|
|
|
17
17
|
const normalizeName = (value: string): string => value.trim().toLowerCase();
|
|
@@ -9,17 +9,16 @@
|
|
|
9
9
|
* Security keys can be configured per domain:
|
|
10
10
|
* - APP_KEY: Default encryption key for all operations (auto-generated)
|
|
11
11
|
* - API_KEY_SECRET: Optional API key authentication (if API_KEY_ENABLED=true)
|
|
12
|
-
* -
|
|
12
|
+
* - ENCRYPTION_CIPHER: Cipher for encrypted envelope interoperability
|
|
13
13
|
* - JWT_SECRET: JWT token signing key
|
|
14
14
|
*
|
|
15
15
|
* Developers can use a single APP_KEY or configure separate keys for different
|
|
16
16
|
* security domains (e.g., different keys for different microservices).
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
|
-
import { Logger } from '@zintrust/core';
|
|
20
|
-
|
|
21
19
|
import { appConfig } from './app';
|
|
22
20
|
import { Env } from './env';
|
|
21
|
+
import { Logger } from './logger';
|
|
23
22
|
import { ErrorFactory } from '@zintrust/core';
|
|
24
23
|
|
|
25
24
|
/**
|
|
@@ -77,6 +76,16 @@ const securityConfigObj = {
|
|
|
77
76
|
* Encryption
|
|
78
77
|
*/
|
|
79
78
|
encryption: {
|
|
79
|
+
// Required for framework-compatible encrypted payloads.
|
|
80
|
+
// Supported values: aes-256-cbc | aes-256-gcm (case-insensitive)
|
|
81
|
+
cipher: Env.get('ENCRYPTION_CIPHER', ''),
|
|
82
|
+
|
|
83
|
+
// Primary key used for encryption interoperability (framework-compatible envelopes).
|
|
84
|
+
// APP_KEY supports both `base64:...` and raw base64.
|
|
85
|
+
appKey: Env.get('APP_KEY', ''),
|
|
86
|
+
appPreviousKeys: Env.get('APP_PREVIOUS_KEYS', ''),
|
|
87
|
+
|
|
88
|
+
// Back-compat fields (not used by EncryptedEnvelope)
|
|
80
89
|
algorithm: Env.get('ENCRYPTION_ALGORITHM', 'aes-256-cbc'),
|
|
81
90
|
key: Env.get('ENCRYPTION_KEY', 'your-encryption-key'),
|
|
82
91
|
},
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Sealed namespace for immutability
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { Env } from '
|
|
8
|
-
import type { StorageConfigRuntime, StorageDriverConfig, StorageDrivers } from '
|
|
7
|
+
import { Env } from './env';
|
|
8
|
+
import type { StorageConfigRuntime, StorageDriverConfig, StorageDrivers } from './type';
|
|
9
9
|
import { ErrorFactory } from '@zintrust/core';
|
|
10
10
|
|
|
11
11
|
const hasOwn = <T extends object>(obj: T, key: PropertyKey): key is keyof T => {
|
|
@@ -3,6 +3,14 @@ type Broadcaster = Readonly<{
|
|
|
3
3
|
}>;
|
|
4
4
|
export declare const Broadcast: Readonly<{
|
|
5
5
|
send(channel: string, event: string, data: unknown): Promise<unknown>;
|
|
6
|
+
broadcastNow(channel: string, event: string, data: unknown): Promise<unknown>;
|
|
7
|
+
BroadcastLater(channel: string, event: string, data: unknown, options?: {
|
|
8
|
+
queueName?: string;
|
|
9
|
+
timestamp?: number;
|
|
10
|
+
}): Promise<string>;
|
|
11
|
+
queue(queueName: string): Readonly<{
|
|
12
|
+
BroadcastLater: (channel: string, event: string, data: unknown, options?: {}) => Promise<string>;
|
|
13
|
+
}>;
|
|
6
14
|
broadcaster(name?: string): Broadcaster;
|
|
7
15
|
}>;
|
|
8
16
|
export default Broadcast;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Broadcast.d.ts","sourceRoot":"","sources":["../../../../src/tools/broadcast/Broadcast.ts"],"names":[],"mappings":"AAQA,KAAK,WAAW,GAAG,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E,CAAC,CAAC;AAyDH,eAAO,MAAM,SAAS;kBACA,MAAM,SAAS,MAAM,QAAQ,OAAO;
|
|
1
|
+
{"version":3,"file":"Broadcast.d.ts","sourceRoot":"","sources":["../../../../src/tools/broadcast/Broadcast.ts"],"names":[],"mappings":"AAQA,KAAK,WAAW,GAAG,QAAQ,CAAC;IAC1B,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;CAC3E,CAAC,CAAC;AAyDH,eAAO,MAAM,SAAS;kBACA,MAAM,SAAS,MAAM,QAAQ,OAAO;0BAM5B,MAAM,SAAS,MAAM,QAAQ,OAAO;4BAMrD,MAAM,SACR,MAAM,QACP,OAAO,YACJ;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE;qBAepC,MAAM;kCAEa,MAAM,SAAS,MAAM,QAAQ,OAAO;;uBAKrD,MAAM,GAAG,WAAW;EAQvC,CAAC;AAEH,eAAe,SAAS,CAAC"}
|
|
@@ -49,6 +49,29 @@ export const Broadcast = Object.freeze({
|
|
|
49
49
|
const config = await resolveBroadcasterConfig();
|
|
50
50
|
return sendWithConfig(config, channel, event, data);
|
|
51
51
|
},
|
|
52
|
+
// Alias for send() - explicit intent for immediate broadcast
|
|
53
|
+
async broadcastNow(channel, event, data) {
|
|
54
|
+
return this.send(channel, event, data);
|
|
55
|
+
},
|
|
56
|
+
// Queue broadcast for async processing
|
|
57
|
+
async BroadcastLater(channel, event, data, options = {}) {
|
|
58
|
+
const { queueName = 'broadcasts', timestamp = Date.now() } = options;
|
|
59
|
+
const { Queue } = await import('../queue/Queue.js');
|
|
60
|
+
const messageId = await Queue.enqueue(queueName, {
|
|
61
|
+
type: 'broadcast',
|
|
62
|
+
channel,
|
|
63
|
+
event,
|
|
64
|
+
data,
|
|
65
|
+
timestamp,
|
|
66
|
+
attempts: 0,
|
|
67
|
+
});
|
|
68
|
+
return messageId;
|
|
69
|
+
},
|
|
70
|
+
queue(queueName) {
|
|
71
|
+
return Object.freeze({
|
|
72
|
+
BroadcastLater: async (channel, event, data, options = {}) => Broadcast.BroadcastLater(channel, event, data, { ...options, queueName }),
|
|
73
|
+
});
|
|
74
|
+
},
|
|
52
75
|
broadcaster(name) {
|
|
53
76
|
return Object.freeze({
|
|
54
77
|
send: async (channel, event, data) => {
|
|
@@ -5,6 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare const Notification: Readonly<{
|
|
7
7
|
send: (recipient: string, message: string, options?: Record<string, unknown>) => Promise<unknown>;
|
|
8
|
+
NotifyNow: (recipient: string, message: string, options?: Record<string, unknown>) => Promise<unknown>;
|
|
9
|
+
NotifyLater(recipient: string, message: string, notifyOptions?: Record<string, unknown>, queueOptions?: {
|
|
10
|
+
queueName?: string;
|
|
11
|
+
timestamp?: number;
|
|
12
|
+
}): Promise<string>;
|
|
13
|
+
queue(queueName: string): Readonly<{
|
|
14
|
+
NotifyLater: (recipient: string, message: string, notifyOptions?: Record<string, unknown>, queueOptions?: {
|
|
15
|
+
timestamp?: number;
|
|
16
|
+
}) => Promise<string>;
|
|
17
|
+
}>;
|
|
8
18
|
channel: (name: string) => Readonly<{
|
|
9
19
|
send: (recipient: string, message: string, options?: Record<string, unknown>) => Promise<unknown>;
|
|
10
20
|
}>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Notification.d.ts","sourceRoot":"","sources":["../../../../src/tools/notification/Notification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,YAAY;;
|
|
1
|
+
{"version":3,"file":"Notification.d.ts","sourceRoot":"","sources":["../../../../src/tools/notification/Notification.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAIH,eAAO,MAAM,YAAY;;;2BAQV,MAAM,WACR,MAAM,kBACA,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBACxB;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GACvD,OAAO,CAAC,MAAM,CAAC;qBAcD,MAAM;iCAGN,MAAM,WACR,MAAM,kBACA,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,iBACxB;YAAE,SAAS,CAAC,EAAE,MAAM,CAAA;SAAE;;oBAM1B,MAAM;0BAEM,MAAM,WAAW,MAAM,YAAY,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;;EAItF,CAAC;AAEH,eAAe,YAAY,CAAC"}
|
|
@@ -6,6 +6,27 @@
|
|
|
6
6
|
import { NotificationService } from './Service.js';
|
|
7
7
|
export const Notification = Object.freeze({
|
|
8
8
|
send: NotificationService.send,
|
|
9
|
+
// Alias for send() - explicit intent for immediate notification
|
|
10
|
+
NotifyNow: NotificationService.send,
|
|
11
|
+
// Queue notification for async processing
|
|
12
|
+
async NotifyLater(recipient, message, notifyOptions = {}, queueOptions = {}) {
|
|
13
|
+
const { queueName = 'notifications', timestamp = Date.now() } = queueOptions;
|
|
14
|
+
const { Queue } = await import('../queue/Queue.js');
|
|
15
|
+
const messageId = await Queue.enqueue(queueName, {
|
|
16
|
+
type: 'notification',
|
|
17
|
+
recipient,
|
|
18
|
+
message,
|
|
19
|
+
options: notifyOptions,
|
|
20
|
+
timestamp,
|
|
21
|
+
attempts: 0,
|
|
22
|
+
});
|
|
23
|
+
return messageId;
|
|
24
|
+
},
|
|
25
|
+
queue(queueName) {
|
|
26
|
+
return Object.freeze({
|
|
27
|
+
NotifyLater: async (recipient, message, notifyOptions = {}, queueOptions = {}) => Notification.NotifyLater(recipient, message, notifyOptions, { ...queueOptions, queueName }),
|
|
28
|
+
});
|
|
29
|
+
},
|
|
9
30
|
channel: (name) => Object.freeze({
|
|
10
31
|
send: async (recipient, message, options) => NotificationService.sendVia(name, recipient, message, options),
|
|
11
32
|
}),
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BroadcastWorker - Processes queued broadcasts
|
|
3
|
+
*
|
|
4
|
+
* This worker dequeues broadcast messages and sends them using the Broadcast service.
|
|
5
|
+
* Use with Queue.dequeue() in a background process or cron job.
|
|
6
|
+
*/
|
|
7
|
+
export declare const BroadcastWorker: Readonly<{
|
|
8
|
+
processOne: (queueName?: string, driverName?: string) => Promise<boolean>;
|
|
9
|
+
processAll: (queueName?: string, driverName?: string) => Promise<number>;
|
|
10
|
+
runOnce: (opts?: {
|
|
11
|
+
queueName?: string;
|
|
12
|
+
driverName?: string;
|
|
13
|
+
maxItems?: number;
|
|
14
|
+
}) => Promise<number>;
|
|
15
|
+
startWorker: (opts?: {
|
|
16
|
+
queueName?: string;
|
|
17
|
+
driverName?: string;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}) => Promise<number>;
|
|
20
|
+
}>;
|
|
21
|
+
export default BroadcastWorker;
|
|
22
|
+
//# sourceMappingURL=BroadcastWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BroadcastWorker.d.ts","sourceRoot":"","sources":["../../../src/workers/BroadcastWorker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,eAAO,MAAM,eAAe;0BAbrB,CAAC,oBAAoB,CAAC;0BAGf,CAAC,oBAAoB,CAAC;kBACnB,CAAC;iBAAiB,CAAC;kBAE3B,CAAC;gBACC,CAAA;;sBAGE,CAAC;iBAGP,CAAA;kBAAwB,CAAC;cAC3B,CAAC;;EAaJ,CAAC;AAEH,eAAe,eAAe,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BroadcastWorker - Processes queued broadcasts
|
|
3
|
+
*
|
|
4
|
+
* This worker dequeues broadcast messages and sends them using the Broadcast service.
|
|
5
|
+
* Use with Queue.dequeue() in a background process or cron job.
|
|
6
|
+
*/
|
|
7
|
+
import { createQueueWorker } from '../workers/createQueueWorker.js';
|
|
8
|
+
import { Broadcast } from '../tools/broadcast/Broadcast.js';
|
|
9
|
+
export const BroadcastWorker = Object.freeze({
|
|
10
|
+
...createQueueWorker({
|
|
11
|
+
kindLabel: 'broadcast',
|
|
12
|
+
defaultQueueName: 'broadcasts',
|
|
13
|
+
maxAttempts: 3,
|
|
14
|
+
getLogFields: (payload) => ({
|
|
15
|
+
channel: payload.channel,
|
|
16
|
+
event: payload.event,
|
|
17
|
+
queuedAt: payload.timestamp,
|
|
18
|
+
}),
|
|
19
|
+
handle: async (payload) => {
|
|
20
|
+
await Broadcast.send(payload.channel, payload.event, payload.data);
|
|
21
|
+
},
|
|
22
|
+
}),
|
|
23
|
+
});
|
|
24
|
+
export default BroadcastWorker;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationWorker - Processes queued notifications
|
|
3
|
+
*
|
|
4
|
+
* This worker dequeues notification messages and sends them using the Notification service.
|
|
5
|
+
* Use with Queue.dequeue() in a background process or cron job.
|
|
6
|
+
*/
|
|
7
|
+
export declare const NotificationWorker: Readonly<{
|
|
8
|
+
processOne: (queueName?: string, driverName?: string) => Promise<boolean>;
|
|
9
|
+
processAll: (queueName?: string, driverName?: string) => Promise<number>;
|
|
10
|
+
runOnce: (opts?: {
|
|
11
|
+
queueName?: string;
|
|
12
|
+
driverName?: string;
|
|
13
|
+
maxItems?: number;
|
|
14
|
+
}) => Promise<number>;
|
|
15
|
+
startWorker: (opts?: {
|
|
16
|
+
queueName?: string;
|
|
17
|
+
driverName?: string;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}) => Promise<number>;
|
|
20
|
+
}>;
|
|
21
|
+
export default NotificationWorker;
|
|
22
|
+
//# sourceMappingURL=NotificationWorker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NotificationWorker.d.ts","sourceRoot":"","sources":["../../../src/workers/NotificationWorker.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,eAAO,MAAM,kBAAkB;0BAdyD,CAAC,oBACzE,CAAC;0BAGf,CAAC,oBAAoB,CAAC;kBACnB,CAAC;iBAAkB,CAAA;kBAAwB,CAAC;gBAEtC,CAAC;;sBAGV,CAAC;iBAAiB,CAAC;kBACZ,CAAC;cAGL,CAAC;;EAaJ,CAAC;AAEH,eAAe,kBAAkB,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NotificationWorker - Processes queued notifications
|
|
3
|
+
*
|
|
4
|
+
* This worker dequeues notification messages and sends them using the Notification service.
|
|
5
|
+
* Use with Queue.dequeue() in a background process or cron job.
|
|
6
|
+
*/
|
|
7
|
+
import { createQueueWorker } from '../workers/createQueueWorker.js';
|
|
8
|
+
import { Notification } from '../tools/notification/Notification.js';
|
|
9
|
+
export const NotificationWorker = Object.freeze({
|
|
10
|
+
...createQueueWorker({
|
|
11
|
+
kindLabel: 'notification',
|
|
12
|
+
defaultQueueName: 'notifications',
|
|
13
|
+
maxAttempts: 3,
|
|
14
|
+
getLogFields: (payload) => ({
|
|
15
|
+
recipient: payload.recipient,
|
|
16
|
+
queuedAt: payload.timestamp,
|
|
17
|
+
}),
|
|
18
|
+
handle: async (payload) => {
|
|
19
|
+
await Notification.send(payload.recipient, payload.message, payload.options);
|
|
20
|
+
},
|
|
21
|
+
}),
|
|
22
|
+
});
|
|
23
|
+
export default NotificationWorker;
|