Skip to content

Commit 1718640

Browse files
authored
add optional P_OIDC_QUERY_PARAMS env var (#1764)
Gives user the option to add query params to the auth url made during OAUTH logins. For example, in order to get a refresh_token from Google, they require `access_type=offline&prompt=consent` query params
1 parent 85fbdea commit 1718640

3 files changed

Lines changed: 77 additions & 4 deletions

File tree

‎src/cli.rs‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -631,12 +631,23 @@ pub struct Options {
631631
long = "oidc-scope",
632632
name = "oidc-scope",
633633
env = "P_OIDC_SCOPE",
634-
default_value = "openid profile email offline_access",
634+
default_value = "openid profile email",
635635
required = false,
636-
help = "OIDC scope to request (default: openid profile email offline_access)"
636+
help = "OIDC scope to request (default: openid profile email)"
637637
)]
638638
pub scope: String,
639639

640+
// additional query params for oidc auth url
641+
#[arg(
642+
long,
643+
name = "oidc-query-params",
644+
env = "P_OIDC_QUERY_PARAMS",
645+
value_parser = validation::validate_query_params,
646+
required = false,
647+
help = "Additional query params to add to the auth url"
648+
)]
649+
pub oidc_query_params: Option<String>,
650+
640651
// event's maximum chunk age in hours
641652
#[arg(
642653
long,

‎src/handlers/http/oidc.rs‎

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,13 @@ pub async fn login(
9696
let scope = PARSEABLE.options.scope.to_string();
9797
let mut auth_url: String = client.read().await.auth_url(&scope, Some(redirect)).into();
9898

99-
auth_url.push_str("&access_type=offline");
99+
if let Some(query_params) = PARSEABLE.options.oidc_query_params.as_ref() {
100+
if !query_params.starts_with('&') {
101+
auth_url = format!("{auth_url}&{query_params}");
102+
} else {
103+
auth_url.push_str(query_params.as_str());
104+
}
105+
}
100106
return Ok(HttpResponse::TemporaryRedirect()
101107
.insert_header((actix_web::http::header::LOCATION, auth_url))
102108
.finish());
@@ -153,7 +159,13 @@ pub async fn login(
153159
.await
154160
.auth_url(&scope, Some(redirect))
155161
.into();
156-
auth_url.push_str("&access_type=offline");
162+
if let Some(query_params) = PARSEABLE.options.oidc_query_params.as_ref() {
163+
if !query_params.starts_with('&') {
164+
auth_url = format!("{auth_url}&{query_params}");
165+
} else {
166+
auth_url.push_str(query_params.as_str());
167+
}
168+
}
157169
HttpResponse::TemporaryRedirect()
158170
.insert_header((actix_web::http::header::LOCATION, auth_url))
159171
.finish()

‎src/option.rs‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,56 @@ pub mod validation {
209209
}
210210
}
211211

212+
pub fn validate_query_params(mut query: &str) -> Result<String, String> {
213+
fn valid_component(value: &str) -> bool {
214+
let bytes = value.as_bytes();
215+
let mut i = 0;
216+
217+
while i < bytes.len() {
218+
match bytes[i] {
219+
b'%' if i + 2 < bytes.len()
220+
&& bytes[i + 1].is_ascii_hexdigit()
221+
&& bytes[i + 2].is_ascii_hexdigit() =>
222+
{
223+
i += 3;
224+
}
225+
b if b.is_ascii_alphanumeric()
226+
|| matches!(b, b'-' | b'.' | b'_' | b'~' | b'+') =>
227+
{
228+
i += 1;
229+
}
230+
_ => return false,
231+
}
232+
}
233+
234+
true
235+
}
236+
237+
if query.is_empty() {
238+
return Err("query string cannot be empty".into());
239+
}
240+
query = if let Some(query) = query.strip_prefix('&') {
241+
query
242+
} else {
243+
query
244+
};
245+
for param in query.split('&') {
246+
let (key, value) = param
247+
.split_once('=')
248+
.ok_or_else(|| format!("invalid parameter: {param}"))?;
249+
250+
if key.is_empty() {
251+
return Err("parameter key cannot be empty".into());
252+
}
253+
254+
if !valid_component(key) || !valid_component(value) {
255+
return Err(format!("invalid characters in parameter: {param}"));
256+
}
257+
}
258+
259+
Ok(query.to_owned())
260+
}
261+
212262
pub fn validate_payload_size(s: &str) -> Result<usize, String> {
213263
const MIN_SIZE: usize = 100; // 100 bytes
214264
const MAX_SIZE: usize = 100 * 1024 * 1024; // 100 MB

0 commit comments

Comments
 (0)