@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunsoft/yuncms-api",
3
- "version": "0.1.24",
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.24",
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 [rows] = await this.database.query('SELECT * FROM yuncms_ai_automations WHERE collection = ? AND enabled = 1', [context.collection]);
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(this.database, async (connection) => {
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(
@@ -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 { INTERNAL_AUDIT_EVENTS } from './audit-events.js';
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 redisClient?.close().catch(() => {});
237
- await closeDatabasePool(pool).catch(() => {});
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
  });