feat(auth): 接入 DCAuth v2 登录并完善成员管理
- 登录链路改为用户名 + 密码 + 短信验证码 + DCAuth v2 设备签名 - 迁移 014:新增 login_sms_codes / user_devices / device_challenges 与 users.phone - 新增 /api/dcauth/authorize 与 challenge 状态轮询;旧密码登录仅放行未绑定手机号的账号 - 成员管理支持手机号绑定、设备重置、账号改名、昵称与重置密码 - 移除最后一个商户关系时软删除账号,释放用户名、手机号并撤销设备绑定
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
-- ============================================================================
|
||||
-- 014_login_sms_device_auth.sql
|
||||
-- 用户名 + 密码 + 短信验证码 + DCAuth v2 设备签名登录。
|
||||
-- 未绑定手机号的历史账号仍可使用原密码登录。
|
||||
-- ============================================================================
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS phone VARCHAR(32) NOT NULL DEFAULT '';
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS phone_bound_at TIMESTAMPTZ;
|
||||
ALTER TABLE users
|
||||
ADD COLUMN IF NOT EXISTS phone_bound_by BIGINT;
|
||||
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM pg_constraint WHERE conname = 'fk_users_phone_bound_by'
|
||||
) THEN
|
||||
ALTER TABLE users
|
||||
ADD CONSTRAINT fk_users_phone_bound_by
|
||||
FOREIGN KEY (phone_bound_by) REFERENCES users(id) ON DELETE SET NULL;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_phone
|
||||
ON users(phone)
|
||||
WHERE phone <> '';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS login_sms_codes (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
phone VARCHAR(32) NOT NULL,
|
||||
purpose VARCHAR(32) NOT NULL DEFAULT 'login',
|
||||
code_hash VARCHAR(64) NOT NULL,
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
consumed_at TIMESTAMPTZ,
|
||||
failed_attempt_count INTEGER NOT NULL DEFAULT 0,
|
||||
request_ip VARCHAR(64) NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_login_sms_codes_user_created
|
||||
ON login_sms_codes(user_id, purpose, created_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS idx_login_sms_codes_phone_created
|
||||
ON login_sms_codes(phone, created_at DESC);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_devices (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
device_id VARCHAR(128) NOT NULL,
|
||||
public_key VARCHAR(128) NOT NULL,
|
||||
client_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'active',
|
||||
bound_at TIMESTAMPTZ NOT NULL,
|
||||
last_used_at TIMESTAMPTZ,
|
||||
revoked_at TIMESTAMPTZ,
|
||||
revoked_by BIGINT REFERENCES users(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_user_devices_active_user
|
||||
ON user_devices(user_id)
|
||||
WHERE status = 'active';
|
||||
CREATE INDEX IF NOT EXISTS idx_user_devices_device_id
|
||||
ON user_devices(device_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS device_challenges (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
challenge_id VARCHAR(128) NOT NULL UNIQUE,
|
||||
app VARCHAR(64) NOT NULL,
|
||||
purpose VARCHAR(32) NOT NULL DEFAULT 'login',
|
||||
challenge VARCHAR(512) NOT NULL,
|
||||
poll_token_hash VARCHAR(64) NOT NULL,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'pending',
|
||||
user_id BIGINT REFERENCES users(id) ON DELETE SET NULL,
|
||||
device_id VARCHAR(128) NOT NULL DEFAULT '',
|
||||
public_key VARCHAR(128) NOT NULL DEFAULT '',
|
||||
client_version VARCHAR(64) NOT NULL DEFAULT '',
|
||||
device_ticket_hash VARCHAR(64) NOT NULL DEFAULT '',
|
||||
expires_at TIMESTAMPTZ NOT NULL,
|
||||
authorized_at TIMESTAMPTZ,
|
||||
ticket_issued_at TIMESTAMPTZ,
|
||||
consumed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_device_challenges_expires
|
||||
ON device_challenges(expires_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_device_challenges_user
|
||||
ON device_challenges(user_id, created_at DESC);
|
||||
|
||||
COMMENT ON COLUMN users.phone IS '账号绑定手机号,用于登录短信验证码';
|
||||
COMMENT ON COLUMN users.phone_bound_by IS '执行手机号绑定的管理员用户 ID';
|
||||
COMMENT ON TABLE login_sms_codes IS '登录短信验证码(哈希存储、一次性消费)';
|
||||
COMMENT ON TABLE user_devices IS '账号绑定的 DCAuth 设备(公钥指纹)';
|
||||
COMMENT ON TABLE device_challenges IS 'DCAuth v2 设备认证 challenge 与一次性 ticket';
|
||||
Reference in New Issue
Block a user