Menu
AkurAI-Build
publicLatest change 30e6b8c347048d64c2c048dfd05a8ded219d37f6 - Host authenticated Git repositories over Smart HTTP with repo host/sync CLI by Ólafur Búi Ólafsson
use std::env;
use serde::Deserialize;
#[derive(Clone, Debug)]
pub struct IdpConfig {
pub issuer: String,
pub internal_url: String,
pub client_id: String,
pub client_secret: String,
pub redirect_uri: String,
pub admin_emails: Vec<String>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct UserInfo {
pub sub: String,
pub email: String,
}
impl IdpConfig {
pub fn from_env(public_origin: &str) -> Self {
let issuer = env::var("AKURAI_BUILD_IDP_ISSUER")
.unwrap_or_else(|_| "https://auth.olibuijr.com".into());
let internal_url =
env::var("AKURAI_BUILD_IDP_INTERNAL_URL").unwrap_or_else(|_| issuer.clone());
Self {
internal_url: if internal_url.starts_with("http://127.0.0.1") {
issuer.clone()
} else {
internal_url
},
issuer,
client_id: env::var("AKURAI_BUILD_IDP_CLIENT_ID").unwrap_or_default(),
client_secret: env::var("AKURAI_BUILD_IDP_CLIENT_SECRET").unwrap_or_default(),
redirect_uri: env::var("AKURAI_BUILD_IDP_REDIRECT")
.unwrap_or_else(|_| format!("{public_origin}/auth/callback")),
admin_emails: env::var("AKURAI_BUILD_ADMIN_EMAILS")
.unwrap_or_default()
.split(',')
.map(str::trim)
.filter(|email| !email.is_empty())
.map(str::to_owned)
.collect(),
}
}
pub fn authorize_url(&self, state: &str) -> String {
format!(
"{}/authorize?client_id={}&redirect_uri={}&response_type=code&scope=openid%20profile%20email&state={}",
self.issuer,
encode(&self.client_id),
encode(&self.redirect_uri),
encode(state),
)
}
pub fn token_url(&self) -> String {
format!("{}/token", self.internal_url.trim_end_matches('/'))
}
pub fn userinfo_url(&self) -> String {
format!("{}/userinfo", self.internal_url.trim_end_matches('/'))
}
}
pub fn encode(value: &str) -> String {
value
.bytes()
.flat_map(|byte| match byte {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => {
vec![byte as char]
}
_ => format!("%{byte:02X}").chars().collect(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn authorize_url_escapes_callback_and_state() {
let config = IdpConfig {
issuer: "https://auth.example".into(),
internal_url: String::new(),
client_id: "build client".into(),
client_secret: String::new(),
redirect_uri: "https://build.example/auth/callback".into(),
admin_emails: vec![],
};
let url = config.authorize_url("a b");
assert!(url.contains("client_id=build%20client"));
assert!(url.contains("state=a%20b"));
}
}