@yunsoft/yuncms-api 0.1.24 → 0.1.26
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/package.json +3 -2
- package/src/audit-hooks.js +27 -0
- package/src/automations/service.js +3 -2
- package/src/error-response.js +16 -0
- package/src/server.js +10 -27
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunsoft/yuncms-api",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"description": "Express API runtime and bundled Studio server for YunCMS.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -35,10 +35,11 @@
|
|
|
35
35
|
"@modelcontextprotocol/node": "2.0.0",
|
|
36
36
|
"@modelcontextprotocol/server": "2.0.0",
|
|
37
37
|
"@node-saml/node-saml": "5.1.0",
|
|
38
|
-
"@yunsoft/yuncms-core": "0.1.
|
|
38
|
+
"@yunsoft/yuncms-core": "0.1.26",
|
|
39
39
|
"express": "5.2.1",
|
|
40
40
|
"ldapts": "9.0.0",
|
|
41
41
|
"openid-client": "6.8.7",
|
|
42
|
+
"proxy-addr": "2.0.8",
|
|
42
43
|
"zod": "4.4.3"
|
|
43
44
|
},
|
|
44
45
|
"funding": {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { createSystemAccountability } from '@yunsoft/yuncms-core';
|
|
2
|
+
import { INTERNAL_AUDIT_EVENTS } from './audit-events.js';
|
|
3
|
+
|
|
4
|
+
export function registerInternalAudit({ emitter, services, database, logger }) {
|
|
5
|
+
const AuditService = services.AuditService;
|
|
6
|
+
const systemAccountability = createSystemAccountability();
|
|
7
|
+
for (const event of INTERNAL_AUDIT_EVENTS) {
|
|
8
|
+
emitter.registerAction(event, async (payload, context) => {
|
|
9
|
+
try {
|
|
10
|
+
const audit = new AuditService({ accountability: systemAccountability,
|
|
11
|
+
database: context.database ?? database, logger, requestId: context.requestId ?? null });
|
|
12
|
+
await audit.record({
|
|
13
|
+
user: context.accountability?.user ?? null,
|
|
14
|
+
action: event,
|
|
15
|
+
collection: context.collection ?? null,
|
|
16
|
+
itemKey: payload?.key ?? null,
|
|
17
|
+
requestId: context.requestId ?? null,
|
|
18
|
+
payload,
|
|
19
|
+
});
|
|
20
|
+
} catch (error) {
|
|
21
|
+
logger.error('YunCMS audit write failed after committed mutation', {
|
|
22
|
+
event, requestId: context.requestId ?? null, code: error?.code, error,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
}, { extensionId: 'core.audit', priority: 1000 });
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -146,7 +146,8 @@ export class AiAutomationsService {
|
|
|
146
146
|
async enqueue(event, payload, context) {
|
|
147
147
|
if (!context.collection || context.collection.startsWith('yuncms_')
|
|
148
148
|
|| context[AI_AUTOMATION_ORIGIN] === true) return;
|
|
149
|
-
const
|
|
149
|
+
const database = context.database ?? this.database;
|
|
150
|
+
const [rows] = await database.query('SELECT * FROM yuncms_ai_automations WHERE collection = ? AND enabled = 1', [context.collection]);
|
|
150
151
|
for (const row of rows) {
|
|
151
152
|
const rule = automationFromRow(row);
|
|
152
153
|
if (!matchesAutomation(rule, event, payload, context)) continue;
|
|
@@ -154,7 +155,7 @@ export class AiAutomationsService {
|
|
|
154
155
|
if (payload.key == null) continue;
|
|
155
156
|
const key = String(payload.key);
|
|
156
157
|
if (!key || key.length > 191) continue;
|
|
157
|
-
await withTransaction(
|
|
158
|
+
await withTransaction(database, async (connection) => {
|
|
158
159
|
const current = await this.read(rule.id, connection, { lock: true });
|
|
159
160
|
if (!current || current.revision !== rule.revision || !current.enabled) return;
|
|
160
161
|
const [pending] = await connection.query(
|
package/src/error-response.js
CHANGED
|
@@ -39,6 +39,22 @@ const STATUS_BY_CODE = new Map([
|
|
|
39
39
|
['INVALID_PAYLOAD', 400],
|
|
40
40
|
['INVALID_PASSWORD', 400],
|
|
41
41
|
['INVALID_TOKEN', 400],
|
|
42
|
+
['INVALID_AUTH_TRANSACTION', 400],
|
|
43
|
+
['INVALID_AUTH_PROVIDER', 400],
|
|
44
|
+
['INVALID_EXTERNAL_IDENTITY', 400],
|
|
45
|
+
['INVALID_REDIRECT_TARGET', 400],
|
|
46
|
+
['AUTH_PROVIDER_FLOW_MISMATCH', 400],
|
|
47
|
+
['AUTH_PROVIDER_NOT_FOUND', 404],
|
|
48
|
+
['VERIFIED_EXTERNAL_EMAIL_REQUIRED', 403],
|
|
49
|
+
['EXTERNAL_USER_INACTIVE', 403],
|
|
50
|
+
['EXTERNAL_ADMIN_LINK_FORBIDDEN', 403],
|
|
51
|
+
['EXTERNAL_IDENTITY_NOT_LINKED', 403],
|
|
52
|
+
['EXTERNAL_EMAIL_CONFLICT', 409],
|
|
53
|
+
['INVALID_AUTH_PROVIDER_CONFIG', 503],
|
|
54
|
+
['EXTERNAL_JIT_ROLE_REQUIRED', 503],
|
|
55
|
+
['INVALID_EXTERNAL_JIT_ROLE', 503],
|
|
56
|
+
['EXTERNAL_IDENTITY_INVALID', 502],
|
|
57
|
+
['EXTERNAL_USERINFO_FAILED', 502],
|
|
42
58
|
['INVALID_ROLE', 400],
|
|
43
59
|
['INVALID_PERMISSION', 400],
|
|
44
60
|
['INVALID_SCHEMA_PAYLOAD', 400],
|
package/src/server.js
CHANGED
|
@@ -23,7 +23,7 @@ import { createApp } from './app.js';
|
|
|
23
23
|
import { AiAssistantService } from './ai/service.js';
|
|
24
24
|
import { loadOrCreateAiSettingsKey } from './ai/secret-key.js';
|
|
25
25
|
import { AiSettingsStore } from './ai/settings-store.js';
|
|
26
|
-
import {
|
|
26
|
+
import { registerInternalAudit } from './audit-hooks.js';
|
|
27
27
|
import { loadExternalAuthConfig } from './external-auth/config.js';
|
|
28
28
|
import { ExternalAuthProviderRegistry } from './external-auth/providers.js';
|
|
29
29
|
import { loadExtensionRuntime } from './extensions/runtime.js';
|
|
@@ -88,28 +88,6 @@ let extensionRuntime = null;
|
|
|
88
88
|
let automationWorker = null;
|
|
89
89
|
let shuttingDown = false;
|
|
90
90
|
|
|
91
|
-
function registerInternalAudit({ emitter, services }) {
|
|
92
|
-
const AuditService = services.AuditService;
|
|
93
|
-
const systemAccountability = createSystemAccountability();
|
|
94
|
-
for (const event of INTERNAL_AUDIT_EVENTS) {
|
|
95
|
-
emitter.registerAction(event, async (payload, context) => {
|
|
96
|
-
try {
|
|
97
|
-
const audit = new AuditService({ accountability: systemAccountability, database: pool, logger, requestId: context.requestId ?? null });
|
|
98
|
-
await audit.record({
|
|
99
|
-
user: context.accountability?.user ?? null,
|
|
100
|
-
action: event,
|
|
101
|
-
collection: context.collection ?? null,
|
|
102
|
-
itemKey: payload?.key ?? null,
|
|
103
|
-
requestId: context.requestId ?? null,
|
|
104
|
-
payload,
|
|
105
|
-
});
|
|
106
|
-
} catch (error) {
|
|
107
|
-
logger.error('YunCMS audit write failed after committed mutation', { event, requestId: context.requestId ?? null, code: error?.code, error });
|
|
108
|
-
}
|
|
109
|
-
}, { extensionId: 'core.audit', priority: 1000 });
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
91
|
async function start() {
|
|
114
92
|
await assertDatabaseCompatible(pool);
|
|
115
93
|
const aiKey = await loadOrCreateAiSettingsKey();
|
|
@@ -141,7 +119,7 @@ async function start() {
|
|
|
141
119
|
const aiRouter = createAiRouter({ assistant: aiAssistant, settingsStore: aiSettingsStore });
|
|
142
120
|
const mcpSettingsStore = new McpSettingsStore({ database: pool });
|
|
143
121
|
mailer?.setEmitter(emitter);
|
|
144
|
-
registerInternalAudit({ emitter, services });
|
|
122
|
+
registerInternalAudit({ emitter, services, database: pool, logger });
|
|
145
123
|
const automationOptions = { assistant: aiAssistant, settingsStore: aiSettingsStore, schemaCache };
|
|
146
124
|
const automations = new AiAutomationsService({ ...automationOptions, database: pool,
|
|
147
125
|
accountability: createSystemAccountability(), services, emitter, storage, logger });
|
|
@@ -183,7 +161,11 @@ async function start() {
|
|
|
183
161
|
|
|
184
162
|
await extensionRuntime.init('app.beforeStart');
|
|
185
163
|
server = await new Promise((resolve, reject) => {
|
|
186
|
-
const listeningServer = app.listen(config.server.port, config.server.host, () => {
|
|
164
|
+
const listeningServer = app.listen(config.server.port, config.server.host, (error) => {
|
|
165
|
+
if (error) {
|
|
166
|
+
reject(error);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
187
169
|
logger.info('YunCMS API listening', { host: config.server.host, port: config.server.port });
|
|
188
170
|
resolve(listeningServer);
|
|
189
171
|
});
|
|
@@ -233,7 +215,8 @@ for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
|
233
215
|
|
|
234
216
|
start().catch(async (error) => {
|
|
235
217
|
logger.error('YunCMS API failed to start', { code: error?.code, error });
|
|
236
|
-
await
|
|
237
|
-
|
|
218
|
+
await shutdown('startup-failure').catch((cleanupError) => {
|
|
219
|
+
logger.error('YunCMS API startup cleanup failed', { code: cleanupError?.code, error: cleanupError });
|
|
220
|
+
});
|
|
238
221
|
process.exit(1);
|
|
239
222
|
});
|