oauth 0.0.2

Universal OAuth 2.0 adapter for Rust web frameworks
Documentation
use std::sync::Arc;

use rocket::fairing::AdHoc;
use rocket::http::Status;
use rocket::serde::json::Json;
use serde::Serialize;

use crate::OAuthConfig;
use crate::error::OAuthError;
use crate::grants::{self, TokenRequest, TokenResponse};

/// Fairing that injects the shared [`OAuthConfig`] into Rocket state.
pub fn manage(config: Arc<OAuthConfig>) -> AdHoc {
    AdHoc::on_ignite("OAuth configuration", |rocket| async move {
        rocket.manage(config)
    })
}

/// Routes exposing the OAuth token endpoint.
pub fn oauth_routes() -> Vec<rocket::Route> {
    rocket::routes![token_endpoint]
}

#[rocket::post("/oauth/token", format = "json", data = "<request>")]
async fn token_endpoint(
    config: &rocket::State<Arc<OAuthConfig>>,
    request: Json<TokenRequest>,
) -> Result<Json<TokenResponse>, (Status, Json<ErrorBody>)> {
    match grants::issue_token(config.inner().as_ref(), request.into_inner()) {
        Ok(token) => Ok(Json(token)),
        Err(err) => {
            let status = Status::from_code(err.status_code().as_u16())
                .unwrap_or(Status::InternalServerError);
            Err((status, Json(ErrorBody::from(&err))))
        }
    }
}

#[derive(Serialize)]
struct ErrorBody {
    error: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    error_description: Option<String>,
}

impl From<&OAuthError> for ErrorBody {
    fn from(err: &OAuthError) -> Self {
        let error = match err {
            OAuthError::InvalidClient => "invalid_client",
            OAuthError::UnsupportedGrant(_) | OAuthError::NotImplemented(_) => {
                "unsupported_grant_type"
            }
            OAuthError::InvalidGrant(_) => "invalid_grant",
            OAuthError::InvalidScope(_) => "invalid_scope",
            OAuthError::Config(_) | OAuthError::TokenStore(_) | OAuthError::Internal(_) => {
                "server_error"
            }
        }
        .to_string();

        let error_description = match err {
            OAuthError::UnsupportedGrant(message)
            | OAuthError::InvalidGrant(message)
            | OAuthError::InvalidScope(message)
            | OAuthError::Internal(message) => Some(message.clone()),
            OAuthError::NotImplemented(message) => Some((*message).into()),
            _ => None,
        };

        Self {
            error,
            error_description,
        }
    }
}