Files
Agent-System/deploy/postgres/goose/00015_2026-09-12-iam-core.sql
T
weichen 3b72503ec6 feat(db): add IAM core migration for users, sessions, and RBAC
Introduce goose migration 00015 to extend users with IAM columns and seed system roles/permissions for Phase 1 authorization.
2026-09-13 02:50:22 +00:00

184 lines
8.4 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- +goose Up
-- Phase 1: IAM 核心表 — 扩展既有 users,新增凭证、会话、角色与权限
-- 1. 扩展 users(与 ctxmgr 既有 account_id/name/role 共存)
ALTER TABLE users ADD COLUMN IF NOT EXISTS username TEXT;
ALTER TABLE users ADD COLUMN IF NOT EXISTS status TEXT NOT NULL DEFAULT 'active';
ALTER TABLE users ADD COLUMN IF NOT EXISTS must_change_password BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE users ADD COLUMN IF NOT EXISTS role_id TEXT NOT NULL DEFAULT 'role-guest';
ALTER TABLE users ADD COLUMN IF NOT EXISTS position TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS department TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS employee_id TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS email TEXT NOT NULL DEFAULT '';
ALTER TABLE users ADD COLUMN IF NOT EXISTS locale TEXT NOT NULL DEFAULT 'zh-CN';
ALTER TABLE users ADD COLUMN IF NOT EXISTS timezone TEXT NOT NULL DEFAULT 'Asia/Shanghai';
UPDATE users SET username = account_id WHERE username IS NULL OR btrim(username) = '';
UPDATE users SET role_id = CASE role
WHEN 'director' THEN 'role-director'
WHEN 'pm' THEN 'role-director'
WHEN 'regulatory' THEN 'role-tech-lead'
WHEN 'engineer' THEN 'role-engineer'
WHEN 'sales' THEN 'role-guest'
ELSE COALESCE(NULLIF(role_id, ''), 'role-guest')
END
WHERE role_id IS NULL OR role_id = '' OR role_id = 'role-guest';
CREATE UNIQUE INDEX IF NOT EXISTS uq_users_username ON users (lower(username))
WHERE username IS NOT NULL AND btrim(username) <> '';
-- 2. 凭证与会话
CREATE TABLE IF NOT EXISTS user_credentials (
user_id BIGINT PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
password_hash TEXT NOT NULL,
password_changed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
failed_count INT NOT NULL DEFAULT 0,
locked_until TIMESTAMPTZ
);
CREATE TABLE IF NOT EXISTS auth_sessions (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
token_hash TEXT NOT NULL UNIQUE,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
idle_expires_at TIMESTAMPTZ NOT NULL,
absolute_expires_at TIMESTAMPTZ NOT NULL,
revoked_at TIMESTAMPTZ,
ip_hash TEXT NOT NULL DEFAULT '',
user_agent TEXT NOT NULL DEFAULT ''
);
CREATE INDEX IF NOT EXISTS idx_auth_sessions_user ON auth_sessions(user_id);
CREATE INDEX IF NOT EXISTS idx_auth_sessions_active ON auth_sessions(user_id) WHERE revoked_at IS NULL;
-- 3. 系统角色与权限(与前端 PermissionKey 对齐)
CREATE TABLE IF NOT EXISTS system_roles (
id TEXT PRIMARY KEY,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
scope_type TEXT NOT NULL DEFAULT 'system',
status TEXT NOT NULL DEFAULT 'active',
is_system BOOLEAN NOT NULL DEFAULT false,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS system_permissions (
id BIGSERIAL PRIMARY KEY,
perm_key TEXT NOT NULL UNIQUE,
resource_type TEXT NOT NULL DEFAULT 'system',
action TEXT NOT NULL DEFAULT '',
risk_level TEXT NOT NULL DEFAULT 'normal',
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS system_role_permissions (
role_id TEXT NOT NULL REFERENCES system_roles(id) ON DELETE CASCADE,
permission_id BIGINT NOT NULL REFERENCES system_permissions(id) ON DELETE CASCADE,
effect TEXT NOT NULL DEFAULT 'allow',
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE IF NOT EXISTS user_role_bindings (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
role_id TEXT NOT NULL REFERENCES system_roles(id) ON DELETE RESTRICT,
scope_type TEXT NOT NULL DEFAULT 'system',
scope_id BIGINT NOT NULL DEFAULT 0,
valid_from TIMESTAMPTZ NOT NULL DEFAULT now(),
valid_until TIMESTAMPTZ,
assigned_by BIGINT REFERENCES users(id) ON DELETE SET NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CONSTRAINT uq_user_role_binding UNIQUE (user_id, role_id, scope_type, scope_id)
);
CREATE INDEX IF NOT EXISTS idx_user_role_bindings_user ON user_role_bindings(user_id);
-- 4. 准入配置(替代 MemoryStore 进程内 config)
CREATE TABLE IF NOT EXISTS iam_system_config (
config_key TEXT PRIMARY KEY,
config_value JSONB NOT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
INSERT INTO iam_system_config (config_key, config_value) VALUES
('auth', '{"allow_registration": true, "require_approval": false}'::jsonb)
ON CONFLICT (config_key) DO NOTHING;
-- 5. 种子角色
INSERT INTO system_roles (id, code, name, description, is_system) VALUES
('role-admin', 'ADMIN', '系统管理员', '拥有系统所有功能与数据的全量管理权限(防死锁兜底)。', true),
('role-director', 'DIRECTOR', '研发总监', '负责研发中心整体项目决策、立项审查、权限监督与终审批准。', false),
('role-tech-lead', 'TECH_LEAD', '研发技术主管', '负责具体研发管线的进度把控、文档责任人分配与技术审核。', false),
('role-engineer', 'ENGINEER', '方案工程师', '负责医疗器械规格设计、受控正文编制、技术测算与推演。', false),
('role-guest', 'GUEST', '外部审计/访客', '默认最小权限,仅在被授权范围内查阅。', false)
ON CONFLICT (id) DO NOTHING;
INSERT INTO system_permissions (perm_key, resource_type, action, risk_level) VALUES
('project:create', 'project', 'create', 'normal'),
('project:assign_lead', 'project', 'assign_lead', 'elevated'),
('perm:manage', 'system', 'manage', 'critical'),
('workflow:config_visibility', 'workflow', 'config_visibility', 'elevated'),
('doc:override_visibility', 'document', 'override_visibility', 'elevated'),
('doc:export', 'document', 'export', 'normal'),
('workflow:view_all', 'workflow', 'view_all', 'normal')
ON CONFLICT (perm_key) DO NOTHING;
INSERT INTO system_role_permissions (role_id, permission_id, effect)
SELECT r.id, p.id, 'allow'
FROM system_roles r
CROSS JOIN system_permissions p
WHERE r.id = 'role-admin'
ON CONFLICT DO NOTHING;
INSERT INTO system_role_permissions (role_id, permission_id, effect)
SELECT 'role-director', p.id, 'allow' FROM system_permissions p
WHERE p.perm_key IN ('project:create','project:assign_lead','workflow:config_visibility','doc:override_visibility','doc:export','workflow:view_all')
ON CONFLICT DO NOTHING;
INSERT INTO system_role_permissions (role_id, permission_id, effect)
SELECT 'role-tech-lead', p.id, 'allow' FROM system_permissions p
WHERE p.perm_key IN ('project:create','project:assign_lead','doc:override_visibility','doc:export','workflow:view_all')
ON CONFLICT DO NOTHING;
INSERT INTO system_role_permissions (role_id, permission_id, effect)
SELECT 'role-engineer', p.id, 'allow' FROM system_permissions p
WHERE p.perm_key IN ('doc:export','workflow:view_all')
ON CONFLICT DO NOTHING;
INSERT INTO system_role_permissions (role_id, permission_id, effect)
SELECT 'role-guest', p.id, 'allow' FROM system_permissions p
WHERE p.perm_key IN ('doc:export')
ON CONFLICT DO NOTHING;
-- 6. 为已存在的 user id=1 绑定管理员(若存在)
INSERT INTO user_role_bindings (user_id, role_id, scope_type, scope_id, assigned_by)
SELECT 1, 'role-admin', 'system', 0, 1
WHERE EXISTS (SELECT 1 FROM users WHERE id = 1)
ON CONFLICT DO NOTHING;
-- +goose Down
DROP TABLE IF EXISTS user_role_bindings;
DROP TABLE IF EXISTS system_role_permissions;
DROP TABLE IF EXISTS system_permissions;
DROP TABLE IF EXISTS system_roles;
DROP TABLE IF EXISTS auth_sessions;
DROP TABLE IF EXISTS user_credentials;
DROP TABLE IF EXISTS iam_system_config;
ALTER TABLE users DROP COLUMN IF EXISTS timezone;
ALTER TABLE users DROP COLUMN IF EXISTS locale;
ALTER TABLE users DROP COLUMN IF EXISTS email;
ALTER TABLE users DROP COLUMN IF EXISTS employee_id;
ALTER TABLE users DROP COLUMN IF EXISTS department;
ALTER TABLE users DROP COLUMN IF EXISTS position;
ALTER TABLE users DROP COLUMN IF EXISTS role_id;
ALTER TABLE users DROP COLUMN IF EXISTS must_change_password;
ALTER TABLE users DROP COLUMN IF EXISTS status;
ALTER TABLE users DROP COLUMN IF EXISTS username;
DROP INDEX IF EXISTS uq_users_username;