1use crate::auth::token::access_token::AccessToken;
2use crate::auth::token::refresh_token::RefreshToken;
3use crate::client::{
4 EXPIRATION_TIME_BUFFER, NADEO_AUTH_URL, NADEO_REFRESH_URL, NADEO_SERVER_AUTH_URL,
5 UBISOFT_APP_ID,
6};
7use crate::request::metadata::MetaData;
8use crate::{Error, NadeoRequest, Result};
9use base64::engine::general_purpose::STANDARD as BASE64_STANDARD;
10use base64::Engine;
11use reqwest::header::{HeaderMap, HeaderValue};
12use reqwest::{Client, Response};
13use serde::{Deserialize, Serialize};
14use serde_json::{json, Value};
15use std::str::FromStr;
16
17pub mod o_auth;
18pub mod token;
19
20const UBISOFT_AUTH_URL: &str = "https://public-ubiservices.ubi.com/v3/profiles/sessions";
21
22#[derive(strum::Display, Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq)]
24pub enum AuthType {
25 #[strum(to_string = "NadeoServices")]
26 NadeoServices,
27 #[strum(to_string = "NadeoLiveServices")]
28 NadeoLiveServices,
29 OAuth,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub(crate) struct AuthInfo {
34 pub service: AuthType,
35 pub access_token: AccessToken,
36 pub refresh_token: RefreshToken,
37}
38
39impl AuthInfo {
40 pub(crate) async fn new(
41 service: AuthType,
42 ticket: &str,
43 meta_data: &MetaData,
44 client: &Client,
45 ) -> Result<Self> {
46 let mut headers = HeaderMap::new();
47 headers.insert("Content-Type", "application/json".parse().unwrap());
48
49 let auth_token = format!("ubi_v1 t={}", ticket);
50 headers.insert("Authorization", auth_token.parse().unwrap());
51 headers.insert("User-Agent", meta_data.user_agent.parse().unwrap());
52
53 let body = json!(
54 {
55 "audience": service.to_string()
56 }
57 );
58
59 let res = client
61 .post(NADEO_AUTH_URL)
62 .headers(headers)
63 .json(&body)
64 .send()
65 .await?
66 .error_for_status()?;
67
68 let json = res.json::<Value>().await?;
69
70 let access_token = AccessToken::from_str(json["accessToken"].as_str().unwrap())?;
71 let refresh_token = RefreshToken::from_str(json["refreshToken"].as_str().unwrap())?;
72
73 Ok(Self {
74 service,
75 access_token,
76 refresh_token,
77 })
78 }
79
80 pub(crate) async fn new_server(
82 service: AuthType,
83 meta_data: &MetaData,
84 username: &str,
85 password: &str,
86 client: &Client,
87 ) -> Result<Self> {
88 let mut headers = HeaderMap::new();
89 headers.insert("Content-Type", "application/json".parse().unwrap());
90
91 let auth_token = format!("Basic {}", encode_auth(username, password));
92 headers.insert("Authorization", auth_token.parse().unwrap());
93 headers.insert("User-Agent", meta_data.user_agent.parse().unwrap());
94
95 let body = json!(
96 {
97 "audience": service.to_string()
98 }
99 );
100
101 let res = client
103 .post(NADEO_SERVER_AUTH_URL)
104 .headers(headers)
105 .json(&body)
106 .send()
107 .await?
108 .error_for_status()?;
109
110 let json = res.json::<Value>().await?;
111
112 let access_token = AccessToken::from_str(json["accessToken"].as_str().unwrap())?;
113 let refresh_token = RefreshToken::from_str(json["refreshToken"].as_str().unwrap())?;
114
115 Ok(Self {
116 service,
117 access_token,
118 refresh_token,
119 })
120 }
121
122 pub(crate) async fn force_refresh(
126 &mut self,
127 meta_data: &MetaData,
128 client: &Client,
129 ) -> Result<()> {
130 let mut headers = HeaderMap::new();
131
132 let auth_token = format!("nadeo_v1 t={}", self.refresh_token.encode());
134 headers.insert("Authorization", auth_token.parse().unwrap());
135 headers.insert("Content-Type", "application/json".parse().unwrap());
136 headers.insert("User-Agent", meta_data.user_agent.parse().unwrap());
137
138 let body = json!(
139 {
140 "audience": self.service.to_string()
141 }
142 );
143
144 let res = client
145 .post(NADEO_REFRESH_URL)
146 .headers(headers)
147 .json(&body)
148 .send()
149 .await
150 .map_err(Error::from)?;
151
152 let json = res.json::<Value>().await.map_err(Error::from)?;
153
154 let access_token = AccessToken::from_str(json["accessToken"].as_str().unwrap())?;
155 let refresh_token = RefreshToken::from_str(json["refreshToken"].as_str().unwrap())?;
156
157 self.access_token = access_token;
158 self.refresh_token = refresh_token;
159
160 Ok(())
161 }
162
163 pub(crate) async fn refresh(&mut self, meta_data: &MetaData, client: &Client) -> Result<bool> {
174 if !self.expires_in() < EXPIRATION_TIME_BUFFER {
175 return Ok(false);
176 }
177
178 self.force_refresh(meta_data, client).await.map(|_| true)
179 }
180
181 pub(crate) fn expires_in(&self) -> i64 {
183 self.access_token.expires_in()
184 }
185
186 pub(crate) async fn execute(
192 &mut self,
193 request: NadeoRequest,
194 meta_data: &MetaData,
195 client: &Client,
196 ) -> Result<Response> {
197 assert_eq!(self.service, request.auth_type);
198
199 self.refresh(meta_data, client).await?;
200 let token = format!("nadeo_v1 t={}", self.access_token.encode());
201
202 let api_request = client.request(request.method, request.url);
203
204 let mut res = api_request
205 .header("Authorization", token.parse::<HeaderValue>().unwrap())
206 .header(
207 "User-Agent",
208 meta_data.user_agent.parse::<HeaderValue>().unwrap(),
209 )
210 .headers(request.headers);
211 if let Some(json) = request.body {
212 res = res.body(json);
213 }
214
215 let res = res.send().await?.error_for_status()?;
216
217 Ok(res)
218 }
219}
220
221fn encode_auth(username: &str, password: &str) -> String {
222 let auth = format!("{}:{}", username, password);
223 let auth = auth.as_bytes();
224
225 let mut b64 = String::new();
226 BASE64_STANDARD.encode_string(auth, &mut b64);
227 b64
228}
229
230pub(crate) async fn get_ubi_auth_ticket(
231 email: &str,
232 password: &str,
233 meta_data: &MetaData,
234 client: &Client,
235) -> Result<String> {
236 let mut headers = HeaderMap::new();
237
238 headers.insert("Content-Type", "application/json".parse().unwrap());
239 headers.insert("Ubi-AppId", UBISOFT_APP_ID.parse().unwrap());
240 headers.insert("User-Agent", meta_data.user_agent.parse().unwrap());
241
242 let ubi_auth_token = format!("Basic {}", encode_auth(email, password));
243 headers.insert("Authorization", ubi_auth_token.parse().unwrap());
244
245 let res = client
247 .post(UBISOFT_AUTH_URL)
248 .headers(headers)
249 .send()
250 .await?
251 .error_for_status()?;
252
253 let json = res.json::<Value>().await?;
254 let ticket = json["ticket"].as_str().unwrap().to_string();
255
256 Ok(ticket)
257}