rs-auth-axum 0.1.2

Axum integration for rs-auth.
Documentation
use axum_extra::extract::SignedCookieJar;
use axum_lib::{
    Json,
    extract::State,
    http::{HeaderMap, StatusCode},
};
use rs_auth_core::types::{NewUser, PublicUser};
use serde::{Deserialize, Serialize};

use crate::cookie::set_session_cookie;
use crate::error::ApiError;
use crate::state::AuthState;

#[derive(Debug, Deserialize)]
pub struct SignupRequest {
    pub email: String,
    pub password: String,
    pub name: Option<String>,
}

#[derive(Debug, Serialize)]
pub struct SignupResponse {
    pub user: PublicUser,
}

pub async fn signup<U, S, V, A, O, E>(
    State(state): State<AuthState<U, S, V, A, O, E>>,
    jar: SignedCookieJar,
    headers: HeaderMap,
    Json(payload): Json<SignupRequest>,
) -> Result<(SignedCookieJar, (StatusCode, Json<SignupResponse>)), ApiError>
where
    U: rs_auth_core::store::UserStore + Send + Sync + 'static,
    S: rs_auth_core::store::SessionStore + Send + Sync + 'static,
    V: rs_auth_core::store::VerificationStore + Send + Sync + 'static,
    A: rs_auth_core::store::AccountStore + Send + Sync + 'static,
    O: rs_auth_core::store::OAuthStateStore + Send + Sync + 'static,
    E: rs_auth_core::email::EmailSender + Send + Sync + 'static,
{
    let result = state
        .service
        .signup(
            NewUser {
                email: payload.email,
                name: payload.name,
                password: payload.password,
            },
            forwarded_ip(&headers),
            user_agent(&headers),
        )
        .await?;

    let jar = match result.session_token.as_deref() {
        Some(token) => {
            set_session_cookie(jar, token, &state.config.cookie, state.config.session_ttl)
        }
        None => jar,
    };

    Ok((
        jar,
        (
            StatusCode::CREATED,
            Json(SignupResponse {
                user: PublicUser::from(result.user),
            }),
        ),
    ))
}

pub(crate) fn forwarded_ip(headers: &HeaderMap) -> Option<String> {
    headers
        .get("x-forwarded-for")
        .or_else(|| headers.get("x-real-ip"))
        .and_then(|value| value.to_str().ok())
        .map(|value| value.split(',').next().unwrap_or(value).trim().to_string())
}

pub(crate) fn user_agent(headers: &HeaderMap) -> Option<String> {
    headers
        .get(axum_lib::http::header::USER_AGENT)
        .and_then(|value| value.to_str().ok())
        .map(str::to_owned)
}