273bd1b
use http_body_util::Full;
use hyper::body::{Bytes, Incoming};
use hyper::header::{self, HeaderValue};
use hyper::{HeaderMap, Request, Response, StatusCode};
use serde_json::Value;
use crate::SocketError;
use super::SocketHub;
pub(super) type HttpRequest = Request<Incoming>;
pub(super) type HttpResponse = Response<Full<Bytes>>;
pub(super) async fn authenticate(
request: &HttpRequest,
hub: &SocketHub,
) -> Result<(i64, String), HttpResponse> {
// 三条来源,按可信度排序:
// 1. `Authorization: Bearer`(服务端/Node 客户端);
// 2. 会话 cookie(浏览器握手拿不到自定义头,这是唯一可用的带 token 方式);
// 3. 都不带 → 返回 0,等客户端在 Socket.IO CONNECT 包里用 `auth.token` 补交
// (有 `socket.auth_timeout` 兜底)。
let token = bearer_token(request)
.map(str::to_owned)
.or_else(|| session_token(request, &hub.options().session_cookie));
let Some(token) = token else {
if request.headers().contains_key(header::AUTHORIZATION) {
return Err(unauthorized("missing bearer token"));
}
return Ok((0, String::new()));
};
hub.authenticate_token(&token)
.await
.map(|user| (user, token.clone()))
.map_err(auth_error_response)
}
/// 从 `Cookie` 头里取会话 token(名字由 `socket.session_cookie` 指定)。
fn session_token(request: &HttpRequest, cookie: &str) -> Option<String> {
if cookie.trim().is_empty() {
return None;
}
let header = request.headers().get(header::COOKIE)?.to_str().ok()?;
header
.split(';')
.filter_map(|pair| pair.split_once('='))
.find_map(|(name, value)| {
(name.trim() == cookie && !value.trim().is_empty())
.then(|| value.trim().trim_matches('"').to_string())
})
}
pub(super) fn empty_response(status: StatusCode) -> HttpResponse {
let mut response = Response::new(Full::new(Bytes::new()));
*response.status_mut() = status;
response
}
pub(super) fn json_response(status: StatusCode, body: Value) -> HttpResponse {
let mut response = Response::new(Full::new(Bytes::from(body.to_string())));
*response.status_mut() = status;
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("application/json"),
);
response
.headers_mut()
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
response
}
pub(super) fn engine_error(code: u16, message: &str) -> HttpResponse {
json_response(
StatusCode::BAD_REQUEST,
serde_json::json!({
"code": code,
"message": message,
}),
)
}
pub(super) fn text_response(body: String) -> HttpResponse {
let mut response = Response::new(Full::new(Bytes::from(body)));
response.headers_mut().insert(
header::CONTENT_TYPE,
HeaderValue::from_static("text/plain; charset=utf-8"),
);
response
.headers_mut()
.insert(header::CACHE_CONTROL, HeaderValue::from_static("no-store"));
response
}
fn unauthorized(message: &str) -> HttpResponse {
json_response(
StatusCode::UNAUTHORIZED,
serde_json::json!({
"code": "UNAUTHENTICATED",
"message": message,
}),
)
}
fn auth_error_response(error: SocketError) -> HttpResponse {
match error {
SocketError::Rejected(message) => unauthorized(&message),
SocketError::Unavailable(message) => json_response(
StatusCode::SERVICE_UNAVAILABLE,
serde_json::json!({
"code": "UNAVAILABLE",
"message": message,
}),
),
SocketError::Config(message) => json_response(
StatusCode::INTERNAL_SERVER_ERROR,
serde_json::json!({
"code": "INTERNAL",
"message": message,
}),
),
}
}
pub(super) fn cors(headers: &HeaderMap, mut response: HttpResponse) -> HttpResponse {
let origin = headers
.get(header::ORIGIN)
.and_then(|value| value.to_str().ok())
.unwrap_or("*");
if let Ok(value) = HeaderValue::from_str(origin) {
response
.headers_mut()
.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, value);
if origin != "*" {
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
HeaderValue::from_static("true"),
);
}
}
response
.headers_mut()
.insert(header::VARY, HeaderValue::from_static("Origin"));
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_METHODS,
HeaderValue::from_static("GET, POST, OPTIONS"),
);
response.headers_mut().insert(
header::ACCESS_CONTROL_ALLOW_HEADERS,
HeaderValue::from_static("authorization, content-type"),
);
response
}
fn bearer_token(request: &HttpRequest) -> Option<&str> {
let value = request
.headers()
.get(header::AUTHORIZATION)?
.to_str()
.ok()?;
let (scheme, token) = value.split_once(' ')?;
if !scheme.eq_ignore_ascii_case("bearer") {
return None;
}
let token = token.trim();
(!token.is_empty()).then_some(token)
}