Move OAuth2 related parts into separate module

This commit is contained in:
Adrian Kumpf
2021-09-14 21:26:47 +02:00
parent 485f6eae34
commit ced95c78fc
2 changed files with 142 additions and 90 deletions
+115
View File
@@ -0,0 +1,115 @@
use std::collections::HashMap;
use oauth2::basic::BasicClient;
use oauth2::reqwest::http_client;
use oauth2::url::Url;
use oauth2::{
AuthType, AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge, PkceCodeVerifier,
RedirectUrl, Scope, TokenResponse, TokenUrl,
};
use reqwest::header::AUTHORIZATION;
use serde::Deserialize;
const AUTH_URL: &str = "https://auth.tesla.com/oauth2/v3/authorize";
const TOKEN_URL: &str = "https://auth.tesla.com/oauth2/v3/token";
const REDIRECT_URL: &str = "https://auth.tesla.com/void/callback";
const SSO_CLIENT_ID: &str = "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384";
const SSO_CLIENT_SECRET: &str = "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3";
const SSO_TOKEN_URL: &str = "https://owner-api.teslamotors.com/oauth/token";
#[derive(Deserialize, Debug)]
struct SsoTokenResponse {
access_token: String,
}
#[derive(Debug)]
pub struct Tokens {
pub access: String,
pub refresh: String,
}
pub struct Client {
oauth_client: BasicClient,
pkce_verifier: Option<PkceCodeVerifier>,
csrf_token: Option<CsrfToken>,
}
impl Client {
pub fn new() -> Self {
let client = BasicClient::new(
ClientId::new("ownerapi".to_string()),
None,
AuthUrl::new(AUTH_URL.to_string()).unwrap(),
Some(TokenUrl::new(TOKEN_URL.to_string()).unwrap()),
)
.set_auth_type(AuthType::RequestBody)
.set_redirect_uri(RedirectUrl::new(REDIRECT_URL.to_string()).unwrap());
Client {
oauth_client: client,
pkce_verifier: None,
csrf_token: None,
}
}
pub fn authorization_url(&mut self) -> Url {
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
self.pkce_verifier = Some(pkce_verifier);
let (auth_url, csrf_token) = self
.oauth_client
.authorize_url(CsrfToken::new_random)
.add_scope(Scope::new("openid".to_string()))
.add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("offline_access".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
self.csrf_token = Some(csrf_token);
auth_url
}
pub fn verify_csrf_state(&self, state: String) {
let csrf_token = self.csrf_token.as_ref().unwrap();
assert_eq!(&&state, &csrf_token.secret());
}
pub fn retrieve_tokens(self, code: AuthorizationCode) -> Tokens {
let token_result = self
.oauth_client
.exchange_code(code)
.set_pkce_verifier(self.pkce_verifier.unwrap())
.request(http_client)
.unwrap();
let short_lived_access_token = token_result.access_token().secret();
let refresh_token = token_result.refresh_token().unwrap().secret().to_string();
let mut body = HashMap::new();
body.insert("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
body.insert("client_id", SSO_CLIENT_ID);
body.insert("client_secret", SSO_CLIENT_SECRET);
let tokens: SsoTokenResponse = reqwest::blocking::Client::new()
.post(SSO_TOKEN_URL)
.header(
AUTHORIZATION,
format!("Bearer {}", short_lived_access_token),
)
.json(&body)
.send()
.unwrap()
.json()
.unwrap();
Tokens {
access: tokens.access_token,
refresh: refresh_token,
}
}
}
+27 -90
View File
@@ -1,29 +1,18 @@
use oauth2::basic::BasicClient;
use oauth2::reqwest::http_client;
use oauth2::url::Url;
use oauth2::{
AuthType, AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge, RedirectUrl,
Scope, TokenResponse, TokenUrl,
};
use reqwest;
use reqwest::header::AUTHORIZATION;
use serde::Deserialize;
use serde_json::Value;
mod auth;
use std::collections::HashMap;
use std::sync::mpsc::channel;
use std::thread;
use wry::{
application::{
event::{Event, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Window, WindowBuilder},
},
webview::{RpcRequest, RpcResponse, WebViewBuilder},
};
use oauth2::url::Url;
use oauth2::AuthorizationCode;
use serde_json::Value;
use wry::application::event::{Event, WindowEvent};
use wry::application::event_loop::{ControlFlow, EventLoop};
use wry::application::window::{Window, WindowBuilder};
use wry::webview::{RpcRequest, RpcResponse, WebViewBuilder};
const INITIALIZATION_SCRIPT: &str = r#"
(function () {
@@ -33,38 +22,11 @@ const INITIALIZATION_SCRIPT: &str = r#"
})();
"#;
#[derive(Deserialize, Debug)]
struct SsoTokenResponse {
access_token: String,
}
fn main() -> wry::Result<()> {
let (sender, receiver) = channel();
let mut client = auth::Client::new();
let auth_url = client.authorization_url();
let client = BasicClient::new(
ClientId::new("ownerapi".to_string()),
None,
AuthUrl::new("https://auth.tesla.com/oauth2/v3/authorize".to_string())?,
Some(TokenUrl::new(
"https://auth.tesla.com/oauth2/v3/token".to_string(),
)?),
)
.set_auth_type(AuthType::RequestBody)
.set_redirect_uri(RedirectUrl::new(
"https://auth.tesla.com/void/callback".to_string(),
)?);
let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();
let (auth_url, csrf_token) = client
.authorize_url(CsrfToken::new_random)
.add_scope(Scope::new("openid".to_string()))
.add_scope(Scope::new("email".to_string()))
.add_scope(Scope::new("offline_access".to_string()))
.set_pkce_challenge(pkce_challenge)
.url();
// println!("Browse to: {}", auth_url);
println!("Opening {} ...", auth_url);
let event_loop = EventLoop::new();
let event_proxy = event_loop.create_proxy();
@@ -74,13 +36,11 @@ fn main() -> wry::Result<()> {
.build(&event_loop)
.unwrap();
let (sender, receiver) = channel();
let handler = move |_window: &Window, mut req: RpcRequest| match req.method.as_str() {
"url" => {
let params = req.params.take().unwrap();
let mut args: Vec<String> = serde_json::from_value(params).unwrap();
let arg = args.swap_remove(0);
let url = Url::parse(&arg).expect("Invalid URL");
let url = parse_url(&mut req);
sender.send(url).unwrap();
Some(RpcResponse::new_result(
@@ -110,45 +70,14 @@ fn main() -> wry::Result<()> {
let state = query.get("state").expect("No state parameter found");
let code = query.get("code").expect("No code parameter found");
assert_eq!(&state.to_string(), csrf_token.secret());
client.verify_csrf_state(state.to_string());
let code = AuthorizationCode::new(code.to_string());
let token_result = client
.exchange_code(code)
.set_pkce_verifier(pkce_verifier)
.request(http_client)
.unwrap();
let req_client = reqwest::blocking::Client::new();
let mut body = HashMap::new();
body.insert("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
body.insert(
"client_id",
"81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384",
);
body.insert(
"client_secret",
"c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3",
);
let tokens: SsoTokenResponse = req_client
.post("https://owner-api.teslamotors.com/oauth/token")
.header(
AUTHORIZATION,
format!("Bearer {}", token_result.access_token().secret()),
)
.json(&body)
.send()
.unwrap()
.json()
.unwrap();
let tokens = client.retrieve_tokens(code);
println!(
"Access Token: {}\nRefresh Token: {}",
tokens.access_token,
token_result.refresh_token().unwrap().secret()
tokens.access, tokens.refresh
);
event_proxy.send_event(()).unwrap();
@@ -170,3 +99,11 @@ fn main() -> wry::Result<()> {
}
});
}
fn parse_url(req: &mut RpcRequest) -> Url {
let params = req.params.take().unwrap();
let mut args: Vec<String> = serde_json::from_value(params).unwrap();
let arg = args.swap_remove(0);
Url::parse(&arg).expect("Invalid URL")
}