263 lines
7.2 KiB
Rust
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"
|
|
})
|
|
);
|
|
}
|
|
}
|