Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/holger

mannequin/src/oidc/mod.rs

use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::sync::Mutex;
use uuid::Uuid;

/// Mock OIDC provider for testing authentication flows.
pub struct OidcService {
    issuer: String,
    tokens: Mutex<Vec<TokenRecord>>,
}

#[derive(Clone, Serialize)]
pub struct TokenRecord {
    pub access_token: String,
    pub subject: String,
    pub issued_at: i64,
    pub expires_at: i64,
}

#[derive(Serialize)]
pub struct OidcDiscovery {
    pub issuer: String,
    pub authorization_endpoint: String,
    pub token_endpoint: String,
    pub userinfo_endpoint: String,
    pub jwks_uri: String,
}

#[derive(Deserialize)]
pub struct TokenRequest {
    pub grant_type: Option<String>,
    pub username: Option<String>,
    pub password: Option<String>,
    pub client_id: Option<String>,
}

#[derive(Serialize)]
pub struct TokenResponse {
    pub access_token: String,
    pub token_type: String,
    pub expires_in: i64,
    pub sub: String,
}

impl OidcService {
    pub fn new(issuer: String) -> Self {
        Self {
            issuer,
            tokens: Mutex::new(Vec::new()),
        }
    }

    pub fn discovery(&self) -> OidcDiscovery {
        OidcDiscovery {
            issuer: self.issuer.clone(),
            authorization_endpoint: format!("{}/authorize", self.issuer),
            token_endpoint: format!("{}/token", self.issuer),
            userinfo_endpoint: format!("{}/userinfo", self.issuer),
            jwks_uri: format!("{}/jwks", self.issuer),
        }
    }

    /// Issue a token (accepts any credentials — it's a test dummy).
    pub fn issue_token(&self, subject: &str) -> TokenResponse {
        let access_token = Uuid::new_v4().to_string();
        let now = Utc::now().timestamp();
        let expires_in = 3600;

        self.tokens.lock().unwrap().push(TokenRecord {
            access_token: access_token.clone(),
            subject: subject.to_string(),
            issued_at: now,
            expires_at: now + expires_in,
        });

        TokenResponse {
            access_token,
            token_type: "Bearer".to_string(),
            expires_in,
            sub: subject.to_string(),
        }
    }

    /// Validate a token (returns subject if valid).
    pub fn validate_token(&self, token: &str) -> Option<String> {
        let now = Utc::now().timestamp();
        self.tokens
            .lock()
            .unwrap()
            .iter()
            .find(|t| t.access_token == token && t.expires_at > now)
            .map(|t| t.subject.clone())
    }
}