feat: add auth login options endpoint

This commit is contained in:
2026-04-21 17:02:55 +08:00
parent c3c5f1acd7
commit d234d27cc0
6 changed files with 184 additions and 5 deletions

View File

@@ -19,6 +19,7 @@ use crate::{
auth_sessions::auth_sessions,
error_middleware::normalize_error_response,
health::health_check,
login_options::auth_login_options,
logout::logout,
logout_all::logout_all,
password_entry::password_entry,
@@ -51,6 +52,10 @@ pub fn build_router(state: AppState) -> Router {
attach_refresh_session_token,
)),
)
.route(
"/api/auth/login-options",
get(auth_login_options),
)
.route(
"/api/auth/me",
get(auth_me).route_layer(middleware::from_fn_with_state(
@@ -438,6 +443,42 @@ mod tests {
assert!(payload["token"].as_str().is_some());
}
#[tokio::test]
async fn auth_login_options_returns_enabled_methods_in_stable_order() {
let config = AppConfig {
sms_auth_enabled: true,
wechat_auth_enabled: true,
..AppConfig::default()
};
let app = build_router(AppState::new(config).expect("state should build"));
let response = app
.oneshot(
Request::builder()
.uri("/api/auth/login-options")
.body(Body::empty())
.expect("request should build"),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let body = response
.into_body()
.collect()
.await
.expect("response body should collect")
.to_bytes();
let payload: Value =
serde_json::from_slice(&body).expect("response body should be valid json");
assert_eq!(
payload["availableLoginMethods"],
serde_json::json!(["phone", "wechat"])
);
}
#[tokio::test]
async fn auth_sessions_returns_multi_device_session_fields() {
let app = build_router(AppState::new(AppConfig::default()).expect("state should build"));
@@ -1238,4 +1279,4 @@ mod tests {
.is_some_and(|value| value.contains("Max-Age=0"))
);
}
}
}

View File

@@ -0,0 +1,33 @@
use axum::{
Json,
extract::{Extension, State},
};
use serde::Serialize;
use crate::{api_response::json_success_body, request_context::RequestContext, state::AppState};
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct AuthLoginOptionsResponse {
pub available_login_methods: Vec<&'static str>,
}
pub async fn auth_login_options(
State(state): State<AppState>,
Extension(request_context): Extension<RequestContext>,
) -> Json<serde_json::Value> {
let mut methods = Vec::new();
if state.config.sms_auth_enabled {
methods.push("phone");
}
if state.config.wechat_auth_enabled {
methods.push("wechat");
}
json_success_body(
Some(&request_context),
AuthLoginOptionsResponse {
available_login_methods: methods,
},
)
}

View File

@@ -9,6 +9,7 @@ mod config;
mod error_middleware;
mod health;
mod http_error;
mod login_options;
mod logout;
mod logout_all;
mod password_entry;
@@ -44,4 +45,4 @@ async fn main() -> Result<(), std::io::Error> {
info!(%bind_address, "api-server 已完成 tracing 初始化并开始监听");
axum::serve(listener, router).await
}
}