Skip to content

Commit

Permalink
oauth: username not always available in token. Use BasicClient
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Steel committed Aug 13, 2024
1 parent 27b2b59 commit 0d5cbed
Showing 1 changed file with 12 additions and 48 deletions.
60 changes: 12 additions & 48 deletions oauth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
use log::{debug, error, info, trace};
use oauth2::basic::{
BasicErrorResponse, BasicRevocationErrorResponse, BasicTokenIntrospectionResponse,
BasicTokenType,
};
use oauth2::reqwest::http_client;
use oauth2::{
AuthUrl, AuthorizationCode, Client, ClientId, CsrfToken, ExtraTokenFields, PkceCodeChallenge,
RedirectUrl, Scope, StandardRevocableToken, StandardTokenResponse, TokenResponse, TokenUrl,
basic::BasicClient, AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge,
RedirectUrl, Scope, TokenResponse, TokenUrl,
};
use serde::{Deserialize, Serialize};
use std::io;
use std::{
io::{BufRead, BufReader, Write},
Expand All @@ -18,33 +13,6 @@ use std::{
};
use url::Url;

// Define extra fields to get the username too.
// TODO: Maybe don't bother and use simpler BasicClient instead?

#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
pub struct SpotifyFields {
#[serde(rename = "username")]
#[serde(skip_serializing_if = "Option::is_none")]
pub username: Option<String>,
}
impl SpotifyFields {
pub fn username(&self) -> Option<&String> {
self.username.as_ref()
}
}
impl ExtraTokenFields for SpotifyFields {}

type SpotifyTokenResponse = StandardTokenResponse<SpotifyFields, BasicTokenType>;

type SpotifyClient = Client<
BasicErrorResponse,
SpotifyTokenResponse,
BasicTokenType,
BasicTokenIntrospectionResponse,
StandardRevocableToken,
BasicRevocationErrorResponse,
>;

fn get_authcode_stdin() -> AuthorizationCode {
println!("Provide code");
let mut buffer = String::new();
Expand Down Expand Up @@ -90,29 +58,25 @@ fn get_authcode_listener(socket_address: SocketAddr) -> AuthorizationCode {
code
}

// TODO: Return a Result
// TODO: Return a Result?
// TODO: Pass in redirect_address instead since the redirect host depends on client ID?
// TODO: Should also return username, for fun?
pub fn get_access_token(client_id: &str, redirect_port: u16) -> String {
// Must use host 127.0.0.1 with Spotify Desktop client ID.
let redirect_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), redirect_port);
let redirect_uri = format!("http://{redirect_address}/login");

let client = SpotifyClient::new(
let client = BasicClient::new(
ClientId::new(client_id.to_string()),
None,
AuthUrl::new("https://accounts.spotify.com/authorize".to_string())
.expect("Invalid authorization endpoint URL"),
Some(
TokenUrl::new("https://accounts.spotify.com/api/token".to_string())
.expect("Invalid token endpoint URL"),
),
AuthUrl::new("https://accounts.spotify.com/authorize".to_string()).unwrap(),
Some(TokenUrl::new("https://accounts.spotify.com/api/token".to_string()).unwrap()),
)
.set_redirect_uri(RedirectUrl::new(redirect_uri).expect("Invalid redirect URL"));

let (pkce_challenge, pkce_verifier) = PkceCodeChallenge::new_random_sha256();

// Generate the full authorization URL.
// Some of these scopes are unavailable for custom client IDs. Which?
let scopes = vec![
"app-remote-control",
"playlist-modify",
Expand Down Expand Up @@ -169,15 +133,15 @@ pub fn get_access_token(client_id: &str, redirect_port: u16) -> String {
});
let token_response = rx.recv().unwrap();
let token = match token_response {
Ok(tok) => tok,
Ok(tok) => {
trace!("Obtained new access token: {tok:?}");
tok
}
Err(e) => {
error!("Failed to exchange code for access token: {e:?}");
exit(1);
}
};
let username = token.extra_fields().username().unwrap().to_string();
let access_token = token.access_token().secret().to_string();
trace!("Obtained new access token for {username}: {token:?}");

access_token
token.access_token().secret().to_string()
}

0 comments on commit 0d5cbed

Please sign in to comment.