Introduce goose migration 00015 to extend users with IAM columns and seed system roles/permissions for Phase 1 authorization.
184 lines
8.4 KiB
SQL
184 lines
8.4 KiB
SQL
-- +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;
|