Artifact ecf21fc1dfd3278bf576d1542593a303d7428231a947c0cfa3d0e4372bc0df85:
- File
backend/src/password.gleam
— part of check-in
[1821522f51]
at
2026-10-23 05:51:23
on branch trunk
— RBAC 权限与多仓数据隔离
- 新增用户、角色、权限点、用户角色、用户授权仓库、登录会话、审计日志表(008_rbac) - 预设五个角色(超级管理员、仓库主管、制单/调度员、打包复核员、库内作业员),按角色限制登录终端 - 账号密码登录,PBKDF2-SHA256 加盐存储,令牌只存哈希,会话按配置过期,连续输错锁定 - 首个超级管理员从 SWIFTFLOW_ADMIN_USERNAME / SWIFTFLOW_ADMIN_PASSWORD 创建,仅在用户表为空时生效 - 除健康检查与登录外所有接口需登录并校验权限点 - 订单、采购单、库位、库存、批次码按授权仓库过滤;收货、上架、出库只能在当前作业仓库进行 - 用户管理、角色查询、审计日志接口;同步、库位、上架、收货、出库写操作记录审计 (user: pluto size: 1854)
import gleam/int import gleam/string pub const iterations = 210_000 pub const min_length = 8 pub const max_length = 128 const dummy_hash = "pbkdf2_sha256$210000$00000000000000000000000000000000$0000000000000000000000000000000000000000000000000000000000000000" @external(erlang, "swiftflow_crypto_ffi", "random_hex") pub fn random_hex(bytes: Int) -> String @external(erlang, "swiftflow_crypto_ffi", "sha256_hex") pub fn sha256_hex(data: String) -> String @external(erlang, "swiftflow_crypto_ffi", "pbkdf2_sha256_hex") fn pbkdf2(password: String, salt: String, iterations: Int) -> String @external(erlang, "swiftflow_crypto_ffi", "secure_equals") fn secure_equals(a: String, b: String) -> Bool pub fn validate(password: String) -> Result(String, String) { let length = string.length(password) case length < min_length, length > max_length { True, _ -> Error("密码至少 " <> int.to_string(min_length) <> " 位") _, True -> Error("密码不能超过 " <> int.to_string(max_length) <> " 位") False, False -> Ok(password) } } pub fn hash(password: String) -> String { hash_with(password, random_hex(16), iterations) } pub fn hash_with(password: String, salt: String, rounds: Int) -> String { "pbkdf2_sha256$" <> int.to_string(rounds) <> "$" <> salt <> "$" <> pbkdf2(password, salt, rounds) } pub fn verify(password: String, stored: String) -> Bool { case string.split(stored, "$") { ["pbkdf2_sha256", rounds, salt, expected] -> case int.parse(rounds) { Ok(rounds) if rounds > 0 -> secure_equals(pbkdf2(password, salt, rounds), expected) _ -> False } _ -> False } } /// 用户不存在时也走一次同等耗时的校验,避免靠响应时间探测账号是否存在。 pub fn burn(password: String) -> Bool { let _ = verify(password, dummy_hash) False }