Files
Genarrative/server-rs/crates/shared-contracts/src/auth.rs

263 lines
7.2 KiB
Rust

use serde::{Deserialize, Serialize};
pub const AUTH_LOGIN_METHOD_PASSWORD: &str = "password";
pub const AUTH_LOGIN_METHOD_PHONE: &str = "phone";
pub const AUTH_LOGIN_METHOD_WECHAT: &str = "wechat";
pub const AUTH_BINDING_STATUS_ACTIVE: &str = "active";
pub const AUTH_BINDING_STATUS_PENDING_BIND_PHONE: &str = "pending_bind_phone";
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AuthLoginOptionsResponse {
pub available_login_methods: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AuthUserPayload {
pub id: String,
pub public_user_code: String,
pub username: String,
pub display_name: String,
pub phone_number_masked: Option<String>,
pub login_method: String,
pub binding_status: String,
pub wechat_bound: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicUserSummaryPayload {
pub id: String,
pub public_user_code: String,
pub display_name: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PublicUserSearchResponse {
pub user: PublicUserSummaryPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordEntryRequest {
pub username: String,
pub password: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordEntryResponse {
pub token: String,
pub user: AuthUserPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordChangeRequest {
pub current_password: Option<String>,
pub new_password: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordChangeResponse {
pub user: AuthUserPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordResetRequest {
pub phone: String,
pub code: String,
pub new_password: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PasswordResetResponse {
pub token: String,
pub user: AuthUserPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AuthMeResponse {
pub user: AuthUserPayload,
pub available_login_methods: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AuthSessionsResponse {
pub sessions: Vec<AuthSessionSummaryPayload>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AuthSessionSummaryPayload {
pub session_id: String,
pub client_type: String,
pub client_runtime: String,
pub client_platform: String,
pub client_label: String,
pub device_display_name: String,
pub mini_program_app_id: Option<String>,
pub mini_program_env: Option<String>,
pub user_agent: Option<String>,
pub ip_masked: Option<String>,
pub is_current: bool,
pub created_at: String,
pub last_seen_at: String,
pub expires_at: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct RefreshSessionResponse {
pub token: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct LogoutResponse {
pub ok: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct LogoutAllResponse {
pub ok: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PhoneSendCodeRequest {
pub phone: String,
pub scene: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PhoneSendCodeResponse {
pub ok: bool,
pub cooldown_seconds: u64,
pub expires_in_seconds: u64,
pub provider_request_id: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PhoneLoginRequest {
pub phone: String,
pub code: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct PhoneLoginResponse {
pub token: String,
pub user: AuthUserPayload,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WechatStartQuery {
pub redirect_path: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WechatStartResponse {
pub authorization_url: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct WechatCallbackQuery {
pub state: Option<String>,
pub code: Option<String>,
pub mock_code: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WechatBindPhoneRequest {
pub phone: String,
pub code: String,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct WechatBindPhoneResponse {
pub token: String,
pub user: AuthUserPayload,
}
pub fn build_available_login_methods(
sms_auth_enabled: bool,
wechat_auth_enabled: bool,
) -> Vec<String> {
let mut methods = vec![AUTH_LOGIN_METHOD_PASSWORD.to_string()];
if sms_auth_enabled {
methods.push(AUTH_LOGIN_METHOD_PHONE.to_string());
}
if wechat_auth_enabled {
methods.push(AUTH_LOGIN_METHOD_WECHAT.to_string());
}
methods
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn available_login_methods_keep_phone_then_wechat_order() {
let methods = build_available_login_methods(true, true);
assert_eq!(
methods,
vec![
AUTH_LOGIN_METHOD_PASSWORD.to_string(),
AUTH_LOGIN_METHOD_PHONE.to_string(),
AUTH_LOGIN_METHOD_WECHAT.to_string()
]
);
}
#[test]
fn password_entry_request_uses_camel_case_fields() {
let payload = serde_json::to_value(PasswordEntryRequest {
username: "guest_001".to_string(),
password: "secret123".to_string(),
})
.expect("payload should serialize");
assert_eq!(
payload,
json!({
"username": "guest_001",
"password": "secret123"
})
);
}
#[test]
fn wechat_callback_query_keeps_provider_compatible_field_names() {
let payload = serde_json::to_value(WechatCallbackQuery {
state: Some("state-1".to_string()),
code: Some("code-1".to_string()),
mock_code: Some("mock-1".to_string()),
})
.expect("payload should serialize");
assert_eq!(
payload,
json!({
"state": "state-1",
"code": "code-1",
"mock_code": "mock-1"
})
);
}
}