better_auth_api/plugins/oauth/providers/
paybin.rs1use better_auth_core::{AuthError, AuthResult};
2use reqwest::header::HeaderMap;
3use serde_json::{Value, json};
4use url::Url;
5
6use super::{OAuthProvider, OAuthUserInfo, defaults::ProviderKind};
7use crate::plugins::oauth::oidc::{OidcVerifier, fetch_discovery};
8
9const DEFAULT_ISSUER: &str = "https://idp.paybin.io";
10
11impl OAuthProvider {
12 pub fn paybin(client_id: &str, client_secret: &str) -> Self {
14 Self::paybin_with_issuer(client_id, client_secret, DEFAULT_ISSUER)
15 }
16
17 pub fn paybin_with_issuer(client_id: &str, client_secret: &str, issuer: &str) -> Self {
20 let issuer = if issuer.is_empty() {
21 DEFAULT_ISSUER
22 } else {
23 issuer
24 };
25 Self {
26 kind: ProviderKind::Paybin {
27 issuer: issuer.into(),
28 },
29 ..Self::custom(
30 client_id,
31 client_secret,
32 &format!("{issuer}/oauth2/authorize"),
33 &format!("{issuer}/oauth2/token"),
34 )
35 }
36 }
37
38 pub(in crate::plugins::oauth) fn paybin_issuer(&self) -> Option<&str> {
39 match &self.kind {
40 ProviderKind::Paybin { issuer } => Some(issuer),
41 _ => None,
42 }
43 }
44}
45
46pub(in crate::plugins::oauth) async fn verify(
47 issuer: &str,
48 client_id: &str,
49 token: &str,
50 nonce: Option<&str>,
51) -> AuthResult<Value> {
52 let jwks_url = if issuer == DEFAULT_ISSUER {
53 Url::parse("https://idp.paybin.io/.well-known/jwks.json")
54 .map_err(|error| AuthError::config(error.to_string()))?
55 } else {
56 let discovery_url = format!(
57 "{}/.well-known/openid-configuration",
58 issuer.trim_end_matches('/')
59 );
60 let document = fetch_discovery(&discovery_url, &HeaderMap::new())
61 .await
62 .map_err(|error| AuthError::internal(error.to_string()))?;
63 if document.issuer.as_deref() != Some(issuer) {
64 return Err(AuthError::config(
65 "Paybin discovery must describe the configured issuer",
66 ));
67 }
68 let jwks = document
69 .jwks_uri
70 .filter(|value| !value.is_empty())
71 .ok_or_else(|| AuthError::config("Paybin discovery must declare its JWKS URI"))?;
72 Url::parse(&discovery_url)
73 .and_then(|url| url.join(&jwks))
74 .map_err(|error| AuthError::config(error.to_string()))?
75 };
76 let verifier = OidcVerifier::new(
78 jwks_url,
79 issuer.into(),
80 client_id.into(),
81 Some(vec![json!("RS256")]),
82 )
83 .map_err(|error| AuthError::internal(error.to_string()))?;
84 verifier
85 .verify(token, nonce)
86 .await
87 .map_err(|_| crate::plugins::oauth::id_token::invalid())
88}
89
90pub(super) fn decode_profile(profile: Value) -> Result<OAuthUserInfo, String> {
91 let name = ["name", "preferred_username"]
92 .into_iter()
93 .find_map(|key| {
94 profile
95 .get(key)
96 .and_then(Value::as_str)
97 .filter(|value| !value.is_empty())
98 })
99 .unwrap_or_default()
100 .to_owned();
101 let verified = super::profile_email_verified(
102 &better_auth_core::SchemaValue::from_json(profile.get("email_verified").cloned())
103 .map_err(|error| error.to_string())?,
104 )
105 .map_err(|error| error.to_string())?;
106 Ok(OAuthUserInfo {
107 id: profile
108 .get("sub")
109 .and_then(Value::as_str)
110 .ok_or("missing sub")?
111 .into(),
112 email: super::defaults::profile_email(&profile)?,
113 name: Some(name).into(),
114 image: profile
115 .get("picture")
116 .cloned()
117 .map(serde_json::from_value)
118 .transpose()
119 .map_err(|error| format!("Invalid Paybin picture: {error}"))?,
120 email_verified: Some(verified).into(),
121 additional_fields: Default::default(),
122 })
123}