86 lines
2.4 KiB
Rust
86 lines
2.4 KiB
Rust
|
//! # OpenID service
|
||
|
|
||
|
use crate::app_config::{AppConfig, OIDCProvider};
|
||
|
use crate::connections::redis_connection;
|
||
|
use crate::constants::OPEN_ID_STATE_DURATION;
|
||
|
use crate::utils::string_utils;
|
||
|
use crate::utils::time_utils::time;
|
||
|
use light_openid::primitives::OpenIDConfig;
|
||
|
use std::cell::RefCell;
|
||
|
use std::collections::HashMap;
|
||
|
use std::io::ErrorKind;
|
||
|
use std::net::IpAddr;
|
||
|
|
||
|
thread_local! {
|
||
|
static CONFIG_CACHES: RefCell<HashMap<String, OpenIDConfig>> = RefCell::new(Default::default());
|
||
|
|
||
|
}
|
||
|
|
||
|
struct OpenIDClient<'a> {
|
||
|
prov: OIDCProvider<'a>,
|
||
|
conf: OpenIDConfig,
|
||
|
}
|
||
|
|
||
|
#[derive(serde::Serialize, serde::Deserialize)]
|
||
|
struct OpenIDState {
|
||
|
#[serde(rename = "i")]
|
||
|
ip: IpAddr,
|
||
|
#[serde(rename = "e")]
|
||
|
expire: u64,
|
||
|
#[serde(rename = "p")]
|
||
|
prov_id: String,
|
||
|
}
|
||
|
|
||
|
impl OpenIDState {
|
||
|
pub fn new(ip: IpAddr, client: &OpenIDClient) -> (String, Self) {
|
||
|
(
|
||
|
string_utils::rand_str(30),
|
||
|
Self {
|
||
|
ip,
|
||
|
expire: time() + OPEN_ID_STATE_DURATION.as_secs(),
|
||
|
prov_id: client.prov.id.to_string(),
|
||
|
},
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn redis_key(state: &str) -> String {
|
||
|
format!("oidc-state-{state}")
|
||
|
}
|
||
|
|
||
|
async fn load_provider_info(prov_id: &str) -> anyhow::Result<OpenIDClient> {
|
||
|
let prov = AppConfig::get()
|
||
|
.openid_providers()
|
||
|
.into_iter()
|
||
|
.find(|p| p.id.eq(prov_id))
|
||
|
.ok_or_else(|| std::io::Error::new(ErrorKind::Other, "Provider not found!"))?;
|
||
|
|
||
|
if let Some(conf) = CONFIG_CACHES.with(|i| i.borrow().get(prov_id).cloned()) {
|
||
|
return Ok(OpenIDClient { prov, conf });
|
||
|
}
|
||
|
|
||
|
let conf = OpenIDConfig::load_from_url(prov.configuration_url)
|
||
|
.await
|
||
|
.map_err(|e| std::io::Error::new(ErrorKind::Other, e.to_string()))?;
|
||
|
|
||
|
CONFIG_CACHES.with(|i| {
|
||
|
i.borrow_mut()
|
||
|
.insert(prov.configuration_url.to_string(), conf.clone())
|
||
|
});
|
||
|
|
||
|
Ok(OpenIDClient { prov, conf })
|
||
|
}
|
||
|
|
||
|
/// Get the URL where a user should be redirected for login
|
||
|
pub async fn start_login(prov_id: &str, ip: IpAddr) -> anyhow::Result<String> {
|
||
|
let prov = load_provider_info(prov_id).await?;
|
||
|
let (state_key, state) = OpenIDState::new(ip, &prov);
|
||
|
redis_connection::set_value(&redis_key(&state_key), &state, OPEN_ID_STATE_DURATION).await?;
|
||
|
|
||
|
Ok(prov.conf.gen_authorization_url(
|
||
|
prov.prov.client_id,
|
||
|
&state_key,
|
||
|
&AppConfig::get().oidc_redirect_url,
|
||
|
))
|
||
|
}
|