alibi-plugins 0.4.1

Built-in authentication plugins and API metadata for Alibi
Documentation
use super::types::{
    IsUsernameAvailableRequest, IsUsernameAvailableResponse, SignInCoreResult, SignInRequest,
    SignInUsernameFailure, SignInUsernameRequest, SignInUsernameResponse, SignUpRequest,
};
use super::{
    EmailPasswordPlugin, MESSAGE_EMAIL_NOT_VERIFIED, MESSAGE_INVALID_USERNAME_OR_PASSWORD,
    MESSAGE_USERNAME_IS_ALREADY_TAKEN, UsernameValidationOrder, append_dont_remember_cookie,
    create_session_cookie_for_remember_me, sign_in_core, sign_in_username_core, sign_up_core,
    username_error_response,
};
use crate::authentication_helpers::parse_body;
use alibi_core::{AuthContext, AuthRequest, AuthResponse, AuthResult, RequestMeta};
use std::sync::Arc;
impl EmailPasswordPlugin {
    pub(in crate::email_password) async fn handle_sign_up(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        let ignored = if self.config.enable_username {
            &[][..]
        } else {
            &["username", "displayUsername"][..]
        };
        let mut signup_req: SignUpRequest =
            match super::super::authentication_helpers::parse_body_with_ignored_fields(req, ignored)
            {
                Ok(value) => value,
                Err(response) => return Ok(response),
            };

        alibi_core::middleware::CsrfMiddleware::new(
            alibi_core::middleware::CsrfConfig::new(),
            Arc::clone(&ctx.config),
        )
        .check_form_origin(req)?;
        signup_req.email = signup_req.email.to_lowercase();
        if self.config.enable_username {
            let policy = &self.config.username;
            if signup_req.username.is_none()
                && let Some(display) = &signup_req.display_username
                && policy.value_error(display).await?.is_none()
            {
                signup_req.username = Some(display.clone());
            }
            if let Some(username) = &signup_req.username {
                policy.validate_hook_value(username).await?;
                if ctx
                    .database
                    .get_user_by_username(&policy.normalize(username)?)
                    .await?
                    .is_some()
                {
                    return username_error_response(
                        400,
                        "USERNAME_IS_ALREADY_TAKEN",
                        MESSAGE_USERNAME_IS_ALREADY_TAKEN,
                    );
                }
            }
            if let Some(display) = &signup_req.display_username {
                policy.validate_display(display).await?;
            }
            if policy.include_display_username
                && signup_req
                    .display_username
                    .as_ref()
                    .is_none_or(String::is_empty)
            {
                signup_req.display_username.clone_from(&signup_req.username);
            }
            if !policy.include_display_username {
                signup_req.display_username = None;
            }
            let mut callback_body = req.body_as_json::<alibi_core::utils::json::JsValue>()?;
            if let alibi_core::utils::json::JsValue::Object(body) = &mut callback_body {
                if let Some(value) = &signup_req.username {
                    _ = body.insert(
                        "username".into(),
                        alibi_core::utils::json::JsValue::String(value.clone()),
                    );
                }
                if let Some(value) = &signup_req.display_username {
                    _ = body.insert(
                        "displayUsername".into(),
                        alibi_core::utils::json::JsValue::String(value.clone()),
                    );
                }
            }
            req.extensions()
                .insert(alibi_core::hooks::TransformedRequestBody(callback_body));
        }

        alibi_core::session::cookie_cache::runtime::set_issuance_preference(
            req,
            signup_req.remember_me == Some(false),
        );
        let meta = RequestMeta::from_request(req);
        let (response, cookies) = sign_up_core(req, &signup_req, &self.config, &meta, ctx).await?;
        let mut response = AuthResponse::json(200, &response)?;
        for cookie in cookies.into_iter().flatten() {
            response.headers.append("Set-Cookie", cookie);
        }
        Ok(response)
    }

    pub(in crate::email_password) async fn handle_sign_in(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        let signin_req: SignInRequest = match parse_body(req) {
            Ok(v) => v,
            Err(resp) => return Ok(resp),
        };

        let mut callback_body: alibi_core::utils::json::JsValue = req.body_as_json()?;
        if let alibi_core::utils::json::JsValue::Object(body) = &mut callback_body {
            _ = body
                .entry("rememberMe".into())
                .or_insert(alibi_core::utils::json::JsValue::Bool(true));
        }
        req.extensions()
            .insert(alibi_core::hooks::ValidatedRequestBody(callback_body));
        alibi_core::middleware::CsrfMiddleware::new(
            alibi_core::middleware::CsrfConfig::new(),
            Arc::clone(&ctx.config),
        )
        .check_form_origin(req)?;

        alibi_core::session::cookie_cache::runtime::set_issuance_preference(
            req,
            signin_req.remember_me == Some(false),
        );
        let meta = RequestMeta::from_request(req);
        match sign_in_core(
            req,
            &signin_req,
            &self.config,
            self.email_verification.as_deref(),
            &meta,
            ctx,
        )
        .await?
        {
            SignInCoreResult::Success {
                response,
                token,
                set_cookie_headers,
            } => {
                let mut auth_response = AuthResponse::json(200, &response)?.with_appended_header(
                    "Set-Cookie",
                    create_session_cookie_for_remember_me(
                        &token,
                        signin_req.remember_me,
                        &ctx.config,
                    )?,
                );
                if let Some(url) = signin_req
                    .callback_url
                    .as_deref()
                    .filter(|url| !url.is_empty())
                {
                    auth_response = auth_response.with_header("Location", url);
                }
                for cookie in set_cookie_headers {
                    auth_response = auth_response.with_appended_header("Set-Cookie", cookie);
                }
                Ok(append_dont_remember_cookie(
                    auth_response,
                    signin_req.remember_me,
                    &ctx.config,
                )?)
            }
            SignInCoreResult::TwoFactorRedirect {
                response,
                set_cookie_headers,
            } => {
                let mut auth_response = AuthResponse::json(200, &response)?;
                for cookie in set_cookie_headers {
                    auth_response = auth_response.with_appended_header("Set-Cookie", cookie);
                }
                Ok(auth_response)
            }
        }
    }

    pub(in crate::email_password) async fn handle_sign_in_username(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        let signin_req: SignInUsernameRequest = match alibi_core::validate_request_body(req) {
            Ok(v) => v,
            Err(resp) => return Ok(resp),
        };

        if signin_req.username.is_empty() || signin_req.password.is_empty() {
            return username_error_response(
                401,
                "INVALID_USERNAME_OR_PASSWORD",
                MESSAGE_INVALID_USERNAME_OR_PASSWORD,
            );
        }

        let policy = &self.config.username;
        let validation_input =
            if policy.validation_order == Some(UsernameValidationOrder::PreNormalization) {
                policy.normalize(&signin_req.username)?
            } else {
                signin_req.username.clone()
            };
        if let Err(error) = policy.validate_value(&validation_input, 422).await {
            return Ok(error.to_auth_response());
        }
        let username = policy.normalize(&validation_input)?;

        let meta = RequestMeta::from_request(req);
        match sign_in_username_core(
            req,
            &signin_req,
            &username,
            &self.config,
            self.email_verification.as_deref(),
            &meta,
            ctx,
        )
        .await
        {
            Ok(SignInCoreResult::Success {
                response,
                token,
                set_cookie_headers,
            }) => {
                let username_response = SignInUsernameResponse {
                    redirect: response.redirect,
                    token: response.token,
                    url: response.url,
                    user: response.user,
                };
                let mut auth_response = AuthResponse::json(200, &username_response)?
                    .with_appended_header(
                        "Set-Cookie",
                        create_session_cookie_for_remember_me(
                            &token,
                            signin_req.remember_me,
                            &ctx.config,
                        )?,
                    );
                if let Some(url) = signin_req
                    .callback_url
                    .as_deref()
                    .filter(|url| !url.is_empty())
                {
                    auth_response = auth_response.with_header("Location", url);
                }
                for cookie in set_cookie_headers {
                    auth_response = auth_response.with_appended_header("Set-Cookie", cookie);
                }
                Ok(append_dont_remember_cookie(
                    auth_response,
                    signin_req.remember_me,
                    &ctx.config,
                )?)
            }
            Ok(SignInCoreResult::TwoFactorRedirect {
                response,
                set_cookie_headers,
            }) => {
                let mut auth_response = AuthResponse::json(200, &response)?;
                for cookie in set_cookie_headers {
                    auth_response = auth_response.with_appended_header("Set-Cookie", cookie);
                }
                Ok(auth_response)
            }
            Err(SignInUsernameFailure::InvalidUsernameOrPassword) => username_error_response(
                401,
                "INVALID_USERNAME_OR_PASSWORD",
                MESSAGE_INVALID_USERNAME_OR_PASSWORD,
            ),
            Err(SignInUsernameFailure::EmailNotVerified) => {
                username_error_response(403, "EMAIL_NOT_VERIFIED", MESSAGE_EMAIL_NOT_VERIFIED)
            }
            Err(SignInUsernameFailure::Auth(error)) => Err(error),
        }
    }

    pub(in crate::email_password) async fn handle_is_username_available(
        &self,
        req: &AuthRequest,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        let body: IsUsernameAvailableRequest = match alibi_core::validate_request_body(req) {
            Ok(v) => v,
            Err(resp) => return Ok(resp),
        };

        if body.username.is_empty() {
            return username_error_response(422, "INVALID_USERNAME", "Username is invalid");
        }

        if let Err(error) = self
            .config
            .username
            .validate_value(&body.username, 422)
            .await
        {
            return Ok(error.to_auth_response());
        }
        let normalized = self.config.username.normalize(&body.username)?;
        let user = ctx.database.get_user_by_username(&normalized).await?;
        let available = user.is_none();

        Ok(AuthResponse::json(
            200,
            &IsUsernameAvailableResponse { available },
        )?)
    }
}