Skip to content

Commit 67bf231

Browse files
committed
Revert "Auth for Jupyter Notebooks (#11855)"
This reverts commit c3e5829.
1 parent b0f3097 commit 67bf231

File tree

19 files changed

+124
-512
lines changed

19 files changed

+124
-512
lines changed

crates/utils/re_auth/src/cli.rs

Lines changed: 75 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ use std::time::Duration;
22

33
use indicatif::ProgressBar;
44

5-
use crate::OauthLoginFlow;
65
pub use crate::callback_server::Error;
7-
use crate::oauth;
8-
use crate::oauth::login_flow::OauthLoginFlowState;
6+
use crate::callback_server::OauthCallbackServer;
7+
use crate::oauth::api::{AuthenticateWithCode, Pkce, send_async};
8+
use crate::oauth::{self, Credentials};
99

1010
pub struct LoginOptions {
1111
pub open_browser: bool,
@@ -42,47 +42,92 @@ pub async fn token() -> Result<(), Error> {
4242
/// This first checks if valid credentials already exist locally,
4343
/// and doesn't perform the login flow if so, unless `options.force_login` is set to `true`.
4444
pub async fn login(options: LoginOptions) -> Result<(), Error> {
45+
let mut login_hint = None;
46+
if !options.force_login {
47+
// NOTE: If the loading fails for whatever reason, we debug log the error
48+
// and have the user login again as if nothing happened.
49+
match oauth::load_credentials() {
50+
Ok(Some(credentials)) => {
51+
login_hint = Some(credentials.user().email.clone());
52+
match oauth::refresh_credentials(credentials).await {
53+
Ok(credentials) => {
54+
println!("You're already logged in as: {}", credentials.user().email);
55+
println!("Note: We've refreshed your credentials.");
56+
println!("Note: Run `rerun auth login --force` to login again.");
57+
return Ok(());
58+
}
59+
Err(err) => {
60+
re_log::debug!("refreshing credentials failed: {err}");
61+
// Credentials are bad, login again.
62+
// fallthrough
63+
}
64+
}
65+
}
66+
67+
Ok(None) => {
68+
// No credentials yet, login as usual.
69+
// fallthrough
70+
}
71+
72+
Err(err) => {
73+
re_log::debug!(
74+
"validating credentials failed, logging user in again anyway. reason: {err}"
75+
);
76+
// fallthrough
77+
}
78+
}
79+
}
80+
81+
let p = ProgressBar::new_spinner();
82+
4583
// Login process:
4684

4785
// 1. Start web server listening for token
48-
let login_flow = match OauthLoginFlow::init(options.force_login).await? {
49-
OauthLoginFlowState::AlreadyLoggedIn(credentials) => {
50-
println!("You're already logged in as: {}", credentials.user().email);
51-
println!("Note: We've refreshed your credentials.");
52-
println!("Note: Run `rerun auth login --force` to login again.");
53-
return Ok(());
54-
}
55-
OauthLoginFlowState::LoginFlowStarted(login_flow) => login_flow,
56-
};
57-
58-
let progress_bar = ProgressBar::new_spinner();
86+
let pkce = Pkce::new();
87+
let server = OauthCallbackServer::new(&pkce, login_hint.as_deref())?;
88+
p.inc(1);
5989

6090
// 2. Open authorization URL in browser
61-
let login_url = login_flow.get_login_url();
91+
let login_url = server.get_login_url();
92+
93+
// Once the user opens the link, they are redirected to the login UI.
94+
// If they were already logged in, it will immediately redirect them
95+
// to the login callback with an authorization code.
96+
// That code is then sent by our callback page back to the web server here.
6297
if options.open_browser {
63-
progress_bar.println("Opening login page in your browser.");
64-
progress_bar.println("Once you've logged in, the process will continue here.");
65-
progress_bar.println(format!(
98+
p.println("Opening login page in your browser.");
99+
p.println("Once you've logged in, the process will continue here.");
100+
p.println(format!(
66101
"Alternatively, manually open this url: {login_url}"
67102
));
68103
webbrowser::open(login_url).ok(); // Ok to ignore error here. The user can just open the above url themselves.
69104
} else {
70-
progress_bar.println("Open the following page in your browser:");
71-
progress_bar.println(login_url);
105+
p.println("Open the following page in your browser:");
106+
p.println(login_url);
72107
}
73-
progress_bar.inc(1);
74-
75-
// 3. Wait for login to finish
76-
progress_bar.set_message("Waiting for browser…");
77-
let credentials = loop {
78-
if let Some(code) = login_flow.poll().await? {
79-
break code;
108+
p.inc(1);
109+
110+
// 3. Wait for callback
111+
p.set_message("Waiting for browser…");
112+
let code = loop {
113+
match server.check_for_browser_response()? {
114+
None => {
115+
p.inc(1);
116+
std::thread::sleep(Duration::from_millis(10));
117+
}
118+
Some(response) => break response,
80119
}
81-
progress_bar.inc(1);
82-
std::thread::sleep(Duration::from_millis(10));
83120
};
84121

85-
progress_bar.finish_and_clear();
122+
// 4. Exchange code for credentials
123+
let auth = send_async(AuthenticateWithCode::new(&code, &pkce))
124+
.await
125+
.map_err(|err| Error::Generic(err.into()))?;
126+
127+
// 5. Store credentials
128+
let credentials = Credentials::from_auth_response(auth.into())?.ensure_stored()?;
129+
130+
p.finish_and_clear();
86131

87132
println!(
88133
"Success! You are now logged in as {}",

crates/utils/re_auth/src/lib.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
1010
#[cfg(not(target_arch = "wasm32"))]
1111
mod error;
12-
1312
#[cfg(not(target_arch = "wasm32"))]
1413
mod provider;
1514

@@ -32,8 +31,6 @@ pub use token::{Jwt, TokenError};
3231

3332
#[cfg(not(target_arch = "wasm32"))]
3433
pub use error::Error;
35-
#[cfg(all(feature = "oauth", not(target_arch = "wasm32")))]
36-
pub use oauth::login_flow::OauthLoginFlow;
3734
#[cfg(not(target_arch = "wasm32"))]
3835
pub use provider::{Claims, RedapProvider, SecretKey, VerificationOptions};
3936
#[cfg(not(target_arch = "wasm32"))]

crates/utils/re_auth/src/oauth.rs

Lines changed: 21 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use crate::Jwt;
55
pub mod api;
66
mod storage;
77

8-
#[cfg(not(target_arch = "wasm32"))]
9-
pub mod login_flow;
10-
118
/// Tokens with fewer than this number of seconds left before expiration
129
/// are considered expired. This ensures tokens don't become expired
1310
/// during network transit.
@@ -56,9 +53,6 @@ pub enum CredentialsRefreshError {
5653

5754
#[error("failed to deserialize credentials: {0}")]
5855
MalformedToken(#[from] MalformedTokenError),
59-
60-
#[error("no refresh token available")]
61-
NoRefreshToken,
6256
}
6357

6458
/// Refresh credentials if they are expired.
@@ -79,11 +73,7 @@ pub async fn refresh_credentials(
7973
-credentials.access_token().remaining_duration_secs()
8074
);
8175

82-
let Some(refresh_token) = &credentials.refresh_token else {
83-
return Err(CredentialsRefreshError::NoRefreshToken);
84-
};
85-
86-
let response = api::refresh(refresh_token).await?;
76+
let response = api::refresh(&credentials.refresh_token).await?;
8777
let credentials = Credentials::from_auth_response(response)?
8878
.ensure_stored()
8979
.map_err(|err| CredentialsRefreshError::Store(err.0))?;
@@ -191,12 +181,7 @@ pub enum VerifyError {
191181
#[derive(Debug, serde::Serialize, serde::Deserialize)]
192182
pub struct Credentials {
193183
user: User,
194-
195-
// Refresh token is optional because it may not be available in some cases,
196-
// like the Jupyter notebook Wasm viewer. In that case, the SDK handles
197-
// token refreshes.
198-
refresh_token: Option<RefreshToken>,
199-
184+
refresh_token: RefreshToken,
200185
access_token: AccessToken,
201186
}
202187

@@ -224,42 +209,14 @@ impl Credentials {
224209
pub fn from_auth_response(
225210
res: api::RefreshResponse,
226211
) -> Result<InMemoryCredentials, MalformedTokenError> {
227-
let access_token = AccessToken::try_from_unverified_jwt(Jwt(res.access_token))?;
212+
let access_token = AccessToken::unverified(Jwt(res.access_token))?;
228213
Ok(InMemoryCredentials(Self {
229214
user: res.user,
230-
refresh_token: Some(RefreshToken(res.refresh_token)),
215+
refresh_token: RefreshToken(res.refresh_token),
231216
access_token,
232217
}))
233218
}
234219

235-
/// Creates credentials from raw token strings.
236-
///
237-
/// Warning: it does not check the signature of the access token.
238-
pub fn try_new(
239-
access_token: String,
240-
refresh_token: Option<String>,
241-
email: String,
242-
) -> Result<InMemoryCredentials, MalformedTokenError> {
243-
// TODO(aedm): check signature of the JWT token
244-
let claims = Jwt(access_token.clone()).unverified_claims()?;
245-
246-
let user = User {
247-
id: claims.sub,
248-
email,
249-
};
250-
let access_token = AccessToken {
251-
token: access_token,
252-
expires_at: claims.exp,
253-
};
254-
let refresh_token = refresh_token.map(RefreshToken);
255-
256-
Ok(InMemoryCredentials(Self {
257-
user,
258-
access_token,
259-
refresh_token,
260-
}))
261-
}
262-
263220
pub fn access_token(&self) -> &AccessToken {
264221
&self.access_token
265222
}
@@ -309,11 +266,25 @@ impl AccessToken {
309266
/// Construct an [`AccessToken`] without verifying it.
310267
///
311268
/// The token should come from a trusted source, like the Rerun auth API.
312-
pub(crate) fn try_from_unverified_jwt(jwt: Jwt) -> Result<Self, MalformedTokenError> {
313-
let claims = jwt.unverified_claims()?;
269+
pub(crate) fn unverified(jwt: Jwt) -> Result<Self, MalformedTokenError> {
270+
use base64::prelude::*;
271+
272+
let (_header, rest) = jwt
273+
.as_str()
274+
.split_once('.')
275+
.ok_or(MalformedTokenError::MissingHeaderPayloadSeparator)?;
276+
let (payload, _signature) = rest
277+
.split_once('.')
278+
.ok_or(MalformedTokenError::MissingPayloadSignatureSeparator)?;
279+
let payload = BASE64_URL_SAFE_NO_PAD
280+
.decode(payload)
281+
.map_err(MalformedTokenError::Base64)?;
282+
let payload: RerunCloudClaims =
283+
serde_json::from_slice(&payload).map_err(MalformedTokenError::Serde)?;
284+
314285
Ok(Self {
315286
token: jwt.0,
316-
expires_at: claims.exp,
287+
expires_at: payload.exp,
317288
})
318289
}
319290
}

crates/utils/re_auth/src/oauth/login_flow.rs

Lines changed: 0 additions & 92 deletions
This file was deleted.

crates/utils/re_auth/src/token.rs

Lines changed: 0 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
1-
use base64::Engine as _;
2-
use base64::prelude::BASE64_URL_SAFE_NO_PAD;
31
use jsonwebtoken::decode_header;
42

5-
use crate::oauth::{MalformedTokenError, RerunCloudClaims};
6-
73
#[derive(Debug, thiserror::Error)]
84
pub enum TokenError {
95
#[error("token does not seem to be a valid JWT token: {0}")]
@@ -19,22 +15,6 @@ impl Jwt {
1915
pub fn as_str(&self) -> &str {
2016
&self.0
2117
}
22-
23-
pub fn unverified_claims(&self) -> Result<RerunCloudClaims, MalformedTokenError> {
24-
let (_header, rest) = self
25-
.as_str()
26-
.split_once('.')
27-
.ok_or(MalformedTokenError::MissingHeaderPayloadSeparator)?;
28-
let (payload, _signature) = rest
29-
.split_once('.')
30-
.ok_or(MalformedTokenError::MissingPayloadSignatureSeparator)?;
31-
let payload = BASE64_URL_SAFE_NO_PAD
32-
.decode(payload)
33-
.map_err(MalformedTokenError::Base64)?;
34-
let claims: RerunCloudClaims =
35-
serde_json::from_slice(&payload).map_err(MalformedTokenError::Serde)?;
36-
Ok(claims)
37-
}
3818
}
3919

4020
impl TryFrom<String> for Jwt {

0 commit comments

Comments
 (0)