feat(admin): ingest аудита из apps и users 1:1 с Sheet журнала
Build and Push Auth Portal Docker Image / build-and-push (push) Successful in 1m46s
Build and Push Auth Portal Docker Image / create-release (push) Skipped

Добавлен POST /api/v1/ingest/audit, фильтры source_app/user_id, last_login_at; таблица пользователей по solution-users-1 с журналом в Sheet.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-21 13:24:28 +07:00
co-authored by Cursor
parent 26fc8253c3
commit 57e34ff5e9
27 changed files with 1983 additions and 639 deletions
+44 -1
View File
@@ -4,16 +4,27 @@ export const AUDIT_SEVERITIES = ['info', 'warning', 'critical'] as const
export type AuditSeverity = (typeof AUDIT_SEVERITIES)[number]
export const auditSeveritySchema = z.enum(AUDIT_SEVERITIES)
export const AUDIT_SOURCE_APPS = [
'portal',
'vps',
'cfdm',
'bgp',
'fw',
] as const
export type AuditSourceApp = (typeof AUDIT_SOURCE_APPS)[number]
export const auditSourceAppSchema = z.enum(AUDIT_SOURCE_APPS)
export const AUDIT_TARGET_TYPES = [
'user',
'settings',
'session',
'system',
'app_resource',
] as const
export type AuditTargetType = (typeof AUDIT_TARGET_TYPES)[number]
export const auditTargetTypeSchema = z.enum(AUDIT_TARGET_TYPES)
/** Well-known action keys written by the API. */
/** Well-known portal-native action keys. */
export const AUDIT_ACTIONS = [
'auth.login',
'auth.login_failed',
@@ -31,6 +42,8 @@ export const auditActionSchema = z.enum(AUDIT_ACTIONS)
export const auditLogEntrySchema = z.object({
id: z.string(),
event_id: z.string().nullable(),
source_app: auditSourceAppSchema,
action: z.string(),
severity: auditSeveritySchema,
actor_user_id: z.string().nullable(),
@@ -48,6 +61,8 @@ export type AuditLogEntry = z.infer<typeof auditLogEntrySchema>
export const auditListQuerySchema = z.object({
action: z.string().optional(),
severity: auditSeveritySchema.optional(),
user_id: z.string().optional(),
source_app: auditSourceAppSchema.optional(),
limit: z.coerce.number().int().min(1).max(500).default(200),
})
export type AuditListQuery = z.infer<typeof auditListQuerySchema>
@@ -73,3 +88,31 @@ export const auditPurgeResponseSchema = z.object({
retention_days: z.number().int(),
})
export type AuditPurgeResponse = z.infer<typeof auditPurgeResponseSchema>
export const ingestAuditEventSchema = z.object({
event_id: z.string().min(1).max(128),
source_app: z.enum(['vps', 'cfdm', 'bgp', 'fw']),
action: z.string().min(1).max(200),
severity: auditSeveritySchema.optional(),
actor_user_id: z.string().nullable().optional(),
actor_email: z.string().email().nullable().optional(),
actor_name: z.string().nullable().optional(),
target_type: auditTargetTypeSchema.nullable().optional(),
target_id: z.string().nullable().optional(),
summary: z.string().min(1).max(500),
details: z.record(z.string(), z.unknown()).nullable().optional(),
ip: z.string().nullable().optional(),
created_at: z.string().optional(),
})
export type IngestAuditEvent = z.infer<typeof ingestAuditEventSchema>
export const ingestAuditRequestSchema = z.object({
events: z.array(ingestAuditEventSchema).min(1).max(50),
})
export type IngestAuditRequest = z.infer<typeof ingestAuditRequestSchema>
export const ingestAuditResponseSchema = z.object({
accepted: z.number().int().nonnegative(),
duplicates: z.number().int().nonnegative(),
})
export type IngestAuditResponse = z.infer<typeof ingestAuditResponseSchema>
+1
View File
@@ -370,6 +370,7 @@ export const adminUserSchema = z.object({
disabled: z.boolean(),
apps: z.array(appIdSchema),
permissions: z.array(z.string()),
last_login_at: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
})