1use better_auth_core::{AuthError, AuthResult};
2
3use super::super::{CognitoOptions, GoogleOptions, TwitchOptions, google};
4use super::{OAuthProvider, ProviderKind};
5
6impl OAuthProvider {
7 pub fn microsoft(
9 client_id: &str,
10 client_secret: &str,
11 options: super::microsoft::MicrosoftOptions,
12 ) -> AuthResult<Self> {
13 if !client_secret.is_empty() && options.client_assertion.is_some() {
14 return Err(AuthError::internal(
15 "Microsoft Entra ID clientAssertion cannot be combined with clientSecret",
16 ));
17 }
18 let authorization = format!(
19 "{}/{}/oauth2/v2.0/authorize",
20 options.authority(),
21 options.tenant()
22 );
23 let token = format!(
24 "{}/{}/oauth2/v2.0/token",
25 options.authority(),
26 options.tenant()
27 );
28 let token_endpoint_auth = options
29 .client_assertion
30 .clone()
31 .map(super::TokenEndpointAuth::PrivateKeyJwt);
32 Ok(Self {
33 kind: ProviderKind::Microsoft {
34 options,
35 token_endpoint_auth,
36 },
37 ..Self::custom(client_id, client_secret, &authorization, &token)
38 })
39 }
40
41 pub fn cognito(
44 client_id: &str,
45 client_secret: &str,
46 options: CognitoOptions,
47 ) -> AuthResult<Self> {
48 if options.domain.is_empty() || options.region.is_empty() || options.user_pool_id.is_empty()
49 {
50 better_auth_core::observability::logger::current().error(
51 "Domain, region and userPoolId are required for Amazon Cognito. Make sure to provide them in the options.",
52 &[],
53 );
54 return Err(AuthError::internal("DOMAIN_AND_REGION_REQUIRED"));
55 }
56 let domain = options
57 .domain
58 .strip_prefix("https://")
59 .or_else(|| options.domain.strip_prefix("http://"))
60 .unwrap_or(&options.domain);
61 let provider = Self {
62 user_info_url: Some(format!("https://{domain}/oauth2/userinfo")),
63 ..Self::custom(
64 client_id,
65 client_secret,
66 &format!("https://{domain}/oauth2/authorize"),
67 &format!("https://{domain}/oauth2/token"),
68 )
69 };
70 Ok(Self {
71 kind: ProviderKind::Cognito(options),
72 ..provider
73 })
74 }
75
76 pub fn custom(client_id: &str, client_secret: &str, auth_url: &str, token_url: &str) -> Self {
78 Self {
79 kind: ProviderKind::Custom,
80 client_id: client_id.into(),
81 client_secret: client_secret.into(),
82 client_key: None,
83 auth_url: auth_url.into(),
84 token_url: token_url.into(),
85 token_endpoint_auth: None,
86 user_info_url: None,
87 redirect_uri: None,
88 end_session_endpoint: None,
89 post_logout_redirect_uri: None,
90 scopes: None,
91 disable_default_scope: false,
92 disable_id_token_sign_in: false,
93 prompt: None,
94 authorization_params: Vec::new(),
95 map_user_info: None,
96 map_profile_to_user: None,
97 get_user_info: None,
98 refresh_access_token: None,
99 verify_id_token: None,
100 disable_implicit_sign_up: None,
101 disable_sign_up: None,
102 require_email_verification: None,
103 override_user_info_on_sign_in: false,
104 }
105 }
106
107 pub fn google(client_id: &str, client_secret: &str) -> Self {
109 Self::google_with_options(client_id, client_secret, GoogleOptions::default())
110 }
111
112 pub fn google_with_options(
114 client_id: &str,
115 client_secret: &str,
116 options: GoogleOptions,
117 ) -> Self {
118 Self {
119 kind: ProviderKind::Google {
120 jwks_url: google::JWKS_URL.to_owned(),
121 options,
122 },
123 user_info_url: Some("https://www.googleapis.com/oauth2/v3/userinfo".into()),
124 authorization_params: vec![("include_granted_scopes".into(), "true".into())],
125 ..Self::custom(
126 client_id,
127 client_secret,
128 "https://accounts.google.com/o/oauth2/v2/auth",
129 "https://oauth2.googleapis.com/token",
130 )
131 }
132 }
133
134 pub fn github(client_id: &str, client_secret: &str) -> Self {
136 Self::github_with_endpoints(
137 client_id,
138 client_secret,
139 "https://github.com/login/oauth/authorize",
140 "https://github.com/login/oauth/access_token",
141 "https://api.github.com/user",
142 "https://api.github.com/user/emails",
143 )
144 }
145
146 pub fn github_with_endpoints(
148 client_id: &str,
149 client_secret: &str,
150 auth_url: &str,
151 token_url: &str,
152 user_info_url: &str,
153 user_emails_url: &str,
154 ) -> Self {
155 Self {
156 kind: ProviderKind::GitHub {
157 user_url: user_info_url.into(),
158 emails_url: user_emails_url.into(),
159 },
160 user_info_url: Some(user_info_url.into()),
161 ..Self::custom(client_id, client_secret, auth_url, token_url)
162 }
163 }
164
165 pub fn discord(client_id: &str, client_secret: &str) -> Self {
167 Self {
168 kind: ProviderKind::Discord,
169 user_info_url: Some("https://discord.com/api/users/@me".into()),
170 ..Self::custom(
171 client_id,
172 client_secret,
173 "https://discord.com/api/oauth2/authorize",
174 "https://discord.com/api/oauth2/token",
175 )
176 }
177 }
178
179 pub fn gitlab(client_id: &str, client_secret: &str) -> Self {
181 Self::gitlab_with_issuer(client_id, client_secret, "")
182 }
183
184 pub fn gitlab_with_issuer(client_id: &str, client_secret: &str, issuer: &str) -> Self {
187 let issuer = if issuer.is_empty() {
188 "https://gitlab.com"
189 } else {
190 issuer
191 };
192 let endpoint = |path| {
193 format!("{issuer}/{path}")
194 .split("://")
195 .map(|segment| {
196 let mut previous_slash = false;
197 segment
198 .chars()
199 .filter(|&character| {
200 let repeated = previous_slash && character == '/';
201 previous_slash = character == '/';
202 !repeated
203 })
204 .collect::<String>()
205 })
206 .collect::<Vec<_>>()
207 .join("://")
208 };
209 Self {
210 kind: ProviderKind::GitLab,
211 user_info_url: Some(endpoint("api/v4/user")),
212 ..Self::custom(
213 client_id,
214 client_secret,
215 &endpoint("oauth/authorize"),
216 &endpoint("oauth/token"),
217 )
218 }
219 }
220
221 pub fn spotify(client_id: &str, client_secret: &str) -> Self {
223 Self {
224 kind: ProviderKind::Spotify,
225 user_info_url: Some("https://api.spotify.com/v1/me".into()),
226 ..Self::custom(
227 client_id,
228 client_secret,
229 "https://accounts.spotify.com/authorize",
230 "https://accounts.spotify.com/api/token",
231 )
232 }
233 }
234
235 pub fn huggingface(client_id: &str, client_secret: &str) -> Self {
237 Self {
238 kind: ProviderKind::HuggingFace,
239 user_info_url: Some("https://huggingface.co/oauth/userinfo".into()),
240 ..Self::custom(
241 client_id,
242 client_secret,
243 "https://huggingface.co/oauth/authorize",
244 "https://huggingface.co/oauth/token",
245 )
246 }
247 }
248
249 pub fn polar(client_id: &str, client_secret: &str) -> Self {
251 Self {
252 kind: ProviderKind::Polar,
253 user_info_url: Some("https://api.polar.sh/v1/oauth2/userinfo".into()),
254 ..Self::custom(
255 client_id,
256 client_secret,
257 "https://polar.sh/oauth2/authorize",
258 "https://api.polar.sh/v1/oauth2/token",
259 )
260 }
261 }
262
263 pub fn vercel(client_id: &str, client_secret: &str) -> Self {
265 Self {
266 kind: ProviderKind::Vercel,
267 user_info_url: Some("https://api.vercel.com/login/oauth/userinfo".into()),
268 ..Self::custom(
269 client_id,
270 client_secret,
271 "https://vercel.com/oauth/authorize",
272 "https://api.vercel.com/login/oauth/token",
273 )
274 }
275 }
276
277 pub fn figma(client_id: &str, client_secret: &str) -> Self {
279 Self {
280 kind: ProviderKind::Figma,
281 user_info_url: Some("https://api.figma.com/v1/me".into()),
282 ..Self::custom(
283 client_id,
284 client_secret,
285 "https://www.figma.com/oauth",
286 "https://api.figma.com/v1/oauth/token",
287 )
288 }
289 }
290
291 pub fn dropbox(client_id: &str, client_secret: &str) -> Self {
293 Self {
294 kind: ProviderKind::Dropbox,
295 user_info_url: Some("https://api.dropboxapi.com/2/users/get_current_account".into()),
296 ..Self::custom(
297 client_id,
298 client_secret,
299 "https://www.dropbox.com/oauth2/authorize",
300 "https://api.dropboxapi.com/oauth2/token",
301 )
302 }
303 }
304
305 pub fn kick(client_id: &str, client_secret: &str) -> Self {
307 Self {
308 kind: ProviderKind::Kick,
309 user_info_url: Some("https://api.kick.com/public/v1/users".into()),
310 ..Self::custom(
311 client_id,
312 client_secret,
313 "https://id.kick.com/oauth/authorize",
314 "https://id.kick.com/oauth/token",
315 )
316 }
317 }
318
319 pub fn linkedin(client_id: &str, client_secret: &str) -> Self {
322 Self {
323 kind: ProviderKind::LinkedIn,
324 user_info_url: Some("https://api.linkedin.com/v2/userinfo".into()),
325 ..Self::custom(
326 client_id,
327 client_secret,
328 "https://www.linkedin.com/oauth/v2/authorization",
329 "https://www.linkedin.com/oauth/v2/accessToken",
330 )
331 }
332 }
333
334 pub fn slack(client_id: &str, client_secret: &str) -> Self {
336 Self {
337 kind: ProviderKind::Slack,
338 user_info_url: Some("https://slack.com/api/openid.connect.userInfo".into()),
339 ..Self::custom(
340 client_id,
341 client_secret,
342 "https://slack.com/openid/connect/authorize",
343 "https://slack.com/api/openid.connect.token",
344 )
345 }
346 }
347
348 pub fn naver(client_id: &str, client_secret: &str) -> Self {
350 Self {
351 kind: ProviderKind::Naver,
352 user_info_url: Some("https://openapi.naver.com/v1/nid/me".into()),
353 ..Self::custom(
354 client_id,
355 client_secret,
356 "https://nid.naver.com/oauth2.0/authorize",
357 "https://nid.naver.com/oauth2.0/token",
358 )
359 }
360 }
361
362 pub fn line(client_id: &str, client_secret: &str) -> Self {
364 Self {
365 kind: ProviderKind::Line {
366 verify_url: "https://api.line.me/oauth2/v2.1/verify".into(),
367 },
368 user_info_url: Some("https://api.line.me/oauth2/v2.1/userinfo".into()),
369 ..Self::custom(
370 client_id,
371 client_secret,
372 "https://access.line.me/oauth2/v2.1/authorize",
373 "https://api.line.me/oauth2/v2.1/token",
374 )
375 }
376 }
377
378 pub fn linear(client_id: &str, client_secret: &str) -> Self {
380 Self {
381 kind: ProviderKind::Linear,
382 user_info_url: Some("https://api.linear.app/graphql".into()),
383 ..Self::custom(
384 client_id,
385 client_secret,
386 "https://linear.app/oauth/authorize",
387 "https://api.linear.app/oauth/token",
388 )
389 }
390 }
391
392 pub fn atlassian(client_id: &str, client_secret: &str) -> Self {
394 Self {
395 kind: ProviderKind::Atlassian,
396 user_info_url: Some("https://api.atlassian.com/me".into()),
397 ..Self::custom(
398 client_id,
399 client_secret,
400 "https://auth.atlassian.com/authorize",
401 "https://auth.atlassian.com/oauth/token",
402 )
403 }
404 }
405
406 pub fn salesforce(client_id: &str, client_secret: &str) -> Self {
409 Self {
410 kind: ProviderKind::Salesforce,
411 user_info_url: Some("https://login.salesforce.com/services/oauth2/userinfo".into()),
412 ..Self::custom(
413 client_id,
414 client_secret,
415 "https://login.salesforce.com/services/oauth2/authorize",
416 "https://login.salesforce.com/services/oauth2/token",
417 )
418 }
419 }
420
421 pub fn reddit(client_id: &str, client_secret: &str) -> Self {
424 Self {
425 kind: ProviderKind::Reddit,
426 user_info_url: Some("https://oauth.reddit.com/api/v1/me".into()),
427 ..Self::custom(
428 client_id,
429 client_secret,
430 "https://www.reddit.com/api/v1/authorize",
431 "https://www.reddit.com/api/v1/access_token",
432 )
433 }
434 }
435
436 pub fn kakao(client_id: &str, client_secret: &str) -> Self {
438 Self {
439 kind: ProviderKind::Kakao,
440 user_info_url: Some("https://kapi.kakao.com/v2/user/me".into()),
441 ..Self::custom(
442 client_id,
443 client_secret,
444 "https://kauth.kakao.com/oauth/authorize",
445 "https://kauth.kakao.com/oauth/token",
446 )
447 }
448 }
449
450 pub fn zoom(client_id: &str, client_secret: &str) -> Self {
453 Self {
454 kind: ProviderKind::Zoom { pkce: true },
455 user_info_url: Some("https://api.zoom.us/v2/users/me".into()),
456 ..Self::custom(
457 client_id,
458 client_secret,
459 "https://zoom.us/oauth/authorize",
460 "https://zoom.us/oauth/token",
461 )
462 }
463 }
464
465 pub fn zoom_without_pkce(client_id: &str, client_secret: &str) -> Self {
468 Self {
469 kind: ProviderKind::Zoom { pkce: false },
470 ..Self::zoom(client_id, client_secret)
471 }
472 }
473
474 pub fn twitter(client_id: &str, client_secret: &str) -> Self {
476 Self {
477 kind: ProviderKind::Twitter,
478 user_info_url: Some(
479 "https://api.x.com/2/users/me?user.fields=profile_image_url".into(),
480 ),
481 ..Self::custom(
482 client_id,
483 client_secret,
484 "https://x.com/i/oauth2/authorize",
485 "https://api.x.com/2/oauth2/token",
486 )
487 }
488 }
489
490 pub fn wechat(client_id: &str, client_secret: &str) -> Self {
493 Self::wechat_with_endpoints(
494 client_id,
495 client_secret,
496 "https://open.weixin.qq.com/connect/qrconnect",
497 "https://api.weixin.qq.com/sns/oauth2/access_token",
498 "https://api.weixin.qq.com/sns/oauth2/refresh_token",
499 "https://api.weixin.qq.com/sns/userinfo",
500 )
501 }
502
503 pub fn wechat_with_endpoints(
505 client_id: &str,
506 client_secret: &str,
507 auth_url: &str,
508 token_url: &str,
509 refresh_url: &str,
510 user_info_url: &str,
511 ) -> Self {
512 Self {
513 kind: ProviderKind::WeChat {
514 refresh_url: refresh_url.into(),
515 },
516 user_info_url: Some(user_info_url.into()),
517 ..Self::custom(client_id, client_secret, auth_url, token_url)
518 }
519 }
520
521 pub fn vk(client_id: &str, client_secret: &str) -> Self {
523 Self {
524 kind: ProviderKind::Vk,
525 user_info_url: Some("https://id.vk.com/oauth2/user_info".into()),
526 ..Self::custom(
527 client_id,
528 client_secret,
529 "https://id.vk.com/authorize",
530 "https://id.vk.com/oauth2/auth",
531 )
532 }
533 }
534
535 pub fn twitch(client_id: &str, client_secret: &str, options: TwitchOptions) -> Self {
537 Self {
538 kind: ProviderKind::Twitch(options),
539 ..Self::custom(
540 client_id,
541 client_secret,
542 "https://id.twitch.tv/oauth2/authorize",
543 "https://id.twitch.tv/oauth2/token",
544 )
545 }
546 }
547
548 pub fn notion(client_id: &str, client_secret: &str) -> Self {
550 Self {
551 kind: ProviderKind::Notion,
552 user_info_url: Some("https://api.notion.com/v1/users/me".into()),
553 ..Self::custom(
554 client_id,
555 client_secret,
556 "https://api.notion.com/v1/oauth/authorize",
557 "https://api.notion.com/v1/oauth/token",
558 )
559 }
560 }
561
562 pub fn roblox(client_id: &str, client_secret: &str) -> Self {
564 Self {
565 kind: ProviderKind::Roblox,
566 user_info_url: Some("https://apis.roblox.com/oauth/v1/userinfo".into()),
567 ..Self::custom(
568 client_id,
569 client_secret,
570 "https://apis.roblox.com/oauth/v1/authorize",
571 "https://apis.roblox.com/oauth/v1/token",
572 )
573 }
574 }
575
576 pub fn tiktok(client_key: &str, client_secret: &str) -> Self {
579 Self {
580 kind: ProviderKind::TikTok,
581 client_key: Some(client_key.into()),
582 user_info_url: Some("https://open.tiktokapis.com/v2/user/info/?fields=open_id,avatar_large_url,display_name,username".into()),
583 ..Self::custom(
584 "",
585 client_secret,
586 "https://www.tiktok.com/v2/auth/authorize",
587 "https://open.tiktokapis.com/v2/oauth/token/",
588 )
589 }
590 }
591
592 pub fn railway(client_id: &str, client_secret: &str) -> Self {
594 Self {
595 kind: ProviderKind::Railway,
596 user_info_url: Some("https://backboard.railway.com/oauth/me".into()),
597 ..Self::custom(
598 client_id,
599 client_secret,
600 "https://backboard.railway.com/oauth/auth",
601 "https://backboard.railway.com/oauth/token",
602 )
603 }
604 }
605
606 pub fn cloudflare(client_id: &str, client_secret: &str) -> Self {
609 Self {
610 kind: ProviderKind::Cloudflare,
611 user_info_url: Some("https://api.cloudflare.com/client/v4/user".into()),
612 ..Self::custom(
613 client_id,
614 client_secret,
615 "https://dash.cloudflare.com/oauth2/auth",
616 "https://dash.cloudflare.com/oauth2/token",
617 )
618 }
619 }
620}