oauth 0.0.2

Universal OAuth 2.0 adapter for Rust web frameworks
Documentation
use std::time::SystemTime;

use uuid::Uuid;

use crate::config::OAuthConfig;
use crate::error::{OAuthError, Result};
use crate::token_store::TokenRecord;

use super::TokenRequest;
use super::TokenResponse;

/// Handle the client credentials grant flow.
pub fn issue_token(config: &OAuthConfig, request: &TokenRequest) -> Result<TokenResponse> {
    let client_id = request
        .client_id
        .as_deref()
        .ok_or(OAuthError::InvalidClient)?;
    let client_secret = request
        .client_secret
        .as_deref()
        .ok_or(OAuthError::InvalidClient)?;

    config.verify_client(client_id, client_secret)?;

    if let Some(scope) = &request.scope {
        if scope.trim().is_empty() {
            return Err(OAuthError::InvalidScope("scope must not be blank".into()));
        }
    }

    let access_token = Uuid::new_v4().to_string();
    let refresh_token = if config.refresh_tokens_enabled() {
        Some(Uuid::new_v4().to_string())
    } else {
        None
    };
    let expires_at = SystemTime::now()
        .checked_add(config.access_token_ttl())
        .ok_or_else(|| OAuthError::Internal("failed to compute access token expiry".into()))?;

    let record = TokenRecord::new(
        access_token.clone(),
        refresh_token.clone(),
        client_id.to_string(),
        request.scope.clone(),
        expires_at,
    );

    config.token_store().save(record)?;

    Ok(TokenResponse::bearer(
        access_token,
        config.access_token_ttl(),
        refresh_token,
        request.scope.clone(),
    ))
}