use better_auth_core::{AuthError, AuthResult};
use super::super::{CognitoOptions, GoogleOptions, TwitchOptions, google};
use super::{OAuthProvider, ProviderKind};
impl OAuthProvider {
pub fn microsoft(
client_id: &str,
client_secret: &str,
options: super::microsoft::MicrosoftOptions,
) -> AuthResult<Self> {
if !client_secret.is_empty() && options.client_assertion.is_some() {
return Err(AuthError::internal(
"Microsoft Entra ID clientAssertion cannot be combined with clientSecret",
));
}
let authorization = format!(
"{}/{}/oauth2/v2.0/authorize",
options.authority(),
options.tenant()
);
let token = format!(
"{}/{}/oauth2/v2.0/token",
options.authority(),
options.tenant()
);
let token_endpoint_auth = options
.client_assertion
.clone()
.map(super::TokenEndpointAuth::PrivateKeyJwt);
Ok(Self {
kind: ProviderKind::Microsoft {
options,
token_endpoint_auth,
},
..Self::custom(client_id, client_secret, &authorization, &token)
})
}
pub fn cognito(
client_id: &str,
client_secret: &str,
options: CognitoOptions,
) -> AuthResult<Self> {
if options.domain.is_empty() || options.region.is_empty() || options.user_pool_id.is_empty()
{
better_auth_core::observability::logger::current().error(
"Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.",
&[],
)?;
return Err(AuthError::internal("DOMAIN_AND_REGION_REQUIRED"));
}
let domain = options
.domain
.strip_prefix("https://")
.or_else(|| options.domain.strip_prefix("http://"))
.unwrap_or(&options.domain);
let provider = Self {
user_info_url: Some(format!("https://{domain}/oauth2/userinfo")),
..Self::custom(
client_id,
client_secret,
&format!("https://{domain}/oauth2/authorize"),
&format!("https://{domain}/oauth2/token"),
)
};
Ok(Self {
kind: ProviderKind::Cognito(options),
..provider
})
}
pub fn custom(client_id: &str, client_secret: &str, auth_url: &str, token_url: &str) -> Self {
Self {
kind: ProviderKind::Custom,
client_id: client_id.into(),
client_secret: client_secret.into(),
client_key: None,
auth_url: auth_url.into(),
token_url: token_url.into(),
token_endpoint_auth: None,
user_info_url: None,
redirect_uri: None,
end_session_endpoint: None,
post_logout_redirect_uri: None,
scopes: None,
disable_default_scope: false,
disable_id_token_sign_in: false,
prompt: None,
authorization_params: Vec::new(),
map_user_info: None,
map_profile_to_user: None,
get_user_info: None,
account_subject: None,
refresh_access_token: None,
verify_id_token: None,
disable_implicit_sign_up: None,
disable_sign_up: None,
require_email_verification: None,
override_user_info_on_sign_in: false,
}
}
pub fn google(client_id: &str, client_secret: &str) -> Self {
Self::google_with_options(client_id, client_secret, GoogleOptions::default())
}
pub fn google_with_options(
client_id: &str,
client_secret: &str,
options: GoogleOptions,
) -> Self {
Self {
kind: ProviderKind::Google {
jwks_url: google::JWKS_URL.to_owned(),
options,
},
user_info_url: Some("https://www.googleapis.com/oauth2/v3/userinfo".into()),
authorization_params: vec![("include_granted_scopes".into(), "true".into())],
..Self::custom(
client_id,
client_secret,
"https://accounts.google.com/o/oauth2/v2/auth",
"https://oauth2.googleapis.com/token",
)
}
}
pub fn github(client_id: &str, client_secret: &str) -> Self {
Self::github_with_endpoints(
client_id,
client_secret,
"https://github.com/login/oauth/authorize",
"https://github.com/login/oauth/access_token",
"https://api.github.com/user",
"https://api.github.com/user/emails",
)
}
pub fn github_with_endpoints(
client_id: &str,
client_secret: &str,
auth_url: &str,
token_url: &str,
user_info_url: &str,
user_emails_url: &str,
) -> Self {
Self {
kind: ProviderKind::GitHub {
user_url: user_info_url.into(),
emails_url: user_emails_url.into(),
},
user_info_url: Some(user_info_url.into()),
..Self::custom(client_id, client_secret, auth_url, token_url)
}
}
pub fn discord(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Discord,
user_info_url: Some("https://discord.com/api/users/@me".into()),
..Self::custom(
client_id,
client_secret,
"https://discord.com/api/oauth2/authorize",
"https://discord.com/api/oauth2/token",
)
}
}
pub fn gitlab(client_id: &str, client_secret: &str) -> Self {
Self::gitlab_with_issuer(client_id, client_secret, "")
}
pub fn gitlab_with_issuer(client_id: &str, client_secret: &str, issuer: &str) -> Self {
let issuer = if issuer.is_empty() {
"https://gitlab.com"
} else {
issuer
};
let endpoint = |path| {
format!("{issuer}/{path}")
.split("://")
.map(|segment| {
let mut previous_slash = false;
segment
.chars()
.filter(|&character| {
let repeated = previous_slash && character == '/';
previous_slash = character == '/';
!repeated
})
.collect::<String>()
})
.collect::<Vec<_>>()
.join("://")
};
Self {
kind: ProviderKind::GitLab,
user_info_url: Some(endpoint("api/v4/user")),
..Self::custom(
client_id,
client_secret,
&endpoint("oauth/authorize"),
&endpoint("oauth/token"),
)
}
}
pub fn spotify(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Spotify,
user_info_url: Some("https://api.spotify.com/v1/me".into()),
..Self::custom(
client_id,
client_secret,
"https://accounts.spotify.com/authorize",
"https://accounts.spotify.com/api/token",
)
}
}
pub fn huggingface(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::HuggingFace,
user_info_url: Some("https://huggingface.co/oauth/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://huggingface.co/oauth/authorize",
"https://huggingface.co/oauth/token",
)
}
}
pub fn polar(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Polar,
user_info_url: Some("https://api.polar.sh/v1/oauth2/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://polar.sh/oauth2/authorize",
"https://api.polar.sh/v1/oauth2/token",
)
}
}
pub fn vercel(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Vercel,
user_info_url: Some("https://api.vercel.com/login/oauth/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://vercel.com/oauth/authorize",
"https://api.vercel.com/login/oauth/token",
)
}
}
pub fn figma(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Figma,
user_info_url: Some("https://api.figma.com/v1/me".into()),
..Self::custom(
client_id,
client_secret,
"https://www.figma.com/oauth",
"https://api.figma.com/v1/oauth/token",
)
}
}
pub fn dropbox(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Dropbox,
user_info_url: Some("https://api.dropboxapi.com/2/users/get_current_account".into()),
..Self::custom(
client_id,
client_secret,
"https://www.dropbox.com/oauth2/authorize",
"https://api.dropboxapi.com/oauth2/token",
)
}
}
pub fn kick(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Kick,
user_info_url: Some("https://api.kick.com/public/v1/users".into()),
..Self::custom(
client_id,
client_secret,
"https://id.kick.com/oauth/authorize",
"https://id.kick.com/oauth/token",
)
}
}
pub fn linkedin(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::LinkedIn,
user_info_url: Some("https://api.linkedin.com/v2/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://www.linkedin.com/oauth/v2/authorization",
"https://www.linkedin.com/oauth/v2/accessToken",
)
}
}
pub fn slack(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Slack,
user_info_url: Some("https://slack.com/api/openid.connect.userInfo".into()),
..Self::custom(
client_id,
client_secret,
"https://slack.com/openid/connect/authorize",
"https://slack.com/api/openid.connect.token",
)
}
}
pub fn naver(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Naver,
user_info_url: Some("https://openapi.naver.com/v1/nid/me".into()),
..Self::custom(
client_id,
client_secret,
"https://nid.naver.com/oauth2.0/authorize",
"https://nid.naver.com/oauth2.0/token",
)
}
}
pub fn line(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Line {
verify_url: "https://api.line.me/oauth2/v2.1/verify".into(),
},
user_info_url: Some("https://api.line.me/oauth2/v2.1/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://access.line.me/oauth2/v2.1/authorize",
"https://api.line.me/oauth2/v2.1/token",
)
}
}
pub fn linear(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Linear,
user_info_url: Some("https://api.linear.app/graphql".into()),
..Self::custom(
client_id,
client_secret,
"https://linear.app/oauth/authorize",
"https://api.linear.app/oauth/token",
)
}
}
pub fn atlassian(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Atlassian,
user_info_url: Some("https://api.atlassian.com/me".into()),
..Self::custom(
client_id,
client_secret,
"https://auth.atlassian.com/authorize",
"https://auth.atlassian.com/oauth/token",
)
}
}
pub fn salesforce(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Salesforce,
user_info_url: Some("https://login.salesforce.com/services/oauth2/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://login.salesforce.com/services/oauth2/authorize",
"https://login.salesforce.com/services/oauth2/token",
)
}
}
pub fn reddit(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Reddit,
user_info_url: Some("https://oauth.reddit.com/api/v1/me".into()),
..Self::custom(
client_id,
client_secret,
"https://www.reddit.com/api/v1/authorize",
"https://www.reddit.com/api/v1/access_token",
)
}
}
pub fn kakao(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Kakao,
user_info_url: Some("https://kapi.kakao.com/v2/user/me".into()),
..Self::custom(
client_id,
client_secret,
"https://kauth.kakao.com/oauth/authorize",
"https://kauth.kakao.com/oauth/token",
)
}
}
pub fn zoom(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Zoom { pkce: true },
user_info_url: Some("https://api.zoom.us/v2/users/me".into()),
..Self::custom(
client_id,
client_secret,
"https://zoom.us/oauth/authorize",
"https://zoom.us/oauth/token",
)
}
}
pub fn zoom_without_pkce(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Zoom { pkce: false },
..Self::zoom(client_id, client_secret)
}
}
pub fn twitter(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Twitter,
user_info_url: Some(
"https://api.x.com/2/users/me?user.fields=profile_image_url".into(),
),
..Self::custom(
client_id,
client_secret,
"https://x.com/i/oauth2/authorize",
"https://api.x.com/2/oauth2/token",
)
}
}
pub fn wechat(client_id: &str, client_secret: &str) -> Self {
Self::wechat_with_endpoints(
client_id,
client_secret,
"https://open.weixin.qq.com/connect/qrconnect",
"https://api.weixin.qq.com/sns/oauth2/access_token",
"https://api.weixin.qq.com/sns/oauth2/refresh_token",
"https://api.weixin.qq.com/sns/userinfo",
)
}
pub fn wechat_with_endpoints(
client_id: &str,
client_secret: &str,
auth_url: &str,
token_url: &str,
refresh_url: &str,
user_info_url: &str,
) -> Self {
Self {
kind: ProviderKind::WeChat {
refresh_url: refresh_url.into(),
},
user_info_url: Some(user_info_url.into()),
..Self::custom(client_id, client_secret, auth_url, token_url)
}
}
pub fn vk(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Vk,
user_info_url: Some("https://id.vk.com/oauth2/user_info".into()),
..Self::custom(
client_id,
client_secret,
"https://id.vk.com/authorize",
"https://id.vk.com/oauth2/auth",
)
}
}
pub fn twitch(client_id: &str, client_secret: &str, options: TwitchOptions) -> Self {
Self {
kind: ProviderKind::Twitch(options),
..Self::custom(
client_id,
client_secret,
"https://id.twitch.tv/oauth2/authorize",
"https://id.twitch.tv/oauth2/token",
)
}
}
pub fn notion(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Notion,
user_info_url: Some("https://api.notion.com/v1/users/me".into()),
..Self::custom(
client_id,
client_secret,
"https://api.notion.com/v1/oauth/authorize",
"https://api.notion.com/v1/oauth/token",
)
}
}
pub fn roblox(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Roblox,
user_info_url: Some("https://apis.roblox.com/oauth/v1/userinfo".into()),
..Self::custom(
client_id,
client_secret,
"https://apis.roblox.com/oauth/v1/authorize",
"https://apis.roblox.com/oauth/v1/token",
)
}
}
pub fn tiktok(client_key: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::TikTok,
client_key: Some(client_key.into()),
user_info_url: Some("https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_large_url,display_name,username".into()),
..Self::custom(
"",
client_secret,
"https://www.tiktok.com/v2/auth/authorize",
"https://open.tiktokapis.com/v2/oauth/token/",
)
}
}
pub fn railway(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Railway,
user_info_url: Some("https://backboard.railway.com/oauth/me".into()),
..Self::custom(
client_id,
client_secret,
"https://backboard.railway.com/oauth/auth",
"https://backboard.railway.com/oauth/token",
)
}
}
pub fn cloudflare(client_id: &str, client_secret: &str) -> Self {
Self {
kind: ProviderKind::Cloudflare,
user_info_url: Some("https://api.cloudflare.com/client/v4/user".into()),
..Self::custom(
client_id,
client_secret,
"https://dash.cloudflare.com/oauth2/auth",
"https://dash.cloudflare.com/oauth2/token",
)
}
}
}