rullst-connect 6.1.6

OAuth2 Social Login for Rust web frameworks.
Documentation
//! A local Mock Identity Provider for End-to-End Testing.
//!
//! Exposes an Axum router that perfectly simulates an OAuth2 / OIDC provider.
//! This allows developers to test their integration locally without internet access
//! or dealing with strict real-world rate limits and domain validation.

#[cfg(feature = "axum")]
use axum::{
    Json, Router,
    extract::{Form, Query},
    response::{IntoResponse, Redirect},
    routing::{get, post},
};
#[cfg(feature = "axum")]
use base64::Engine;
#[cfg(feature = "axum")]
use serde::Deserialize;
#[cfg(feature = "axum")]
use serde_json::json;

#[cfg(feature = "axum")]
#[derive(Deserialize)]
pub struct AuthQuery {
    pub client_id: String,
    pub redirect_uri: String,
    pub response_type: String,
    pub scope: Option<String>,
    pub state: Option<String>,
}

#[cfg(feature = "axum")]
#[derive(Deserialize)]
pub struct TokenForm {
    pub client_id: String,
    pub client_secret: String,
    pub code: String,
    pub grant_type: String,
    pub redirect_uri: String,
}

#[cfg(feature = "axum")]
/// Returns an `axum::Router` configured as a fake OAuth provider.
///
/// Endpoints:
/// - `GET /auth`: Redirects back with a static code.
/// - `POST /token`: Exchanges the code for fake access/id tokens.
/// - `GET /userinfo`: Returns a static fake user profile.
/// - `GET /.well-known/openid-configuration`: Returns the fake discovery document.
pub fn mock_router() -> Router {
    Router::new()
        .route("/auth", get(authorize_handler))
        .route("/token", post(token_handler))
        .route("/userinfo", get(userinfo_handler))
        .route("/.well-known/openid-configuration", get(discovery_handler))
}

#[cfg(feature = "axum")]
async fn authorize_handler(Query(params): Query<AuthQuery>) -> impl IntoResponse {
    let mut redirect = format!("{}?code=mock_auth_code_12345", params.redirect_uri);
    if let Some(state) = params.state {
        redirect = format!("{}&state={}", redirect, state);
    }
    Redirect::temporary(&redirect)
}

#[cfg(feature = "axum")]
async fn token_handler(Form(form): Form<TokenForm>) -> impl IntoResponse {
    if form.code != "mock_auth_code_12345" {
        return Json(json!({
            "error": "invalid_grant",
            "error_description": "The provided authorization code is invalid."
        }));
    }

    // A fake JWT for the id_token
    let header = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"{\"alg\":\"none\"}");
    let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
        b"{\"sub\":\"mock_user_999\",\"name\":\"Mock User\",\"email\":\"mock@example.com\",\"email_verified\":true}"
    );
    let id_token = format!("{}.{}.", header, payload);

    Json(json!({
        "access_token": "mock_access_token_abcde",
        "token_type": "Bearer",
        "expires_in": 3600,
        "refresh_token": "mock_refresh_token_fghij",
        "id_token": id_token
    }))
}

#[cfg(feature = "axum")]
async fn userinfo_handler() -> impl IntoResponse {
    Json(json!({
        "sub": "mock_user_999",
        "name": "Mock User",
        "email": "mock@example.com",
        "email_verified": true,
        "picture": "https://mock.provider/avatar.png"
    }))
}

#[cfg(feature = "axum")]
async fn discovery_handler() -> impl IntoResponse {
    Json(json!({
        "issuer": "http://localhost:8080",
        "authorization_endpoint": "http://localhost:8080/auth",
        "token_endpoint": "http://localhost:8080/token",
        "userinfo_endpoint": "http://localhost:8080/userinfo",
        "jwks_uri": "http://localhost:8080/jwks",
        "response_types_supported": ["code"],
        "subject_types_supported": ["public"],
        "id_token_signing_alg_values_supported": ["RS256", "none"]
    }))
}