AkurAI Build
Menu

Public repository / 65d9e4103c7a6a1601ba4b287022e495bfac7f18

AkurAI-IDP / src/routes/well_known.rs

Latest change fce146c19e4ee44321ed964db4111dca7d75cc8b - recover: MCP OAuth public clients, RFC 8414 metadata, /api/health (0.8.4) by Ólafur Búi Ólafsson

use axum::{Json, Router, routing::get};
use serde_json::{Value, json};

use crate::config;
use crate::db::with_db;
use crate::lib::jwt::export_public_key_jwk;

pub fn router() -> Router {
    Router::new()
        .route(
            "/.well-known/openid-configuration",
            get(openid_configuration),
        )
        // RFC 8414 metadata: MCP clients discover the authorization server here.
        .route(
            "/.well-known/oauth-authorization-server",
            get(openid_configuration),
        )
        .route("/jwks", get(jwks))
}

async fn openid_configuration() -> Json<Value> {
    Json(discovery_document(&config::get().base_url))
}

fn discovery_document(base: &str) -> Value {
    json!({
        "issuer": base,
        "authorization_endpoint": format!("{base}/authorize"),
        "token_endpoint": format!("{base}/token"),
        "userinfo_endpoint": format!("{base}/userinfo"),
        "jwks_uri": format!("{base}/jwks"),
        "introspection_endpoint": format!("{base}/introspect"),
        "revocation_endpoint": format!("{base}/revoke"),
        "response_types_supported": ["code"],
        "grant_types_supported": ["authorization_code", "refresh_token", "client_credentials"],
        "subject_types_supported": ["public"],
        "id_token_signing_alg_values_supported": ["EdDSA"],
        "token_endpoint_auth_methods_supported": ["client_secret_basic", "client_secret_post", "none"],
        "scopes_supported": [
            "openid", "profile", "email", "groups", "offline_access",
            "mail.read", "mail.draft", "mail.send"
        ],
        "claims_supported": ["sub", "iss", "aud", "exp", "iat", "email", "email_verified", "groups", "tenant_id"],
        "code_challenge_methods_supported": ["S256"],
    })
}

async fn jwks() -> Json<Value> {
    let keys: Vec<Value> = with_db(|conn| {
        let mut stmt = conn
            .prepare("SELECT kid, public_key, alg FROM signing_keys WHERE active = 1")
            .expect("failed to prepare signing_keys query");
        stmt.query_map([], |row| {
            let kid: String = row.get(0)?;
            let public_key: String = row.get(1)?;
            let alg: String = row.get(2)?;
            Ok((kid, public_key, alg))
        })
        .expect("failed to query signing_keys")
        .filter_map(|r| r.ok())
        .filter_map(|(kid, public_key, _alg)| export_public_key_jwk(&public_key, &kid).ok())
        .collect()
    });

    Json(json!({ "keys": keys }))
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn discovery_advertises_public_clients_and_mail_scopes() {
        let doc = discovery_document("https://auth.example");
        assert_eq!(doc["issuer"], "https://auth.example");
        let methods = doc["token_endpoint_auth_methods_supported"]
            .as_array()
            .unwrap();
        assert!(methods.iter().any(|m| m == "none"));
        let scopes = doc["scopes_supported"].as_array().unwrap();
        for scope in ["mail.read", "mail.draft", "mail.send"] {
            assert!(scopes.iter().any(|s| s == scope), "{scope}");
        }
        assert_eq!(doc["code_challenge_methods_supported"], json!(["S256"]));
    }
}