Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OAuth process made by a struct, allowing customization options. #1462

Open
wants to merge 23 commits into
base: dev
Choose a base branch
from

Conversation

SharliBeicon
Copy link

Hello!

I'm currently working on a side project using this library. While working on it, I felt the need to open the auth URL automatically and customize the message returned by the socket. I added this functionality in my own fork, but I thought you might find it useful, so here's a Pull Request for it. If you don’t think it’s useful or it doesn’t meet your standards, feel free to close it!

I implemented it using a builder pattern that returns an OAuthClient with two functions: get_access_token() and refresh_token().

The good old get_access_token() is now marked for deprecation to allow for a smooth transition to the new structs. I also removed a weird overhead in the function, where it spawned a thread only to wait for its completion right after—so I figured it could just be removed.

I’ve also modified the OAuth example so you can see how this implementation works.

Copy link
Member

@photovoltex photovoltex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice job! Overall pretty clean code, had just some nitpicks and minor comments here and there.

Thanks for adding full documentation out of the box. To put the cherry on top you could add #![warn(missing_docs)] at the top of the crate to enforce documentation of all public items in the future.

I suppose this will supersede #1396?

CHANGELOG.md Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/examples/response.html Outdated Show resolved Hide resolved
oauth/Cargo.toml Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@kingosticks kingosticks left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I created this I envisioned it being useful as a standalone library function, therefore I intentionally did not make it async. I personally find it frustrating when stuff is async for no reason and it means the caller now needs to drag something like tokio in too.

@photovoltex
Copy link
Member

When I created this I envisioned it being useful as a standalone library function, therefore I intentionally did not make it async. I personally find it frustrating when stuff is async for no reason and it means the caller now needs to drag something like tokio in too.

What about a crate feature then, that would gives the option to op-out (or in) from async?

I personally like that methods are exposed as async, as the user then can decide how to handle it. But I can also see the appeal of having a sync blocking option.

@SharliBeicon
Copy link
Author

When I created this I envisioned it being useful as a standalone library function, therefore I intentionally did not make it async. I personally find it frustrating when stuff is async for no reason and it means the caller now needs to drag something like tokio in too.

What about a crate feature then, that would gives the option to op-out (or in) from async?

I personally like that methods are exposed as async, as the user then can decide how to handle it. But I can also see the appeal of having a sync blocking option.

Or maybe, thanks to the freedom provided by the Struct, there could be 4 methods (two sync and two async) coexisting and each user could choose the one that suits its needs.

@roderickvd
Copy link
Member

...or: the async function just wraps a blocking spawn and waits for it to return.

My train of thought to select the best alternative:

  • Yes, not being async imposes less requirements on others, but we use async throughout librespot anyway.
  • First time I notice we're using reqwest here where we're relying on hyper elsewhere. I like consistency. I would not mind moving to reqwest because I don't think we need the lower-level access hyper. Totally outside of this PR, just thinking out loud.

@SharliBeicon
Copy link
Author

I've added methods to handle this both async and sync, completed the documentation for the module and some other minor changes.

oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
Copy link
Member

@photovoltex photovoltex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks a lot for documenting the whole crate :D

even tho I just suggested it as addition not as requirement

Some general point on your documentation. If you look at other rust docs, it usually starts with a short summary and then follows up with some details or remarks in paragraphs. It's most noticeably at the methods/function. Most other docs work quite well tho^^

oauth/examples/oauth_async.rs Show resolved Hide resolved
oauth/examples/oauth_sync.rs Show resolved Hide resolved
oauth/src/lib.rs Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
@photovoltex
Copy link
Member

First time I notice we're using reqwest here where we're relying on hyper elsewhere. I like consistency. I would not mind moving to reqwest because I don't think we need the lower-level access hyper. Totally outside of this PR, just thinking out loud.

@roderickvd Just took a looked into the topic and it seems reqwest "just" wraps hyper and primarily tries to provide an easier way of making http requests. So in general reqwest is just an easier version of hyper. If we wouldn't need the server feature in discovery (or find a replacement for it) it might be worth to switch completely to reqwest for consistency.

@SharliBeicon
Copy link
Author

SharliBeicon commented Feb 4, 2025

even tho I just suggested it as addition not as requirement

@photovoltex Don't worry! I did it with pleasure.

SharliBeicon and others added 4 commits February 4, 2025 21:03
Co-authored-by: Felix Prillwitz <[email protected]>
Co-authored-by: Felix Prillwitz <[email protected]>
Co-authored-by: Felix Prillwitz <[email protected]>
Co-authored-by: Felix Prillwitz <[email protected]>
Copy link
Member

@photovoltex photovoltex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one more thing from my side.

oauth/src/lib.rs Outdated Show resolved Hide resolved
Co-authored-by: Felix Prillwitz <[email protected]>
photovoltex
photovoltex previously approved these changes Feb 5, 2025
oauth/src/lib.rs Outdated Show resolved Hide resolved
oauth/src/lib.rs Outdated Show resolved Hide resolved
Co-authored-by: Nick Steel <[email protected]>
Co-authored-by: Nick Steel <[email protected]>
}

/// Synchronously obtain a new valid OAuth token from `refresh_token`
pub fn refresh_token(&self, refresh_token: &str) -> Result<OAuthToken, OAuthError> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name refresh_token as function and parameter might be a bit misleading, as it is refers to the token that refreshes the access_token, but here it also the method which refreshes the access_token and not referring to the refresh_token itself. Probably something like get_refreshed_access_token might be a better explanation without overloading two meaning, but it's totally not wrong how it is, so we could also keep it that way.

}
};
last_credentials = Some(Credentials::with_access_token(access_token));
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
)
)
.open_in_browser()

@kingosticks
Copy link
Contributor

Finally got around to taking it for a spin. I'm not sure I like what veil does. It's not longer possible to use the library to quickly obtain an access token, all the good info is redacted! It's also redcated way more than it should.

[2025-02-06T21:47:31Z INFO  librespot_oauth] OAuth server listening on 127.0.0.1:8898
[2025-02-06T21:47:38Z TRACE librespot_oauth] Exchange AuthorizationCode([redacted]) for access token
[2025-02-06T21:47:38Z TRACE librespot_oauth] Obtained new access token: StandardTokenResponse { access_token: AccessToken([redacted]), token_type: Bearer, expires_in: Some(3600), refresh_token: Some(RefreshToken([redacted])), scopes: Some([Scope("streaming")]), extra_fields: EmptyExtraTokenFields }
OAuth Token: OAuthToken {
    access_token: "BQD_***-******************************************_**********************************************************__******************************--*************************************************************************mbk",
    refresh_token: "AQB*****************-*************************************************************_*********************************************9zQ",
    expires_at: Ins**** {
        **_***: ****,
        **_****: ******111,
    },
    token_type: "Be**er",
    scopes: [
        "str***ing",
    ],
}
[2025-02-06T21:47:38Z TRACE librespot_oauth] Obtained new access token: StandardTokenResponse { access_token: AccessToken([redacted]), token_type: Bearer, expires_in: Some(3600), refresh_token: Some(RefreshToken([redacted])), scopes: Some([Scope("playlist-modify"), Scope("ugc-image-upload"), Scope("user-follow-read"), Scope("user-read-email"), Scope("user-read-private"), Scope("app-remote-control"), Scope("streaming"), Scope("user-follow-modify"), Scope("user-modify-playback-state"), Scope("user-library-modify"), Scope("playlist-modify-public"), Scope("playlist-read"), Scope("user-read-birthdate"), Scope("user-top-read"), Scope("playlist-read-private"), Scope("playlist-read-collaborative"), Scope("transfer-auth-session"), Scope("user-modify-private"), Scope("playlist-modify-private"), Scope("user-modify"), Scope("user-library-read"), Scope("user-personalized"), Scope("user-read-play-history"), Scope("user-read-playback-state"), Scope("user-read-currently-playing"), Scope("user-read-recently-played"), Scope("user-read-playback-position")]), extra_fields: EmptyExtraTokenFields }
New refreshed OAuth Token: OAuthToken {
    access_token: "BQB**********************************_****-*******_*************_*************_****************_****-**********************************************_***_***-*******************************************-****************-****************************-********************************-**************************_********************************************_***********************_*****************16I",
    refresh_token: "AQB***********-***************************************_************************-******************************************-********NJg",
    expires_at: Ins**** {
        **_***: ****,
        **_****: ******942,
    },
    token_type: "Be**er",
    scopes: [
        "pla*****-******",
        "***-*****-******",
        "****-******-****",
        "****-****-*****",
        "****-****-*******",
        "***-******-*******",
        "*********",
        "****-******-******",
        "****-******-********-*****",
        "****-*******-******",
        "********-******-******",
        "********-****",
        "****-****-*********",
        "****-***-****",
        "********-****-*******",
        "********-****-*************",
        "********-****-*******",
        "****-******-*******",
        "********-******-*******",
        "****-******",
        "****-*******-****",
        "****-************",
        "****-****-****-*******",
        "****-****-********-*****",
        "****-****-*********-*******",
        "****-****-********-******",
        "****-****-********-*****ion",
    ],
}

@roderickvd
Copy link
Member

roderickvd commented Feb 6, 2025

Finally got around to taking it for a spin. I'm not sure I like what veil does. It's not longer possible to use the library to quickly obtain an access token, all the good info is redacted! It's also redcated way more than it should.

You can configure which fields it redacts:

#[derive(Redact)]
pub enum Credentials {
    Login {
        email: String,

        // only redact the password
        #[redact]
        password: String,
    }
}

You can also set how it redacts.

@SharliBeicon
Copy link
Author

SharliBeicon commented Feb 6, 2025

I thought that was the behavior requested in previous comments. To not log sensible information but with the library having all the information to do authorized requests. If its expected to get all the info through the standard output, I don't think veil is useful at all.

@kingosticks
Copy link
Contributor

Ok, that's good. Ideally it would redact just the tokens (not the other things) and only for logs. Ideally there would still be a way to print out the full un-redacted info, which is what the examples should do.

@photovoltex
Copy link
Member

photovoltex commented Feb 6, 2025

I was initially thinking about something like this when I notate the idea^^;.

println!("Test: {:*<8.4}", "some long string that will only print out `some` with 4 following `*`");

Cuts of 4 chars, and amends * until it reaches the requested amount (here 8, so 4 * are added).

ref:

When I saw the veil change I thought it might just make sense or work, but I somewhat didn't expect it to censor the debug output and well didn't tested it myself. In general it probably makes more sense to keep the info visible and rather keep a look out what we print onto stdout with something similar to the above formatting.

But this solution is only viable for a single print/log, so users of the lib would need to handle it on their own if they want to keep the token redacted form the logs.

@kingosticks
Copy link
Contributor

kingosticks commented Feb 6, 2025

We could undo this aspect and sort it out in a later PR?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants