firebase_rs_sdk/auth/oauth/
credential.rs1use std::convert::TryFrom;
2
3use serde_json::Value;
4use url::form_urlencoded::Serializer;
5
6use crate::auth::error::{AuthError, AuthResult};
7use crate::auth::model::AuthCredential;
8
9#[derive(Debug, Clone)]
16pub struct OAuthCredential {
17 provider_id: String,
18 sign_in_method: String,
19 raw_nonce: Option<String>,
20 token_response: Value,
21}
22
23impl OAuthCredential {
24 pub fn new(
25 provider_id: impl Into<String>,
26 sign_in_method: impl Into<String>,
27 token_response: Value,
28 ) -> Self {
29 Self {
30 provider_id: provider_id.into(),
31 sign_in_method: sign_in_method.into(),
32 raw_nonce: None,
33 token_response,
34 }
35 }
36
37 pub fn with_raw_nonce(mut self, nonce: Option<String>) -> Self {
38 self.raw_nonce = nonce;
39 self
40 }
41
42 pub fn provider_id(&self) -> &str {
43 &self.provider_id
44 }
45
46 pub fn sign_in_method(&self) -> &str {
47 &self.sign_in_method
48 }
49
50 pub fn token_response(&self) -> &Value {
51 &self.token_response
52 }
53
54 pub fn raw_nonce(&self) -> Option<&String> {
55 self.raw_nonce.as_ref()
56 }
57
58 pub fn build_post_body(&self) -> AuthResult<String> {
60 let mut serializer = Serializer::new(String::new());
61 let mut has_credential = false;
62
63 if let Some(id_token) = self
64 .token_response
65 .get("idToken")
66 .or_else(|| self.token_response.get("oauthIdToken"))
67 .and_then(Value::as_str)
68 {
69 serializer.append_pair("id_token", id_token);
70 has_credential = true;
71 }
72
73 if let Some(access_token) = self
74 .token_response
75 .get("accessToken")
76 .or_else(|| self.token_response.get("oauthAccessToken"))
77 .and_then(Value::as_str)
78 {
79 serializer.append_pair("access_token", access_token);
80 has_credential = true;
81 }
82
83 if let Some(code) = self.token_response.get("code").and_then(Value::as_str) {
84 serializer.append_pair("code", code);
85 has_credential = true;
86 }
87
88 if !has_credential {
89 return Err(AuthError::InvalidCredential(
90 "OAuth token response missing id_token/access_token/code".into(),
91 ));
92 }
93
94 if let Some(nonce) = self.raw_nonce() {
95 serializer.append_pair("nonce", nonce);
96 }
97
98 serializer.append_pair("providerId", &self.provider_id);
99
100 Ok(serializer.finish())
101 }
102}
103
104impl TryFrom<AuthCredential> for OAuthCredential {
105 type Error = AuthError;
106
107 fn try_from(credential: AuthCredential) -> Result<Self, Self::Error> {
108 let raw_nonce = credential
109 .token_response
110 .get("nonce")
111 .and_then(Value::as_str)
112 .map(|value| value.to_owned());
113
114 Ok(Self {
115 provider_id: credential.provider_id,
116 sign_in_method: credential.sign_in_method,
117 raw_nonce,
118 token_response: credential.token_response,
119 })
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use serde_json::json;
127
128 #[test]
129 fn post_body_includes_id_token() {
130 let credential = AuthCredential {
131 provider_id: "google.com".into(),
132 sign_in_method: "google.com".into(),
133 token_response: json!({ "idToken": "test-id-token" }),
134 };
135
136 let oauth = OAuthCredential::try_from(credential).unwrap();
137 let result = oauth.build_post_body().unwrap();
138 assert!(result.contains("id_token=test-id-token"));
139 assert!(result.contains("providerId=google.com"));
140 }
141
142 #[test]
143 fn post_body_includes_access_token() {
144 let credential = AuthCredential {
145 provider_id: "github.com".into(),
146 sign_in_method: "github.com".into(),
147 token_response: json!({ "accessToken": "gh-token" }),
148 };
149
150 let oauth = OAuthCredential::try_from(credential).unwrap();
151 let result = oauth.build_post_body().unwrap();
152 assert!(result.contains("access_token=gh-token"));
153 assert!(result.contains("providerId=github.com"));
154 }
155
156 #[test]
157 fn post_body_errors_when_missing_tokens() {
158 let credential = AuthCredential {
159 provider_id: "google.com".into(),
160 sign_in_method: "google.com".into(),
161 token_response: json!({}),
162 };
163
164 let oauth = OAuthCredential::try_from(credential).unwrap();
165 let err = oauth.build_post_body().unwrap_err();
166 assert!(matches!(err, AuthError::InvalidCredential(_)));
167 }
168}