use cratefield_oauth_client::ClientAuth;
use crate::Provider;
#[must_use]
pub fn x() -> Provider {
Provider {
key: "x".to_owned(),
authorize_url: "https://x.com/i/oauth2/authorize".to_owned(),
token_url: "https://api.x.com/2/oauth2/token".to_owned(),
revoke_url: Some("https://api.x.com/2/oauth2/revoke".to_owned()),
scopes: vec![
"tweet.read".to_owned(),
"users.read".to_owned(),
"offline.access".to_owned(),
],
scope_separator: " ",
client_auth: ClientAuth::ClientSecretBasic,
authorize_params: Vec::new(),
rotating_refresh: true,
pkce: true,
}
}
#[must_use]
pub fn google() -> Provider {
Provider {
key: "google".to_owned(),
authorize_url: "https://accounts.google.com/o/oauth2/v2/auth".to_owned(),
token_url: "https://oauth2.googleapis.com/token".to_owned(),
revoke_url: Some("https://oauth2.googleapis.com/revoke".to_owned()),
scopes: vec![
"openid".to_owned(),
"email".to_owned(),
"profile".to_owned(),
],
scope_separator: " ",
client_auth: ClientAuth::ClientSecretBasic,
authorize_params: vec![
("access_type".to_owned(), "offline".to_owned()),
("prompt".to_owned(), "consent".to_owned()),
],
rotating_refresh: false,
pkce: true,
}
}
#[must_use]
pub fn linkedin() -> Provider {
Provider {
key: "linkedin".to_owned(),
authorize_url: "https://www.linkedin.com/oauth/v2/authorization".to_owned(),
token_url: "https://www.linkedin.com/oauth/v2/accessToken".to_owned(),
revoke_url: None,
scopes: vec![
"openid".to_owned(),
"profile".to_owned(),
"email".to_owned(),
],
scope_separator: " ",
client_auth: ClientAuth::ClientSecretPost,
authorize_params: Vec::new(),
rotating_refresh: false,
pkce: false,
}
}
#[must_use]
pub fn vercel() -> Provider {
Provider {
key: "vercel".to_owned(),
authorize_url: "https://vercel.com/oauth/authorize".to_owned(),
token_url: "https://api.vercel.com/login/oauth/token".to_owned(),
revoke_url: Some("https://api.vercel.com/login/oauth/token/revoke".to_owned()),
scopes: vec![
"openid".to_owned(),
"email".to_owned(),
"profile".to_owned(),
],
scope_separator: " ",
client_auth: ClientAuth::ClientSecretPost,
authorize_params: Vec::new(),
rotating_refresh: true,
pkce: true,
}
}
#[must_use]
pub fn gitlab() -> Provider {
gitlab_at("https://gitlab.com")
}
#[must_use]
pub fn gitlab_at(base: &str) -> Provider {
let base = base.trim_end_matches('/');
Provider {
key: "gitlab".to_owned(),
authorize_url: format!("{base}/oauth/authorize"),
token_url: format!("{base}/oauth/token"),
revoke_url: Some(format!("{base}/oauth/revoke")),
scopes: vec!["read_user".to_owned()],
scope_separator: " ",
client_auth: ClientAuth::ClientSecretPost,
authorize_params: Vec::new(),
rotating_refresh: true,
pkce: true,
}
}
#[must_use]
pub fn linear() -> Provider {
Provider {
key: "linear".to_owned(),
authorize_url: "https://linear.app/oauth/authorize".to_owned(),
token_url: "https://api.linear.app/oauth/token".to_owned(),
revoke_url: Some("https://api.linear.app/oauth/revoke".to_owned()),
scopes: vec!["read".to_owned(), "write".to_owned()],
scope_separator: ",",
client_auth: ClientAuth::ClientSecretPost,
authorize_params: Vec::new(),
rotating_refresh: true,
pkce: true,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn presets_carry_the_wire_facts() {
for provider in [x(), google(), vercel(), gitlab(), linear()] {
assert!(!provider.key.is_empty());
assert!(
provider.authorize_url.starts_with("https://"),
"{provider:?}"
);
assert!(provider.token_url.starts_with("https://"), "{provider:?}");
assert!(
provider.revoke_url.is_some(),
"{} has no revoke URL",
provider.key
);
assert!(
!provider.scopes.is_empty(),
"{} asks for no scopes",
provider.key
);
}
assert!(
linkedin().revoke_url.is_none(),
"LinkedIn documents no revocation endpoint, so the preset must not invent one"
);
assert_eq!(linear().scope_separator, ",");
assert_eq!(google().scope_separator, " ");
let self_managed = gitlab_at("https://gitlab.example.com/");
assert_eq!(self_managed.key, "gitlab");
assert_eq!(
self_managed.authorize_url,
"https://gitlab.example.com/oauth/authorize"
);
assert_eq!(
self_managed.token_url,
"https://gitlab.example.com/oauth/token"
);
}
}