manta_server/server/handlers/
auth.rs1use std::net::SocketAddr;
10use std::sync::Arc;
11
12use axum::{
13 Json,
14 extract::{ConnectInfo, State},
15 http::StatusCode,
16 response::IntoResponse,
17};
18use manta_shared::common::audit;
19use manta_shared::shared::auth::{
20 AuthTokenRequest, AuthTokenResponse, ValidateTokenRequest,
21};
22
23use super::{ErrorResponse, ServerState, SiteHeader, SiteName};
24use crate::service;
25
26fn generic_invalid_credentials() -> (StatusCode, Json<ErrorResponse>) {
29 (
30 StatusCode::UNAUTHORIZED,
31 Json(ErrorResponse {
32 error: "invalid credentials".to_string(),
33 }),
34 )
35}
36
37#[utoipa::path(post, path = "/auth/token", tag = "auth",
39 params(SiteHeader),
40 request_body = AuthTokenRequest,
41 responses(
42 (status = 200, description = "Token issued", body = AuthTokenResponse),
43 (status = 401, description = "Invalid credentials", body = ErrorResponse),
44 (status = 429, description = "Rate limit exceeded", body = ErrorResponse),
45 (status = 500, description = "Internal error", body = ErrorResponse),
46 )
47)]
48#[tracing::instrument(skip_all)]
49pub async fn auth_token(
50 State(state): State<Arc<ServerState>>,
51 SiteName(site_name): SiteName,
52 ConnectInfo(peer): ConnectInfo<SocketAddr>,
53 Json(req): Json<AuthTokenRequest>,
54) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
55 let infra = state.infra_context(&site_name).map_err(|e| {
56 tracing::warn!("auth_token: site lookup failed: {}", e);
57 generic_invalid_credentials()
58 })?;
59 let source_ip = peer.ip().to_string();
60
61 tracing::info!(
62 user = %req.username,
63 site = %site_name,
64 from = %source_ip,
65 "auth_token: credential exchange requested"
66 );
67
68 match service::auth::get_api_token(&infra, &req.username, &req.password).await
69 {
70 Ok(token) => {
71 tracing::info!(
72 user = %req.username,
73 site = %site_name,
74 from = %source_ip,
75 "auth_token: token issued"
76 );
77 audit::send_auth_audit(
78 state.auditor.as_ref(),
79 "success",
80 &req.username,
81 &source_ip,
82 &site_name,
83 )
84 .await;
85 Ok(Json(AuthTokenResponse { token }))
86 }
87 Err(e) => {
88 tracing::warn!(
89 "auth_token: backend rejected user={} site={} from={}: {}",
90 req.username,
91 site_name,
92 source_ip,
93 e
94 );
95 audit::send_auth_audit(
96 state.auditor.as_ref(),
97 "failure",
98 &req.username,
99 &source_ip,
100 &site_name,
101 )
102 .await;
103 Err(generic_invalid_credentials())
104 }
105 }
106}
107
108#[utoipa::path(post, path = "/auth/validate", tag = "auth",
110 params(SiteHeader),
111 request_body = ValidateTokenRequest,
112 responses(
113 (status = 200, description = "Token is valid"),
114 (status = 401, description = "Token rejected", body = ErrorResponse),
115 (status = 429, description = "Rate limit exceeded", body = ErrorResponse),
116 (status = 500, description = "Internal error", body = ErrorResponse),
117 )
118)]
119#[tracing::instrument(skip_all)]
120pub async fn auth_validate(
121 State(state): State<Arc<ServerState>>,
122 SiteName(site_name): SiteName,
123 Json(req): Json<ValidateTokenRequest>,
124) -> Result<impl IntoResponse, (StatusCode, Json<ErrorResponse>)> {
125 let infra = state.infra_context(&site_name).map_err(|e| {
126 tracing::warn!("auth_validate: site lookup failed: {}", e);
127 generic_invalid_credentials()
128 })?;
129 tracing::info!(site = %site_name, "auth_validate: token check requested");
130 match service::auth::validate_api_token(&infra, &req.token).await {
131 Ok(()) => {
132 tracing::info!(site = %site_name, "auth_validate: token accepted");
133 Ok(StatusCode::OK)
134 }
135 Err(e) => {
136 tracing::warn!("auth_validate: backend rejected token: {}", e);
137 Err(generic_invalid_credentials())
138 }
139 }
140}