#![allow(clippy::needless_for_each)]
use actix_web::{
Result, Scope,
dev::{ServiceFactory, ServiceRequest},
error::{ErrorInternalServerError, ErrorUnauthorized},
route,
web::{self, Json},
};
use moosicbox_middleware::tunnel_info::TunnelInfo;
use serde::Deserialize;
use serde_json::{Value, json};
use switchy_database::config::ConfigDatabase;
use url::form_urlencoded;
use crate::{NonTunnelRequestAuthorized, create_magic_token, get_credentials_from_magic_token};
#[must_use]
pub fn bind_services<
T: ServiceFactory<ServiceRequest, Config = (), Error = actix_web::Error, InitError = ()>,
>(
scope: Scope<T>,
) -> Scope<T> {
scope
.service(get_magic_token_endpoint)
.service(create_magic_token_endpoint)
}
#[cfg(feature = "openapi")]
#[derive(utoipa::OpenApi)]
#[openapi(
tags((name = "Auth")),
paths(get_magic_token_endpoint, create_magic_token_endpoint,),
components(schemas(MagicTokenQuery, CreateMagicTokenQuery))
)]
pub struct Api;
#[derive(Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "camelCase")]
pub struct MagicTokenQuery {
magic_token: String,
}
#[cfg_attr(
feature = "openapi", utoipa::path(
tags = ["Auth"],
get,
path = "/magic-token",
description = "Get the credentials associated with a magic token",
params(
("moosicbox-profile" = String, Header, description = "MoosicBox profile"),
("magicToken" = String, Query,
description = "The magic token to fetch the credentials for"),
),
responses(
(status = 200, description = "The credentials for the magic token", body = Value)
)
)
)]
#[route("/magic-token", method = "GET")]
pub async fn get_magic_token_endpoint(
query: web::Query<MagicTokenQuery>,
db: ConfigDatabase,
) -> Result<Json<Value>> {
if let Some((client_id, access_token)) =
get_credentials_from_magic_token(&db, &query.magic_token)
.await
.map_err(|e| {
log::error!("Failed to get magic token: {e:?}");
ErrorInternalServerError("Failed to get magic token")
})?
{
Ok(Json(
json!({"clientId": client_id, "accessToken": access_token}),
))
} else {
log::warn!("Unauthorized get magic-token request");
Err(ErrorUnauthorized("Unauthorized"))
}
}
#[derive(Deserialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
pub struct CreateMagicTokenQuery {
host: Option<String>,
}
#[cfg_attr(
feature = "openapi", utoipa::path(
tags = ["Auth"],
post,
path = "/magic-token",
description = "Create a new magic token",
params(
("moosicbox-profile" = String, Header, description = "MoosicBox profile"),
("host" = Option<String>, Query,
description = "The host to generate a link with the magic token for"),
),
responses(
(status = 200, description = "The magic token", body = Value)
)
)
)]
#[route("/magic-token", method = "POST")]
pub async fn create_magic_token_endpoint(
query: web::Query<CreateMagicTokenQuery>,
tunnel_info: TunnelInfo,
db: ConfigDatabase,
_: NonTunnelRequestAuthorized,
) -> Result<Json<Value>> {
let token = create_magic_token(&db, tunnel_info.host.as_ref().clone())
.await
.map_err(|e| {
log::error!("Failed to create magic token: {e:?}");
ErrorInternalServerError("Failed to create magic token")
})?;
let mut query_string = form_urlencoded::Serializer::new(String::new());
query_string.append_pair("magicToken", &token);
if let Some(tunnel_host) = &*tunnel_info.host {
query_string.append_pair("apiUrl", tunnel_host);
}
let query_string = query_string.finish();
query.host.as_ref().map_or_else(
|| {
Ok(Json(json!({
"token": token,
})))
},
|host| {
Ok(Json(json!({
"token": token,
"url": format!("{host}?{query_string}")
})))
},
)
}