feat(secrets-mcp): 审计页、audit_log user_id、OAuth 登录与仪表盘 footer
All checks were successful
Secrets MCP — Build & Release / 版本 & Release (push) Successful in 3s
Secrets MCP — Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 7m20s
Secrets MCP — Build & Release / Build Linux (musl) (push) Successful in 8m23s
Secrets MCP — Build & Release / 发布草稿 Release (push) Successful in 1s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s
All checks were successful
Secrets MCP — Build & Release / 版本 & Release (push) Successful in 3s
Secrets MCP — Build & Release / 质量检查 (fmt / clippy / test) (push) Successful in 7m20s
Secrets MCP — Build & Release / Build Linux (musl) (push) Successful in 8m23s
Secrets MCP — Build & Release / 发布草稿 Release (push) Successful in 1s
Secrets MCP — Build & Release / 部署 secrets-mcp (push) Successful in 6s
- audit_log 增加 user_id;业务写审计透传 user_id - Web /audit 与侧边栏;Dashboard 版本 footer 贴底(margin-top: auto) - 停止 API Key 鉴权成功写入登录审计 - 文档、CI、release-check 配套更新 Made-with: Cursor
This commit is contained in:
@@ -36,9 +36,10 @@ pub async fn log_login(
|
||||
let actor = current_actor();
|
||||
let detail = login_detail(user_id, provider, client_ip, user_agent);
|
||||
let result: Result<_, sqlx::Error> = sqlx::query(
|
||||
"INSERT INTO audit_log (action, namespace, kind, name, detail, actor) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"INSERT INTO audit_log (user_id, action, namespace, kind, name, detail, actor) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(ACTION_LOGIN)
|
||||
.bind(NAMESPACE_AUTH)
|
||||
.bind(kind)
|
||||
@@ -58,6 +59,7 @@ pub async fn log_login(
|
||||
/// Write an audit entry within an existing transaction.
|
||||
pub async fn log_tx(
|
||||
tx: &mut Transaction<'_, Postgres>,
|
||||
user_id: Option<Uuid>,
|
||||
action: &str,
|
||||
namespace: &str,
|
||||
kind: &str,
|
||||
@@ -66,9 +68,10 @@ pub async fn log_tx(
|
||||
) {
|
||||
let actor = current_actor();
|
||||
let result: Result<_, sqlx::Error> = sqlx::query(
|
||||
"INSERT INTO audit_log (action, namespace, kind, name, detail, actor) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
"INSERT INTO audit_log (user_id, action, namespace, kind, name, detail, actor) \
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(action)
|
||||
.bind(namespace)
|
||||
.bind(kind)
|
||||
|
||||
@@ -67,6 +67,7 @@ pub async fn migrate(pool: &PgPool) -> Result<()> {
|
||||
-- ── audit_log: append-only operation log ─────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
user_id UUID,
|
||||
action VARCHAR(32) NOT NULL,
|
||||
namespace VARCHAR(64) NOT NULL,
|
||||
kind VARCHAR(64) NOT NULL,
|
||||
@@ -76,8 +77,10 @@ pub async fn migrate(pool: &PgPool) -> Result<()> {
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
ALTER TABLE audit_log ADD COLUMN IF NOT EXISTS user_id UUID;
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_created ON audit_log(created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_ns_kind ON audit_log(namespace, kind);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_log_user_id ON audit_log(user_id) WHERE user_id IS NOT NULL;
|
||||
|
||||
-- ── entries_history ───────────────────────────────────────────────────────
|
||||
CREATE TABLE IF NOT EXISTS entries_history (
|
||||
|
||||
@@ -174,6 +174,20 @@ pub struct OauthAccount {
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// A single audit log row, optionally scoped to a business user.
|
||||
#[derive(Debug, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct AuditLogEntry {
|
||||
pub id: i64,
|
||||
pub user_id: Option<Uuid>,
|
||||
pub action: String,
|
||||
pub namespace: String,
|
||||
pub kind: String,
|
||||
pub name: String,
|
||||
pub detail: Value,
|
||||
pub actor: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
// ── TOML ↔ JSON value conversion ──────────────────────────────────────────────
|
||||
|
||||
/// Convert a serde_json Value to a toml Value.
|
||||
|
||||
@@ -346,6 +346,7 @@ pub async fn run(pool: &PgPool, params: AddParams<'_>, master_key: &[u8; 32]) ->
|
||||
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
params.user_id,
|
||||
"add",
|
||||
params.namespace,
|
||||
params.kind,
|
||||
|
||||
23
crates/secrets-core/src/service/audit_log.rs
Normal file
23
crates/secrets-core/src/service/audit_log.rs
Normal file
@@ -0,0 +1,23 @@
|
||||
use anyhow::Result;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::models::AuditLogEntry;
|
||||
|
||||
pub async fn list_for_user(pool: &PgPool, user_id: Uuid, limit: i64) -> Result<Vec<AuditLogEntry>> {
|
||||
let limit = limit.clamp(1, 200);
|
||||
|
||||
let rows = sqlx::query_as(
|
||||
"SELECT id, user_id, action, namespace, kind, name, detail, actor, created_at \
|
||||
FROM audit_log \
|
||||
WHERE user_id = $1 OR (user_id IS NULL AND detail->>'user_id' = $1::text) \
|
||||
ORDER BY created_at DESC, id DESC \
|
||||
LIMIT $2",
|
||||
)
|
||||
.bind(user_id)
|
||||
.bind(limit)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(rows)
|
||||
}
|
||||
@@ -137,7 +137,7 @@ async fn delete_one(
|
||||
};
|
||||
|
||||
snapshot_and_delete(&mut tx, namespace, kind, name, &row, user_id).await?;
|
||||
crate::audit::log_tx(&mut tx, "delete", namespace, kind, name, json!({})).await;
|
||||
crate::audit::log_tx(&mut tx, user_id, "delete", namespace, kind, name, json!({})).await;
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(DeleteResult {
|
||||
@@ -240,6 +240,7 @@ async fn delete_bulk(
|
||||
.await?;
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
user_id,
|
||||
"delete",
|
||||
namespace,
|
||||
&row.kind,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
pub mod add;
|
||||
pub mod api_key;
|
||||
pub mod audit_log;
|
||||
pub mod delete;
|
||||
pub mod env_map;
|
||||
pub mod export;
|
||||
|
||||
@@ -274,6 +274,7 @@ pub async fn run(
|
||||
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
user_id,
|
||||
"rollback",
|
||||
namespace,
|
||||
kind,
|
||||
|
||||
@@ -241,6 +241,7 @@ pub async fn run(
|
||||
|
||||
crate::audit::log_tx(
|
||||
&mut tx,
|
||||
params.user_id,
|
||||
"update",
|
||||
params.namespace,
|
||||
params.kind,
|
||||
|
||||
Reference in New Issue
Block a user