use super::{MagicLinkPlugin, MagicLinkSignInInput};
use crate::authentication_helpers::{JsonField, RequestBody};
use crate::endpoint::{definition, validate_fields, validation};
use alibi_core::endpoint::{
EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
};
use alibi_core::{AuthContext, AuthResult, AuthSchema, HttpMethod};
use serde::{Deserialize, Serialize};
#[derive(Debug, Deserialize)]
pub struct MagicLinkSent {
pub status: bool,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MagicLinkVerifyInput {
pub token: String,
#[serde(rename = "callbackURL", skip_serializing_if = "Option::is_none")]
pub callback_url: Option<String>,
#[serde(rename = "newUserCallbackURL", skip_serializing_if = "Option::is_none")]
pub new_user_callback_url: Option<String>,
#[serde(rename = "errorCallbackURL", skip_serializing_if = "Option::is_none")]
pub error_callback_url: Option<String>,
}
#[derive(Debug, Deserialize)]
pub struct MagicLinkVerified {
pub token: String,
pub user: Box<alibi_core::UserView>,
pub session: Box<alibi_core::wire::SessionView>,
}
const VERIFY_FIELDS: &[JsonField] = &[
JsonField::string("token", true),
JsonField::string("callbackURL", false),
JsonField::string("errorCallbackURL", false),
JsonField::string("newUserCallbackURL", false),
];
pub(super) fn definitions() -> Vec<EndpointDefinition> {
vec![
definition(
"signInMagicLink",
"signInWithMagicLink",
Some("/sign-in/magic-link"),
HttpMethod::Post,
),
definition(
"magicLinkVerify",
"verifyMagicLink",
Some("/magic-link/verify"),
HttpMethod::Get,
),
]
}
pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
let input = if call.operation_id() == "verifyMagicLink" {
EndpointInput {
body: call.body().cloned(),
query: Some(validate_fields(call.query(), "query", VERIFY_FIELDS)?),
}
} else {
EndpointInput {
body: Some(validate_fields(
call.body(),
"body",
MagicLinkSignInInput::FIELDS,
)?),
query: call.query().cloned(),
}
};
if call.headers().is_none() {
return Err(validation("Headers is required"));
}
Ok(input)
}
impl MagicLinkPlugin {
pub fn verify_endpoint(
query: &MagicLinkVerifyInput,
) -> AuthResult<ServerEndpoint<MagicLinkVerified>> {
ServerEndpoint::new("magic-link", "magicLinkVerify").with_query(query)
}
pub fn sign_in_endpoint(
input: &MagicLinkSignInInput,
) -> AuthResult<ServerEndpoint<MagicLinkSent>> {
ServerEndpoint::new("magic-link", "signInMagicLink").with_body(input)
}
pub(super) async fn call_endpoint(
&self,
call: &EndpointCall,
ctx: &AuthContext<impl AuthSchema>,
) -> AuthResult<EndpointResponse> {
let output = if call.operation_id() == "verifyMagicLink" {
self.verify_core(call, ctx, call.query_as()?).await?
} else {
if let Some(request) = call.request() {
alibi_core::middleware::CsrfMiddleware::new(
alibi_core::middleware::CsrfConfig::new(),
ctx.config.clone(),
)
.check_login_origin(request)?;
}
self.sign_in_core(call.request(), ctx, call.body_as()?)
.await?
};
if output.status == 302 {
let mut response = EndpointResponse::redirect(
output.headers.get("location").cloned().unwrap_or_default(),
);
response.merge_headers(output.headers);
return Ok(response);
}
let mut response = if output.status >= 400 {
EndpointResponse::error(crate::endpoint::error_response(output.clone()))
.without_error_body()
} else {
EndpointResponse::value(alibi_core::utils::json::from_slice(&output.body)?)
};
response.set_status(Some(output.status));
response.merge_headers(output.headers);
Ok(response)
}
}