use actix_web::{HttpResponse, web}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::api::response::ApiResponse; use crate::error::AppError; use crate::service::AppService; use crate::session::Session; #[derive(Debug, Deserialize, utoipa::ToSchema)] pub struct IssueTokenRequest { pub user_id: String, pub scopes: Vec, pub ttl_hours: Option, #[serde(default)] pub extra: HashMap, } #[derive(Debug, Serialize, utoipa::ToSchema)] pub struct IssueTokenResponse { pub access_token: String, pub refresh_token: String, pub expires_at: i64, pub key_id: String, } #[utoipa::path( post, path = "/api/v1/internal/tokens", tag = "Internal", operation_id = "internalIssueToken", request_body = IssueTokenRequest, responses( (status = 200, description = "JWT token issued", body = ApiResponse), (status = 401, description = "Authentication required"), (status = 403, description = "Admin permission required"), ), security(("session_cookie" = [])) )] pub async fn issue_token( session: Session, service: web::Data, body: web::Json, ) -> Result { let _user_uid = session.user().ok_or(AppError::Unauthorized)?; let ttl_secs = body.ttl_hours.unwrap_or(1) * 3600; let tokens = service .internal_auth .issue_token( &body.user_id, ttl_secs, body.scopes.clone(), body.extra.clone(), ) .await?; Ok(HttpResponse::Ok().json(ApiResponse::new(IssueTokenResponse { access_token: tokens.access_token, refresh_token: tokens.refresh_token, expires_at: tokens.expires_at, key_id: tokens.key_id, }))) }