openauth-plugins 0.0.3

Official OpenAuth plugin modules.
Documentation
use http::StatusCode;
use openauth_core::api::{ApiErrorResponse, ApiResponse};
use openauth_core::error::OpenAuthError;
use openauth_core::plugin::PluginErrorCode;

pub const PHONE_NUMBER_ERROR_CODES: &[(&str, &str)] = &[
    ("INVALID_PHONE_NUMBER", "Invalid phone number"),
    ("PHONE_NUMBER_EXIST", "Phone number already exists"),
    ("PHONE_NUMBER_NOT_EXIST", "phone number isn't registered"),
    (
        "INVALID_PHONE_NUMBER_OR_PASSWORD",
        "Invalid phone number or password",
    ),
    ("UNEXPECTED_ERROR", "Unexpected error"),
    ("OTP_NOT_FOUND", "OTP not found"),
    ("OTP_EXPIRED", "OTP expired"),
    ("INVALID_OTP", "Invalid OTP"),
    ("PHONE_NUMBER_NOT_VERIFIED", "Phone number not verified"),
    (
        "PHONE_NUMBER_CANNOT_BE_UPDATED",
        "Phone number cannot be updated",
    ),
    ("SEND_OTP_NOT_IMPLEMENTED", "sendOTP not implemented"),
    ("TOO_MANY_ATTEMPTS", "Too many attempts"),
];

macro_rules! error_code_fn {
    ($name:ident, $code:literal, $message:literal) => {
        pub fn $name() -> PluginErrorCode {
            PluginErrorCode::new($code, $message)
        }
    };
}

error_code_fn!(
    invalid_phone_number,
    "INVALID_PHONE_NUMBER",
    "Invalid phone number"
);
error_code_fn!(
    phone_number_exists,
    "PHONE_NUMBER_EXIST",
    "Phone number already exists"
);
error_code_fn!(
    phone_number_not_exists,
    "PHONE_NUMBER_NOT_EXIST",
    "phone number isn't registered"
);
error_code_fn!(
    invalid_phone_number_or_password,
    "INVALID_PHONE_NUMBER_OR_PASSWORD",
    "Invalid phone number or password"
);
error_code_fn!(unexpected_error, "UNEXPECTED_ERROR", "Unexpected error");
error_code_fn!(otp_not_found, "OTP_NOT_FOUND", "OTP not found");
error_code_fn!(otp_expired, "OTP_EXPIRED", "OTP expired");
error_code_fn!(invalid_otp, "INVALID_OTP", "Invalid OTP");
error_code_fn!(
    phone_number_not_verified,
    "PHONE_NUMBER_NOT_VERIFIED",
    "Phone number not verified"
);
error_code_fn!(
    phone_number_cannot_be_updated,
    "PHONE_NUMBER_CANNOT_BE_UPDATED",
    "Phone number cannot be updated"
);
error_code_fn!(
    send_otp_not_implemented,
    "SEND_OTP_NOT_IMPLEMENTED",
    "sendOTP not implemented"
);
error_code_fn!(too_many_attempts, "TOO_MANY_ATTEMPTS", "Too many attempts");

pub(crate) fn error_response(
    status: StatusCode,
    error: PluginErrorCode,
) -> Result<ApiResponse, OpenAuthError> {
    json_response(
        status,
        &ApiErrorResponse {
            code: error.code,
            message: error.message,
            original_message: None,
        },
        Vec::new(),
    )
}

pub(crate) fn json_response<T>(
    status: StatusCode,
    body: &T,
    cookies: Vec<openauth_core::cookies::Cookie>,
) -> Result<ApiResponse, OpenAuthError>
where
    T: serde::Serialize,
{
    let body = serde_json::to_vec(body).map_err(|error| OpenAuthError::Api(error.to_string()))?;
    let mut response = http::Response::builder()
        .status(status)
        .header(http::header::CONTENT_TYPE, "application/json")
        .body(body)
        .map_err(|error| OpenAuthError::Api(error.to_string()))?;
    for cookie in cookies {
        response.headers_mut().append(
            http::header::SET_COOKIE,
            http::HeaderValue::from_str(&serialize_cookie(&cookie))
                .map_err(|error| OpenAuthError::Cookie(error.to_string()))?,
        );
    }
    Ok(response)
}

fn serialize_cookie(cookie: &openauth_core::cookies::Cookie) -> String {
    let mut value = format!("{}={}", cookie.name, cookie.value);
    if let Some(max_age) = cookie.attributes.max_age {
        value.push_str(&format!("; Max-Age={max_age}"));
    }
    if let Some(domain) = &cookie.attributes.domain {
        value.push_str(&format!("; Domain={domain}"));
    }
    if let Some(path) = &cookie.attributes.path {
        value.push_str(&format!("; Path={path}"));
    }
    if cookie.attributes.http_only.unwrap_or(false) {
        value.push_str("; HttpOnly");
    }
    if cookie.attributes.secure.unwrap_or(false) {
        value.push_str("; Secure");
    }
    if let Some(same_site) = &cookie.attributes.same_site {
        value.push_str("; SameSite=");
        value.push_str(same_site);
    }
    value
}