Skip to main content

alibi_plugins/email_otp/
endpoint.rs

1use super::{EmailOtpPlugin, EmailOtpType};
2use crate::authentication_helpers::{JsonField, JsonFieldKind};
3use crate::endpoint::{definition, validate_fields};
4use alibi_core::endpoint::{
5    EndpointCall, EndpointDefinition, EndpointInput, EndpointResponse, ServerEndpoint,
6};
7use alibi_core::utils::json::JsValue;
8use alibi_core::{AuthContext, AuthResult, AuthSchema, HttpMethod};
9use serde::Deserialize;
10
11#[derive(Debug, Deserialize)]
12pub struct EmailOtpRead {
13    pub otp: Option<String>,
14}
15
16const FIELDS: &[JsonField] = &[
17    JsonField::string("email", true),
18    JsonField {
19        name: "type",
20        kind: JsonFieldKind::OneOf(&[
21            "email-verification",
22            "sign-in",
23            "forget-password",
24            "change-email",
25        ]),
26        required: true,
27    },
28];
29
30pub(super) fn definitions() -> Vec<EndpointDefinition> {
31    vec![
32        definition(
33            "createVerificationOTP",
34            "createEmailVerificationOTP",
35            None,
36            HttpMethod::Post,
37        ),
38        definition(
39            "getVerificationOTP",
40            "getEmailVerificationOTP",
41            None,
42            HttpMethod::Get,
43        ),
44    ]
45}
46
47pub(super) fn validate(call: &EndpointCall) -> AuthResult<EndpointInput> {
48    if call.operation_id() == "createEmailVerificationOTP" {
49        Ok(EndpointInput {
50            body: Some(validate_fields(call.body(), "body", FIELDS)?),
51            query: call.query().cloned(),
52        })
53    } else {
54        Ok(EndpointInput {
55            body: call.body().cloned(),
56            query: Some(validate_fields(call.query(), "query", FIELDS)?),
57        })
58    }
59}
60
61impl EmailOtpPlugin {
62    /// Create a code through the installed endpoint hooks and post-hook validation.
63    #[must_use]
64    pub fn create_verification_otp_endpoint(
65        email: impl Into<String>,
66        otp_type: EmailOtpType,
67    ) -> ServerEndpoint<String> {
68        ServerEndpoint::new("email-otp", "createVerificationOTP")
69            .with_body_value(input(email.into(), otp_type))
70    }
71
72    /// Retrieve a code through the installed endpoint hooks.
73    #[must_use]
74    pub fn get_verification_otp_endpoint(
75        email: impl Into<String>,
76        otp_type: EmailOtpType,
77    ) -> ServerEndpoint<EmailOtpRead> {
78        ServerEndpoint::new("email-otp", "getVerificationOTP")
79            .with_query_value(input(email.into(), otp_type))
80    }
81
82    pub(super) async fn call_endpoint(
83        &self,
84        call: &EndpointCall,
85        ctx: &AuthContext<impl AuthSchema>,
86    ) -> AuthResult<EndpointResponse> {
87        let (email, otp_type) = if call.operation_id() == "createEmailVerificationOTP" {
88            let body: super::types::SendRequest = call.body_as()?;
89            (body.email, body.otp_type)
90        } else {
91            let query: super::types::SendRequest = call.query_as()?;
92            (query.email, query.otp_type)
93        };
94        if call.operation_id() == "createEmailVerificationOTP" {
95            EndpointResponse::json(&self.create_verification_otp(ctx, &email, otp_type).await?)
96        } else {
97            EndpointResponse::json(
98                &serde_json::json!({"otp": self.get_verification_otp(ctx, &email, otp_type).await?}),
99            )
100        }
101    }
102}
103
104fn input(email: String, otp_type: EmailOtpType) -> JsValue {
105    JsValue::Object(
106        [
107            ("email".into(), JsValue::String(email)),
108            ("type".into(), JsValue::String(otp_type.as_str().into())),
109        ]
110        .into_iter()
111        .collect(),
112    )
113}