use super::{discovery::OidcProvider, SsoError};
#[derive(Clone)]
pub struct OidcConfig {
pub provider: OidcProvider,
pub client_id: String,
pub client_secret: String,
pub redirect_uri: String,
pub scopes: Vec<String>,
pub post_login_redirect: String,
}
impl OidcConfig {
pub fn google(client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
OidcConfig {
provider: OidcProvider::google(),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
}
}
pub fn microsoft(
tenant_id: &str,
client_id: &str,
client_secret: &str,
redirect_uri: &str,
) -> Self {
OidcConfig {
provider: OidcProvider::microsoft(tenant_id),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
}
}
pub fn github(client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
OidcConfig {
provider: OidcProvider::github(),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["read:user".into(), "user:email".into()],
post_login_redirect: "/".into(),
}
}
pub fn okta(domain: &str, client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
OidcConfig {
provider: OidcProvider::okta(domain),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
}
}
pub fn auth0(domain: &str, client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
OidcConfig {
provider: OidcProvider::auth0(domain),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
}
}
pub fn keycloak(
base_url: &str,
realm: &str,
client_id: &str,
client_secret: &str,
redirect_uri: &str,
) -> Self {
OidcConfig {
provider: OidcProvider::keycloak(base_url, realm),
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
}
}
pub fn discover(
issuer: &str,
client_id: &str,
client_secret: &str,
redirect_uri: &str,
) -> Result<Self, SsoError> {
Ok(OidcConfig {
provider: OidcProvider::discover(issuer)?,
client_id: client_id.into(),
client_secret: client_secret.into(),
redirect_uri: redirect_uri.into(),
scopes: vec!["openid".into(), "email".into(), "profile".into()],
post_login_redirect: "/".into(),
})
}
pub fn from_env() -> Result<Self, SsoError> {
let provider_name = std::env::var("RWS_OIDC_PROVIDER")
.map_err(|_| SsoError("RWS_OIDC_PROVIDER not set".into()))?;
let client_id = std::env::var("RWS_OIDC_CLIENT_ID")
.map_err(|_| SsoError("RWS_OIDC_CLIENT_ID not set".into()))?;
let client_secret = std::env::var("RWS_OIDC_CLIENT_SECRET").unwrap_or_default();
let redirect_uri = std::env::var("RWS_OIDC_REDIRECT_URI")
.map_err(|_| SsoError("RWS_OIDC_REDIRECT_URI not set".into()))?;
let post_login_redirect =
std::env::var("RWS_OIDC_POST_LOGIN_REDIRECT").unwrap_or_else(|_| "/".into());
let scopes: Vec<String> = std::env::var("RWS_OIDC_SCOPES")
.unwrap_or_else(|_| "openid email profile".into())
.split_whitespace()
.map(String::from)
.collect();
let provider = match provider_name.as_str() {
"google" => OidcProvider::google(),
"github" => OidcProvider::github(),
"microsoft" => {
let tenant = std::env::var("RWS_OIDC_TENANT_ID")
.map_err(|_| SsoError("RWS_OIDC_TENANT_ID required for microsoft".into()))?;
OidcProvider::microsoft(&tenant)
}
"okta" => {
let domain = std::env::var("RWS_OIDC_ISSUER")
.map_err(|_| SsoError("RWS_OIDC_ISSUER required for okta".into()))?;
OidcProvider::okta(&domain)
}
"auth0" => {
let domain = std::env::var("RWS_OIDC_ISSUER")
.map_err(|_| SsoError("RWS_OIDC_ISSUER required for auth0".into()))?;
OidcProvider::auth0(&domain)
}
"keycloak" => {
let base = std::env::var("RWS_OIDC_ISSUER").map_err(|_| {
SsoError("RWS_OIDC_ISSUER required for keycloak (base_url)".into())
})?;
let realm = std::env::var("RWS_OIDC_TENANT_ID").map_err(|_| {
SsoError("RWS_OIDC_TENANT_ID required for keycloak (realm)".into())
})?;
OidcProvider::keycloak(&base, &realm)
}
_ => {
let issuer = std::env::var("RWS_OIDC_ISSUER")
.map_err(|_| SsoError("RWS_OIDC_ISSUER required for custom provider".into()))?;
OidcProvider::discover(&issuer)?
}
};
Ok(OidcConfig {
provider,
client_id,
client_secret,
redirect_uri,
scopes,
post_login_redirect,
})
}
pub fn post_login_redirect(mut self, path: &str) -> Self {
self.post_login_redirect = path.into();
self
}
pub fn scopes(mut self, scopes: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.scopes = scopes.into_iter().map(Into::into).collect();
self
}
}