update mian.rs and openapi.json
This commit is contained in:
@@ -16,6 +16,7 @@ pub mod rsa;
|
||||
pub mod verify_2fa;
|
||||
pub mod verify_email;
|
||||
pub mod verify_reset_password;
|
||||
pub mod ws_token;
|
||||
|
||||
use actix_web::web;
|
||||
|
||||
@@ -27,6 +28,7 @@ pub fn configure(cfg: &mut web::ServiceConfig) {
|
||||
.route("/login", web::post().to(login::handle))
|
||||
.route("/logout", web::post().to(logout::handle))
|
||||
.route("/me", web::get().to(me::handle))
|
||||
.route("/ws-token", web::post().to(ws_token::handle))
|
||||
.route(
|
||||
"/register/email-code",
|
||||
web::post().to(register_email_code::handle),
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
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,
|
||||
})))
|
||||
}
|
||||
@@ -4,6 +4,7 @@ use crate::api::auth::regenerate_2fa_backup_codes::{
|
||||
Regenerate2FABackupCodesRequest, Regenerate2FABackupCodesResponse,
|
||||
};
|
||||
use crate::api::auth::register::RegisterResponse;
|
||||
use crate::api::auth::ws_token::WsTokenResponse;
|
||||
use crate::api::issue::lock::LockIssueParams;
|
||||
use crate::api::issue::subscribers::MuteIssueParams;
|
||||
use crate::api::issue::transfer::TransferIssueParams;
|
||||
@@ -174,6 +175,7 @@ use crate::service::im::members::{InviteMemberParams, UpdateMemberParams};
|
||||
crate::api::auth::disable_2fa::handle,
|
||||
crate::api::auth::regenerate_2fa_backup_codes::handle,
|
||||
crate::api::auth::change_password::change_password,
|
||||
crate::api::auth::ws_token::handle,
|
||||
// User
|
||||
crate::api::user::get_account::get_account,
|
||||
crate::api::user::update_account::update_account,
|
||||
@@ -839,6 +841,8 @@ use crate::service::im::members::{InviteMemberParams, UpdateMemberParams};
|
||||
NotifyUpdateTemplateParams,
|
||||
// Auth additions
|
||||
ChangePasswordParams,
|
||||
ApiResponse<WsTokenResponse>,
|
||||
WsTokenResponse,
|
||||
// User additions - Presence/Block/Follow
|
||||
ApiResponse<UserPresence>,
|
||||
UserPresence,
|
||||
|
||||
@@ -75,6 +75,22 @@ async fn main() -> AppResult<()> {
|
||||
}
|
||||
});
|
||||
|
||||
// Background task: rotate JWT signing keys every 10 minutes.
|
||||
let token_service = service.internal_auth.clone();
|
||||
tokio::spawn(async move {
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(600));
|
||||
// Skip the first immediate tick.
|
||||
interval.tick().await;
|
||||
loop {
|
||||
interval.tick().await;
|
||||
match token_service.rotate_if_needed().await {
|
||||
Ok(true) => tracing::info!("signing key rotated"),
|
||||
Ok(false) => tracing::debug!("signing key rotation not needed"),
|
||||
Err(e) => tracing::error!(error = %e, "signing key rotation failed"),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let host = config.get_env_or::<String>("APP_HTTP_HOST", "0.0.0.0".to_string())?;
|
||||
let port = config.get_env_or::<u16>("APP_HTTP_PORT", 8000)?;
|
||||
let workers = config.get_env_or::<usize>(
|
||||
|
||||
@@ -1035,6 +1035,48 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/auth/ws-token": {
|
||||
"post": {
|
||||
"tags": [
|
||||
"Auth"
|
||||
],
|
||||
"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.",
|
||||
"operationId": "authWsToken",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Token issued successfully.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiResponse_WsTokenResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "The current session is unauthenticated or the login state has expired.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Token issuance or Redis write failed.",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/im/workspaces/{workspace_name}/categories": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -44098,6 +44140,33 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiResponse_WsTokenResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"data"
|
||||
],
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"description": "Response payload for `POST /auth/ws-token`.",
|
||||
"required": [
|
||||
"token",
|
||||
"expires_at"
|
||||
],
|
||||
"properties": {
|
||||
"expires_at": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Unix timestamp (seconds) when the token expires."
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Short-lived JWT prefixed with \"Bearer \" for use in the Socket.IO CONNECT auth packet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ApiResponse_bool": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -55495,6 +55564,25 @@
|
||||
"format": "uuid"
|
||||
}
|
||||
}
|
||||
},
|
||||
"WsTokenResponse": {
|
||||
"type": "object",
|
||||
"description": "Response payload for `POST /auth/ws-token`.",
|
||||
"required": [
|
||||
"token",
|
||||
"expires_at"
|
||||
],
|
||||
"properties": {
|
||||
"expires_at": {
|
||||
"type": "integer",
|
||||
"format": "int64",
|
||||
"description": "Unix timestamp (seconds) when the token expires."
|
||||
},
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Short-lived JWT prefixed with \"Bearer \" for use in the Socket.IO CONNECT auth packet."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user