1use super::{discovery::OidcProvider, SsoError};
9
10#[derive(Clone)]
12pub struct OidcConfig {
13 pub provider: OidcProvider,
15 pub client_id: String,
17 pub client_secret: String,
19 pub redirect_uri: String,
21 pub scopes: Vec<String>,
23 pub post_login_redirect: String,
25}
26
27impl OidcConfig {
28 pub fn google(client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
30 OidcConfig {
31 provider: OidcProvider::google(),
32 client_id: client_id.into(),
33 client_secret: client_secret.into(),
34 redirect_uri: redirect_uri.into(),
35 scopes: vec!["openid".into(), "email".into(), "profile".into()],
36 post_login_redirect: "/".into(),
37 }
38 }
39
40 pub fn microsoft(
44 tenant_id: &str,
45 client_id: &str,
46 client_secret: &str,
47 redirect_uri: &str,
48 ) -> Self {
49 OidcConfig {
50 provider: OidcProvider::microsoft(tenant_id),
51 client_id: client_id.into(),
52 client_secret: client_secret.into(),
53 redirect_uri: redirect_uri.into(),
54 scopes: vec!["openid".into(), "email".into(), "profile".into()],
55 post_login_redirect: "/".into(),
56 }
57 }
58
59 pub fn github(client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
61 OidcConfig {
62 provider: OidcProvider::github(),
63 client_id: client_id.into(),
64 client_secret: client_secret.into(),
65 redirect_uri: redirect_uri.into(),
66 scopes: vec!["read:user".into(), "user:email".into()],
67 post_login_redirect: "/".into(),
68 }
69 }
70
71 pub fn okta(domain: &str, client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
75 OidcConfig {
76 provider: OidcProvider::okta(domain),
77 client_id: client_id.into(),
78 client_secret: client_secret.into(),
79 redirect_uri: redirect_uri.into(),
80 scopes: vec!["openid".into(), "email".into(), "profile".into()],
81 post_login_redirect: "/".into(),
82 }
83 }
84
85 pub fn auth0(domain: &str, client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
89 OidcConfig {
90 provider: OidcProvider::auth0(domain),
91 client_id: client_id.into(),
92 client_secret: client_secret.into(),
93 redirect_uri: redirect_uri.into(),
94 scopes: vec!["openid".into(), "email".into(), "profile".into()],
95 post_login_redirect: "/".into(),
96 }
97 }
98
99 pub fn keycloak(
101 base_url: &str,
102 realm: &str,
103 client_id: &str,
104 client_secret: &str,
105 redirect_uri: &str,
106 ) -> Self {
107 OidcConfig {
108 provider: OidcProvider::keycloak(base_url, realm),
109 client_id: client_id.into(),
110 client_secret: client_secret.into(),
111 redirect_uri: redirect_uri.into(),
112 scopes: vec!["openid".into(), "email".into(), "profile".into()],
113 post_login_redirect: "/".into(),
114 }
115 }
116
117 pub fn discover(
119 issuer: &str,
120 client_id: &str,
121 client_secret: &str,
122 redirect_uri: &str,
123 ) -> Result<Self, SsoError> {
124 Ok(OidcConfig {
125 provider: OidcProvider::discover(issuer)?,
126 client_id: client_id.into(),
127 client_secret: client_secret.into(),
128 redirect_uri: redirect_uri.into(),
129 scopes: vec!["openid".into(), "email".into(), "profile".into()],
130 post_login_redirect: "/".into(),
131 })
132 }
133
134 pub fn from_env() -> Result<Self, SsoError> {
148 let provider_name = std::env::var("RWS_OIDC_PROVIDER")
149 .map_err(|_| SsoError("RWS_OIDC_PROVIDER not set".into()))?;
150 let client_id = std::env::var("RWS_OIDC_CLIENT_ID")
151 .map_err(|_| SsoError("RWS_OIDC_CLIENT_ID not set".into()))?;
152 let client_secret = std::env::var("RWS_OIDC_CLIENT_SECRET").unwrap_or_default();
153 let redirect_uri = std::env::var("RWS_OIDC_REDIRECT_URI")
154 .map_err(|_| SsoError("RWS_OIDC_REDIRECT_URI not set".into()))?;
155 let post_login_redirect =
156 std::env::var("RWS_OIDC_POST_LOGIN_REDIRECT").unwrap_or_else(|_| "/".into());
157 let scopes: Vec<String> = std::env::var("RWS_OIDC_SCOPES")
158 .unwrap_or_else(|_| "openid email profile".into())
159 .split_whitespace()
160 .map(String::from)
161 .collect();
162
163 let provider = match provider_name.as_str() {
164 "google" => OidcProvider::google(),
165 "github" => OidcProvider::github(),
166 "microsoft" => {
167 let tenant = std::env::var("RWS_OIDC_TENANT_ID")
168 .map_err(|_| SsoError("RWS_OIDC_TENANT_ID required for microsoft".into()))?;
169 OidcProvider::microsoft(&tenant)
170 }
171 "okta" => {
172 let domain = std::env::var("RWS_OIDC_ISSUER")
173 .map_err(|_| SsoError("RWS_OIDC_ISSUER required for okta".into()))?;
174 OidcProvider::okta(&domain)
175 }
176 "auth0" => {
177 let domain = std::env::var("RWS_OIDC_ISSUER")
178 .map_err(|_| SsoError("RWS_OIDC_ISSUER required for auth0".into()))?;
179 OidcProvider::auth0(&domain)
180 }
181 "keycloak" => {
182 let base = std::env::var("RWS_OIDC_ISSUER").map_err(|_| {
183 SsoError("RWS_OIDC_ISSUER required for keycloak (base_url)".into())
184 })?;
185 let realm = std::env::var("RWS_OIDC_TENANT_ID").map_err(|_| {
186 SsoError("RWS_OIDC_TENANT_ID required for keycloak (realm)".into())
187 })?;
188 OidcProvider::keycloak(&base, &realm)
189 }
190 _ => {
191 let issuer = std::env::var("RWS_OIDC_ISSUER")
193 .map_err(|_| SsoError("RWS_OIDC_ISSUER required for custom provider".into()))?;
194 OidcProvider::discover(&issuer)?
195 }
196 };
197
198 Ok(OidcConfig {
199 provider,
200 client_id,
201 client_secret,
202 redirect_uri,
203 scopes,
204 post_login_redirect,
205 })
206 }
207
208 pub fn post_login_redirect(mut self, path: &str) -> Self {
210 self.post_login_redirect = path.into();
211 self
212 }
213
214 pub fn scopes(mut self, scopes: impl IntoIterator<Item = impl Into<String>>) -> Self {
216 self.scopes = scopes.into_iter().map(Into::into).collect();
217 self
218 }
219}