forked from wangziqi/gongxue-base
feat: add platform audit alert notifications
This commit is contained in:
@@ -1341,6 +1341,105 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
});
|
||||
assert.equal(invalidAuditAlertStatus.code, 'INVALID_ALERT_STATUS', 'audit alerts should not be reopened through status endpoint');
|
||||
|
||||
const notificationChannel = await request('/api/platform-admin/audit-notification-channels', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
channelCode: 'integration_platform_audit',
|
||||
name: '集成测试平台审计通知',
|
||||
provider: 'generic',
|
||||
webhookUrl: 'https://ops.example.test/platform-audit',
|
||||
secret: 'integration-platform-audit-notification-secret',
|
||||
minSeverity: 'medium',
|
||||
statusFilter: ['open'],
|
||||
actionPatterns: ['platform.tenant.*'],
|
||||
tenantIds: [tenantId],
|
||||
timeoutSec: 5,
|
||||
metadata: { owner: 'security' },
|
||||
},
|
||||
});
|
||||
assert.equal(notificationChannel.item?.channelCode, 'integration_platform_audit', 'platform admin should upsert audit notification channel');
|
||||
assert.equal(notificationChannel.item?.secretRef, 'app_private.platform_secrets:webhook:integration_platform_audit', 'channel should expose only platform secretRef');
|
||||
assert.equal(notificationChannel.item?.webhook?.host, 'ops.example.test', 'channel response should expose safe webhook host');
|
||||
assert.equal(notificationChannel.item?.webhookUrl, undefined, 'channel response must not expose raw webhook URL');
|
||||
assert.ok(!JSON.stringify(notificationChannel).includes('integration-platform-audit-notification-secret'), 'channel response must not leak webhook secret');
|
||||
|
||||
const notificationChannels = await request('/api/platform-admin/audit-notification-channels', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { enabled: true, provider: 'generic', limit: 20 },
|
||||
});
|
||||
assert.ok(
|
||||
notificationChannels.items?.some(item => item.channelCode === 'integration_platform_audit'),
|
||||
'platform admin should list audit notification channels',
|
||||
);
|
||||
assert.ok(!JSON.stringify(notificationChannels).includes('integration-platform-audit-notification-secret'), 'channel list must not leak webhook secret');
|
||||
|
||||
const invalidNotificationChannel = await request('/api/platform-admin/audit-notification-channels', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
method: 'PUT',
|
||||
body: {
|
||||
channelCode: 'bad_channel',
|
||||
name: 'bad channel',
|
||||
provider: 'generic',
|
||||
webhookUrl: 'ftp://ops.example.test/hook',
|
||||
},
|
||||
expectStatus: 400,
|
||||
});
|
||||
assert.equal(invalidNotificationChannel.code, 'INVALID_WEBHOOK_URL', 'audit notification channel should reject unsafe webhook URL');
|
||||
|
||||
const eventPool = new pg.Pool({ connectionString: process.env.DATABASE_URL || DEFAULT_DATABASE_URL });
|
||||
let platformNotificationEventId = '';
|
||||
try {
|
||||
const channelRow = await eventPool.query(
|
||||
"select id from public.platform_audit_notification_channels where channel_code = 'integration_platform_audit' limit 1",
|
||||
);
|
||||
const insertedEvent = await eventPool.query(
|
||||
`
|
||||
insert into public.platform_audit_notification_events (
|
||||
channel_id, alert_id, audit_log_id, provider, status, attempts,
|
||||
last_http_code, request_payload, last_response_summary
|
||||
)
|
||||
values (
|
||||
$1, $2, $3, 'generic', 'sent', 1,
|
||||
200,
|
||||
'{"body":{"token":"must-not-leak","nested":{"password":"must-not-leak"}}}'::jsonb,
|
||||
'{"ok":true}'
|
||||
)
|
||||
on conflict (channel_id, alert_id)
|
||||
do update set status = excluded.status,
|
||||
attempts = excluded.attempts,
|
||||
request_payload = excluded.request_payload,
|
||||
updated_at = now()
|
||||
returning id
|
||||
`,
|
||||
[channelRow.rows[0].id, platformAlertId, statusAuditLog.id],
|
||||
);
|
||||
platformNotificationEventId = insertedEvent.rows[0].id;
|
||||
} finally {
|
||||
await eventPool.end();
|
||||
}
|
||||
|
||||
const notificationEvents = await request('/api/platform-admin/audit-notification-events', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
headers: adminHeaders,
|
||||
query: { alertId: platformAlertId, status: 'sent', limit: 20 },
|
||||
});
|
||||
assert.ok(
|
||||
notificationEvents.items?.some(item => item.id === platformNotificationEventId),
|
||||
'platform admin should list audit notification events',
|
||||
);
|
||||
const listedNotificationEvent = notificationEvents.items?.find(item => item.id === platformNotificationEventId);
|
||||
assert.equal(listedNotificationEvent?.requestPayload?.body?.token, '[REDACTED]', 'notification event list should redact token-like payload details');
|
||||
assert.equal(listedNotificationEvent?.requestPayload?.body?.nested?.password, '[REDACTED]', 'notification event list should redact nested password-like payload details');
|
||||
assert.ok(!JSON.stringify(notificationEvents).includes('must-not-leak'), 'notification event list must not leak sensitive details');
|
||||
|
||||
const candidates = await request('/api/platform-admin/invoices/subscription-candidates', {
|
||||
tenantId: false,
|
||||
userId: false,
|
||||
@@ -1555,6 +1654,20 @@ async function testPlatformTenantOperationsAndAudit() {
|
||||
});
|
||||
assert.equal(studentAuditAlertStatusDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not update platform audit alerts');
|
||||
|
||||
const studentAuditNotificationChannelDenied = await request('/api/platform-admin/audit-notification-channels', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(studentAuditNotificationChannelDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not read platform audit notification channels');
|
||||
|
||||
const studentAuditNotificationEventDenied = await request('/api/platform-admin/audit-notification-events', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
expectStatus: 403,
|
||||
});
|
||||
assert.equal(studentAuditNotificationEventDenied.code, 'PLATFORM_ADMIN_REQUIRED', 'student must not read platform audit notification events');
|
||||
|
||||
const studentReminderDenied = await request('/api/platform-admin/invoices/reminders', {
|
||||
tenantId: false,
|
||||
userId: USER_ID,
|
||||
|
||||
Reference in New Issue
Block a user