rustauth-core 0.2.0

Core types and primitives for RustAuth.
Documentation
use http::{Method, StatusCode};
use serde::{Deserialize, Serialize};
use serde_json::json;

use super::shared::{auth_session_cookies, error_response, json_response, sensitive_session};
use crate::api::services::user as user_service;
use crate::api::services::user::{
    ChangeEmailError, ChangeEmailErrorOrRustAuth, ChangeEmailInput, ChangeEmailResult,
};
use crate::api::{
    create_auth_endpoint, parse_request_body, AsyncAuthEndpoint, AuthEndpointOptions, BodyField,
    BodySchema, JsonSchemaType, OpenApiOperation,
};
use serde_json::Value;

use crate::api::http_json::user_to_http_value;

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ChangeEmailBody {
    new_email: String,
    #[serde(default, rename = "callbackURL", alias = "callbackUrl")]
    callback_url: Option<String>,
}

#[derive(Debug, Serialize)]
struct ChangeEmailResponse {
    status: bool,
    message: &'static str,
    user: Option<Value>,
}

pub(super) fn change_email_endpoint() -> AsyncAuthEndpoint {
    create_auth_endpoint(
        "/change-email",
        Method::POST,
        AuthEndpointOptions::new()
            .operation_id("changeEmail")
            .body_schema(change_email_body_schema())
            .openapi(
                OpenApiOperation::new("changeEmail")
                    .description("Change the current user's email")
                    .response(
                        "200",
                        super::shared::json_openapi_response(
                            "Email change request processed successfully",
                            json!({
                                "type": "object",
                                "properties": {
                                    "status": { "type": "boolean" },
                                    "message": { "type": "string", "nullable": true },
                                    "user": {
                                        "oneOf": [
                                            { "$ref": "#/components/schemas/User" },
                                            { "type": "null" }
                                        ],
                                    },
                                },
                                "required": ["status"],
                            }),
                        ),
                    ),
            ),
        move |context, request| async move {
            let Some((session, user, _cookies)) = sensitive_session(&context, &request).await?
            else {
                return super::shared::unauthorized();
            };
            let dont_remember = super::shared::request_dont_remember(&context, &request)?;
            let body: ChangeEmailBody = parse_request_body(&request)?;
            let result = match user_service::change_email(
                &context,
                Some(&request),
                user,
                ChangeEmailInput {
                    new_email: body.new_email,
                    callback_url: body.callback_url,
                },
            )
            .await
            {
                Ok(result) => result,
                Err(error) => return change_email_error_response(error),
            };
            match result {
                ChangeEmailResult::Updated(updated) => {
                    let cookies =
                        auth_session_cookies(&context, &session, &updated, dont_remember)?;
                    json_response(
                        StatusCode::OK,
                        &ChangeEmailResponse {
                            status: true,
                            message: "Email updated",
                            user: Some(user_to_http_value(&updated)?),
                        },
                        cookies,
                    )
                }
                ChangeEmailResult::VerificationSent => json_response(
                    StatusCode::OK,
                    &ChangeEmailResponse {
                        status: true,
                        message: "Verification email sent",
                        user: None,
                    },
                    Vec::new(),
                ),
            }
        },
    )
}

fn change_email_error_response(
    error: ChangeEmailErrorOrRustAuth,
) -> Result<crate::api::ApiResponse, crate::error::RustAuthError> {
    match error {
        ChangeEmailErrorOrRustAuth::RustAuth(error) => Err(error),
        ChangeEmailErrorOrRustAuth::Service(error) => match error {
            ChangeEmailError::Disabled => error_response(
                StatusCode::BAD_REQUEST,
                "CHANGE_EMAIL_DISABLED",
                "Change email is disabled",
            ),
            ChangeEmailError::EmailIsSame => error_response(
                StatusCode::BAD_REQUEST,
                "EMAIL_IS_SAME",
                "Email is the same",
            ),
            ChangeEmailError::VerificationEmailNotEnabled => error_response(
                StatusCode::BAD_REQUEST,
                "VERIFICATION_EMAIL_NOT_ENABLED",
                "Verification email isn't enabled",
            ),
        },
    }
}

fn change_email_body_schema() -> BodySchema {
    BodySchema::object([
        BodyField::new("newEmail", JsonSchemaType::String)
            .description("The new email address to set"),
        BodyField::optional("callbackURL", JsonSchemaType::String)
            .description("The URL to redirect to after email verification"),
    ])
}