Files
gitks/api/auth/ws_token.rs
T
2026-06-12 16:55:36 +08:00

58 lines
2.1 KiB
Rust

use std::collections::HashMap;
use actix_web::{HttpResponse, web};
use serde::Serialize;
use crate::api::response::{ApiErrorResponse, ApiResponse};
use crate::error::AppError;
use crate::service::AppService;
use crate::session::Session;
/// Response payload for `POST /auth/ws-token`.
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct WsTokenResponse {
/// Short-lived JWT prefixed with "Bearer " for use in the Socket.IO CONNECT auth packet.
pub token: String,
/// Unix timestamp (seconds) when the token expires.
pub expires_at: i64,
}
#[utoipa::path(
post,
path = "/api/v1/auth/ws-token",
tag = "Auth",
operation_id = "authWsToken",
summary = "Issue a short-lived WebSocket token",
description = "Issue a short-lived JWT (30 minutes) scoped to IM WebSocket access. \
The token is signed by the appks signing key and can be verified by imks either \
locally (via cached signing keys) or via RPC. The returned token should be passed \
as `{ token: <value> }` in the Socket.IO CONNECT auth packet. Requires an \
authenticated session.",
responses(
(status = 200, description = "Token issued successfully.", body = ApiResponse<WsTokenResponse>),
(status = 401, description = "The current session is unauthenticated or the login state has expired.", body = ApiErrorResponse),
(status = 500, description = "Token issuance or Redis write failed.", body = ApiErrorResponse)
)
)]
pub async fn handle(
service: web::Data<AppService>,
session: Session,
) -> Result<HttpResponse, AppError> {
let user_uid = session.user().ok_or(AppError::Unauthorized)?;
let issued = service
.internal_auth
.issue_token(
&user_uid.to_string(),
1800, // 30-minute TTL (frontend refreshes every 25 min)
vec!["im:read".into(), "im:write".into()],
HashMap::new(),
)
.await?;
Ok(HttpResponse::Ok().json(ApiResponse::new(WsTokenResponse {
token: format!("Bearer {}", issued.access_token),
expires_at: issued.expires_at,
})))
}