Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion crates/uv-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ pub use credentials::{Credentials, Username};
pub use index::{AuthPolicy, Index, Indexes};
pub use keyring::KeyringProvider;
pub use middleware::AuthMiddleware;
pub use pyx::{DEFAULT_TOLERANCE_SECS, PyxOAuthTokens, PyxTokenStore, PyxTokens, TokenStoreError};
pub use pyx::{
DEFAULT_TOLERANCE_SECS, PyxJwt, PyxOAuthTokens, PyxTokenStore, PyxTokens, TokenStoreError,
};
pub use realm::Realm;
pub use service::{Service, ServiceParseError};
pub use store::{AuthBackend, AuthScheme, TextCredentialStore, TomlCredentialError};
Expand Down
22 changes: 14 additions & 8 deletions crates/uv-auth/src/pyx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -368,9 +368,9 @@ impl PyxTokenStore {
tolerance_secs: u64,
) -> Result<PyxTokens, TokenStoreError> {
// Decode the access token.
let jwt = Jwt::decode(match &tokens {
PyxTokens::OAuth(PyxOAuthTokens { access_token, .. }) => access_token.as_str(),
PyxTokens::ApiKey(PyxApiKeyTokens { access_token, .. }) => access_token.as_str(),
let jwt = PyxJwt::decode(match &tokens {
PyxTokens::OAuth(PyxOAuthTokens { access_token, .. }) => access_token,
PyxTokens::ApiKey(PyxApiKeyTokens { access_token, .. }) => access_token,
})?;

// If the access token is expired, refresh it.
Expand Down Expand Up @@ -503,14 +503,20 @@ impl TokenStoreError {

/// The payload of the JWT.
#[derive(Debug, serde::Deserialize)]
struct Jwt {
exp: Option<i64>,
pub struct PyxJwt {
/// The expiration time of the JWT, as a Unix timestamp.
pub exp: Option<i64>,
/// The issuer of the JWT.
pub iss: Option<String>,
/// The name of the organization, if any.
#[serde(rename = "urn:pyx:org_name")]
pub name: Option<String>,
}

impl Jwt {
impl PyxJwt {
/// Decode the JWT from the access token.
fn decode(access_token: &str) -> Result<Self, JwtError> {
let mut token_segments = access_token.splitn(3, '.');
pub fn decode(access_token: &AccessToken) -> Result<Self, JwtError> {
let mut token_segments = access_token.as_str().splitn(3, '.');

let _header = token_segments.next().ok_or(JwtError::MissingHeader)?;
let payload = token_segments.next().ok_or(JwtError::MissingPayload)?;
Expand Down
23 changes: 15 additions & 8 deletions crates/uv/src/commands/auth/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ use url::Url;
use uuid::Uuid;

use uv_auth::{
AccessToken, AuthBackend, Credentials, PyxOAuthTokens, PyxTokenStore, PyxTokens, Service,
TextCredentialStore,
AccessToken, AuthBackend, Credentials, PyxJwt, PyxOAuthTokens, PyxTokenStore, PyxTokens,
Service, TextCredentialStore,
};
use uv_client::{AuthIntegration, BaseClient, BaseClientBuilder};
use uv_distribution_types::IndexUrl;
Expand Down Expand Up @@ -45,12 +45,19 @@ pub(crate) async fn login(
.auth_integration(AuthIntegration::NoAuthMiddleware)
.build();

pyx_login_with_browser(&pyx_store, &client, &printer).await?;
writeln!(
printer.stderr(),
"Logged in to {}",
pyx_store.api().bold().cyan()
)?;
let access_token = pyx_login_with_browser(&pyx_store, &client, &printer).await?;
let jwt = PyxJwt::decode(&access_token)?;

if let Some(name) = jwt.name.as_deref() {
writeln!(printer.stderr(), "Logged in to {}", name.bold().cyan())?;
} else {
writeln!(
printer.stderr(),
"Logged in to {}",
pyx_store.api().bold().cyan()
)?;
}

return Ok(ExitStatus::Success);
}

Expand Down
Loading