use axum::{Json, extract::State};
use serde_json::{Value, json};
use crate::ctx::AuthCtx;
pub fn build_jwks(public_jwks: Vec<Value>) -> Value {
json!({ "keys": public_jwks })
}
pub async fn jwks_handler(State(ctx): State<AuthCtx>) -> Json<Value> {
let keys = match &ctx.oidc_provider {
Some(p) => load_public_jwks(p).await.unwrap_or_default(),
None => Vec::new(),
};
Json(build_jwks(keys))
}
pub async fn load_public_jwks(provider: &super::OidcProviderConfig) -> anyhow::Result<Vec<Value>> {
match &provider.jwks_source {
#[cfg(feature = "backend-postgres")]
super::JwksSource::Postgres(pool) => load_pg(pool).await,
#[cfg(feature = "backend-sqlite")]
super::JwksSource::Sqlite(pool) => load_sqlite(pool).await,
super::JwksSource::Memory(values) => Ok(values.clone()),
}
}
#[cfg(feature = "backend-postgres")]
async fn load_pg(pool: &sqlx::PgPool) -> anyhow::Result<Vec<Value>> {
use sqlx::Row;
let rows = sqlx::query(
"SELECT public_jwk FROM auth.jwks_keys
ORDER BY (rotated_at IS NULL) DESC, created_at",
)
.fetch_all(pool)
.await?;
let mut out = Vec::with_capacity(rows.len());
for row in rows {
let v: Value = row.get("public_jwk");
out.push(v);
}
Ok(out)
}
#[cfg(feature = "backend-sqlite")]
async fn load_sqlite(pool: &sqlx::SqlitePool) -> anyhow::Result<Vec<Value>> {
use sqlx::Row;
let rows = sqlx::query(
"SELECT public_jwk FROM auth.jwks_keys
ORDER BY (rotated_at IS NULL) DESC, created_at",
)
.fetch_all(pool)
.await?;
let mut out = Vec::with_capacity(rows.len());
for row in rows {
let s: String = row.get("public_jwk");
let v: Value = serde_json::from_str(&s).unwrap_or_else(|_| json!({}));
out.push(v);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_jwks_returns_keys_array() {
let k1 = json!({"kty": "OKP", "crv": "Ed25519", "kid": "kid_a"});
let k2 = json!({"kty": "OKP", "crv": "Ed25519", "kid": "kid_b"});
let set = build_jwks(vec![k1.clone(), k2.clone()]);
assert_eq!(set["keys"], json!([k1, k2]));
}
#[test]
fn build_jwks_empty_is_well_formed() {
let set = build_jwks(Vec::new());
assert_eq!(set, json!({"keys": []}));
}
}