package lkgateway // lkaudit.go — журнал входов сотрудника в кабинет клиента. // // Сотрудник может открыть кабинет клиента, чтобы помочь ему или подготовить поручение. // Это доступ к персональным данным, поэтому каждый такой вход записывается: кто, к кому, // когда и с какого адреса. Запись нужна и для внутреннего контроля, и чтобы ответить на // вопрос клиента «кто смотрел мои данные». import ( "context" "fmt" "log" "time" ) // lkAuditSchema — журнал доступа (создаётся вместе со схемой lk). const lkAuditSchema = ` CREATE TABLE IF NOT EXISTS lk.audit ( id bigserial PRIMARY KEY, at timestamptz NOT NULL DEFAULT now(), event text NOT NULL, actor text NOT NULL, target_login text, target_contract text, ip text ); CREATE INDEX IF NOT EXISTS ix_lk_audit_at ON lk.audit (at DESC); CREATE INDEX IF NOT EXISTS ix_lk_audit_target ON lk.audit (target_contract, at DESC); ` // lkAuditImpersonate — записать вход сотрудника в кабинет клиента. func lkAuditImpersonate(ctx context.Context, rc *RuntimeConfig, actor, targetLogin, contract, ip string) { go func() { bg, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() db, err := lkDB(rc) if err != nil { log.Printf("кабинет: журнал доступа недоступен: %v", err) return } if _, err := db.ExecContext(bg, `INSERT INTO lk.audit (event, actor, target_login, target_contract, ip) VALUES ('impersonate', $1, $2, $3, $4)`, actor, targetLogin, contract, ip); err != nil { log.Printf("кабинет: не записан вход в кабинет клиента: %v", err) } }() raiseSystemAlert(rc, "Кабинет: вход сотрудника в кабинет клиента", fmt.Sprintf("Сотрудник %s открыл кабинет клиента %s (договор %s) с адреса %s. Доступ только на просмотр, запись в журнале lk.audit.", actor, targetLogin, contract, ip)) } // lkAuditRecord — строка журнала для страницы настроек. type lkAuditRecord struct { At time.Time `json:"at"` Event string `json:"event"` Actor string `json:"actor"` Target string `json:"target"` Contract string `json:"contract"` IP string `json:"ip"` } // lkAuditList — последние записи журнала доступа. func lkAuditList(ctx context.Context, rc *RuntimeConfig, limit int) ([]lkAuditRecord, error) { if limit <= 0 || limit > 500 { limit = 100 } db, err := lkDB(rc) if err != nil { return nil, err } rows, err := db.QueryContext(ctx, `SELECT at, event, actor, COALESCE(target_login,''), COALESCE(target_contract,''), COALESCE(ip,'') FROM lk.audit ORDER BY at DESC LIMIT $1`, limit) if err != nil { return nil, err } defer rows.Close() var out []lkAuditRecord for rows.Next() { var a lkAuditRecord if rows.Scan(&a.At, &a.Event, &a.Actor, &a.Target, &a.Contract, &a.IP) == nil { out = append(out, a) } } return out, rows.Err() }