Harden auth/vault path sanitization and clean WeKnora docs
This commit is contained in:
@@ -19,12 +19,17 @@ pub struct UserRecord {
|
||||
pub revision: i64,
|
||||
}
|
||||
|
||||
/// 会话令牌哈希(领域前缀,避免与 share/password 等 token 类型混淆)。
|
||||
/// 注意:改前缀会使既有 session 行失效,需用户重新登录。
|
||||
pub fn session_token_hash(raw_token: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"mnote-session-token-v1:");
|
||||
hasher.update(raw_token.as_bytes());
|
||||
hex::encode(hasher.finalize())
|
||||
format!("sha256-v1:{}", hex::encode(hasher.finalize()))
|
||||
}
|
||||
|
||||
/// 密码哈希 v1:SHA-256 + 固定领域前缀(兼容既有 `sha256-v1:` 存档)。
|
||||
/// 技术债:生产应迁移 Argon2id + 每用户随机盐;在此保持算法兼容以免批量锁死账号。
|
||||
pub fn password_hash_v1(password: &str) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(b"mnote-password-v1:");
|
||||
@@ -39,27 +44,76 @@ pub fn share_token_hash_v1(token: &str) -> String {
|
||||
format!("sha256-v1:{}", hex::encode(hasher.finalize()))
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct UpsertUserInput {
|
||||
pub id: Option<EntityId>,
|
||||
pub email: Option<String>,
|
||||
pub username: String,
|
||||
pub display_name: String,
|
||||
pub role: Option<String>,
|
||||
/// 密码哈希;Debug 脱敏,避免日志泄露。
|
||||
pub password_hash: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
impl std::fmt::Debug for UpsertUserInput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UpsertUserInput")
|
||||
.field("id", &self.id)
|
||||
.field("email", &self.email)
|
||||
.field("username", &self.username)
|
||||
.field("display_name", &self.display_name)
|
||||
.field("role", &self.role)
|
||||
.field(
|
||||
"password_hash",
|
||||
&self
|
||||
.password_hash
|
||||
.as_ref()
|
||||
.map(|_| "<redacted>")
|
||||
.unwrap_or("None"),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct CreatePasswordIdentityInput {
|
||||
pub user_id: EntityId,
|
||||
pub email: Option<String>,
|
||||
pub username: String,
|
||||
/// 明文密码仅用于创建身份;Debug/Serialize 必须脱敏。
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
impl Serialize for CreatePasswordIdentityInput {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeStruct;
|
||||
let mut state = serializer.serialize_struct("CreatePasswordIdentityInput", 4)?;
|
||||
state.serialize_field("user_id", &self.user_id)?;
|
||||
state.serialize_field("email", &self.email)?;
|
||||
state.serialize_field("username", &self.username)?;
|
||||
state.serialize_field("password", "<redacted>")?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for CreatePasswordIdentityInput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("CreatePasswordIdentityInput")
|
||||
.field("user_id", &self.user_id)
|
||||
.field("email", &self.email)
|
||||
.field("username", &self.username)
|
||||
.field("password", &"<redacted>")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Deserialize)]
|
||||
pub struct AuthenticatePasswordInput {
|
||||
pub account: String,
|
||||
/// 明文密码仅用于鉴权;Debug/Serialize 脱敏。
|
||||
pub password: String,
|
||||
pub session_id: Option<EntityId>,
|
||||
pub token_hash: String,
|
||||
@@ -68,6 +122,38 @@ pub struct AuthenticatePasswordInput {
|
||||
pub expires_at: Option<Timestamp>,
|
||||
}
|
||||
|
||||
impl Serialize for AuthenticatePasswordInput {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
use serde::ser::SerializeStruct;
|
||||
let mut state = serializer.serialize_struct("AuthenticatePasswordInput", 7)?;
|
||||
state.serialize_field("account", &self.account)?;
|
||||
state.serialize_field("password", "<redacted>")?;
|
||||
state.serialize_field("session_id", &self.session_id)?;
|
||||
state.serialize_field("token_hash", "<redacted>")?;
|
||||
state.serialize_field("user_agent", &self.user_agent)?;
|
||||
state.serialize_field("ip_hash", &self.ip_hash)?;
|
||||
state.serialize_field("expires_at", &self.expires_at)?;
|
||||
state.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for AuthenticatePasswordInput {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("AuthenticatePasswordInput")
|
||||
.field("account", &self.account)
|
||||
.field("password", &"<redacted>")
|
||||
.field("session_id", &self.session_id)
|
||||
.field("token_hash", &"<redacted>")
|
||||
.field("user_agent", &self.user_agent)
|
||||
.field("ip_hash", &self.ip_hash)
|
||||
.field("expires_at", &self.expires_at)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct AuthSessionRecord {
|
||||
pub id: EntityId,
|
||||
|
||||
Reference in New Issue
Block a user