feat(admin): журналы входов и изменений с IP/UA и сессиями
Build and Push Auth Portal Docker Image / build-and-push (push) Successful in 1m57s
Build and Push Auth Portal Docker Image / create-release (push) Skipped

Разделены экраны «Входы» и «Изменения»; логин пишет IP/UA и last_login_ip;
SSO handoff и revoke refresh-сессий; улучшены audit-карточки.

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-07-31 03:12:39 +07:00
co-authored by Cursor
parent 8b1203f5e9
commit cc38bf06f8
24 changed files with 1431 additions and 69 deletions
+14
View File
@@ -29,6 +29,7 @@ export const AUDIT_ACTIONS = [
'auth.login',
'auth.login_failed',
'auth.logout',
'auth.sso_handoff',
'user.create',
'user.update',
'user.delete',
@@ -40,6 +41,17 @@ export const AUDIT_ACTIONS = [
export type AuditAction = (typeof AUDIT_ACTIONS)[number]
export const auditActionSchema = z.enum(AUDIT_ACTIONS)
export const AUDIT_KINDS = ['logins', 'changes'] as const
export type AuditKind = (typeof AUDIT_KINDS)[number]
export const auditKindSchema = z.enum(AUDIT_KINDS)
export const AUTH_AUDIT_ACTIONS = [
'auth.login',
'auth.login_failed',
'auth.logout',
'auth.sso_handoff',
] as const
export const auditLogEntrySchema = z.object({
id: z.string(),
event_id: z.string().nullable(),
@@ -62,7 +74,9 @@ export const auditListQuerySchema = z.object({
action: z.string().optional(),
severity: auditSeveritySchema.optional(),
user_id: z.string().optional(),
actor_email: z.string().email().optional(),
source_app: auditSourceAppSchema.optional(),
kind: auditKindSchema.optional(),
limit: z.coerce.number().int().min(1).max(500).default(200),
})
export type AuditListQuery = z.infer<typeof auditListQuerySchema>
+18
View File
@@ -371,11 +371,29 @@ export const adminUserSchema = z.object({
apps: z.array(appIdSchema),
permissions: z.array(z.string()),
last_login_at: z.string().nullable(),
last_login_ip: z.string().nullable(),
created_at: z.string(),
updated_at: z.string(),
})
export type AdminUser = z.infer<typeof adminUserSchema>
export const adminSessionSchema = z.object({
id: z.string(),
user_id: z.string(),
email: z.string().email(),
name: z.string(),
ip: z.string().nullable(),
user_agent: z.string().nullable(),
created_at: z.string(),
expires_at: z.string(),
})
export type AdminSession = z.infer<typeof adminSessionSchema>
export const ssoAccessRequestSchema = z.object({
return_to: z.string().url(),
})
export type SsoAccessRequest = z.infer<typeof ssoAccessRequestSchema>
export const createUserRequestSchema = z.object({
email: z.string().email(),
name: z.string().min(1),