diff --git a/Cargo.lock b/Cargo.lock index 41fd394a6890..b5f4bc9bdd97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8444,6 +8444,7 @@ dependencies = [ "getrandom 0.3.3", "http", "indicatif", + "jiff", "js-sys", "jsonwebtoken", "rand 0.9.2", @@ -10670,6 +10671,7 @@ dependencies = [ "indexmap 2.11.4", "indicatif", "itertools 0.14.0", + "jiff", "log", "puffin", "rayon", @@ -10710,6 +10712,7 @@ dependencies = [ "similar-asserts", "tokio", "unindent", + "url", ] [[package]] diff --git a/crates/top/rerun/Cargo.toml b/crates/top/rerun/Cargo.toml index fcf53744e407..ddcb91947b96 100644 --- a/crates/top/rerun/Cargo.toml +++ b/crates/top/rerun/Cargo.toml @@ -36,7 +36,7 @@ default = [ ] ## Support the `rerun auth` command -auth = ["re_auth/cli"] +auth = ["re_auth/cli", "dep:jiff", "dep:url"] ## Enable anonymized telemetry using our analytics SDK. analytics = [ @@ -192,9 +192,11 @@ puffin.workspace = true rayon.workspace = true # Native, optional: -re_perf_telemetry = { workspace = true, features = ["tracy"], optional = true } clap = { workspace = true, optional = true, features = ["derive"] } +jiff = { workspace = true, optional = true } +re_perf_telemetry = { workspace = true, features = ["tracy"], optional = true } unindent = { workspace = true, optional = true } +url = { workspace = true, optional = true } [build-dependencies] re_build_tools.workspace = true diff --git a/crates/top/rerun/src/commands/auth.rs b/crates/top/rerun/src/commands/auth.rs index b6ec5c93dc5f..c25a1edf6a6b 100644 --- a/crates/top/rerun/src/commands/auth.rs +++ b/crates/top/rerun/src/commands/auth.rs @@ -17,6 +17,9 @@ pub enum AuthCommands { /// The access token is part of the credentials produced by `rerun auth login`, /// and is used to authorize requests to the Rerun data platform. Token(TokenCommand), + + /// Generate a fresh token. + GenerateToken(GenerateTokenCommand), } #[derive(Debug, Clone, Parser)] @@ -34,8 +37,21 @@ pub struct LoginCommand { #[derive(Debug, Clone, Parser)] pub struct TokenCommand {} +#[derive(Debug, Clone, Parser)] +pub struct GenerateTokenCommand { + /// Origin of the server to request the token from. + #[clap(long)] + server: String, + + /// Duration of the token, either in: + /// - "human time", e.g. `1 day`, or + /// - ISO 8601 duration format, e.g. `P1D`. + #[clap(long)] + expiration: String, +} + impl AuthCommands { - pub fn run(&self, runtime: &tokio::runtime::Handle) -> Result<(), re_auth::cli::Error> { + pub fn run(self, runtime: &tokio::runtime::Handle) -> Result<(), re_auth::cli::Error> { match self { Self::Login(args) => { let options = re_auth::cli::LoginOptions { @@ -46,6 +62,18 @@ impl AuthCommands { } Self::Token(_) => runtime.block_on(re_auth::cli::token()), + + Self::GenerateToken(args) => { + let server = url::Url::parse(&args.server) + .map_err(|err| re_auth::cli::Error::Generic(err.into()))? + .origin(); + let expiration = args + .expiration + .parse::() + .map_err(|err| re_auth::cli::Error::Generic(err.into()))?; + let options = re_auth::cli::GenerateTokenOptions { server, expiration }; + runtime.block_on(re_auth::cli::generate_token(options)) + } } } } diff --git a/crates/utils/re_auth/Cargo.toml b/crates/utils/re_auth/Cargo.toml index e43ecfcea45b..88c4bb04bfe9 100644 --- a/crates/utils/re_auth/Cargo.toml +++ b/crates/utils/re_auth/Cargo.toml @@ -42,6 +42,7 @@ re_log.workspace = true async-trait.workspace = true base64.workspace = true http.workspace = true +jiff = { workspace = true, features = ["serde"] } jsonwebtoken.workspace = true saturating_cast.workspace = true thiserror.workspace = true diff --git a/crates/utils/re_auth/src/cli.rs b/crates/utils/re_auth/src/cli.rs index 08b65a072a48..12af5dc977a0 100644 --- a/crates/utils/re_auth/src/cli.rs +++ b/crates/utils/re_auth/src/cli.rs @@ -4,7 +4,7 @@ use indicatif::ProgressBar; pub use crate::callback_server::Error; use crate::callback_server::OauthCallbackServer; -use crate::oauth::api::{AuthenticateWithCode, Pkce, send_async}; +use crate::oauth::api::{AuthenticateWithCode, GenerateToken, Pkce, send_async}; use crate::oauth::{self, Credentials}; pub struct LoginOptions { @@ -137,3 +137,33 @@ pub async fn login(options: LoginOptions) -> Result<(), Error> { Ok(()) } + +pub struct GenerateTokenOptions { + pub server: url::Origin, + pub expiration: jiff::Span, +} + +pub async fn generate_token(options: GenerateTokenOptions) -> Result<(), Error> { + let credentials = match oauth::load_and_refresh_credentials().await { + Ok(Some(credentials)) => credentials, + + Ok(None) => return Err(Error::Generic(NoCredentialsError.into())), + + Err(err) => { + re_log::debug!("invalid credentials: {err}"); + return Err(Error::Generic(Box::new(ExpiredCredentialsError))); + } + }; + + let res = send_async(GenerateToken { + server: options.server, + token: credentials.access_token().as_str(), + expiration: options.expiration, + }) + .await + .map_err(|err| Error::Generic(err.into()))?; + + println!("{}", res.token); + + Ok(()) +} diff --git a/crates/utils/re_auth/src/oauth/api.rs b/crates/utils/re_auth/src/oauth/api.rs index 1d162cb429e1..d7c18272acba 100644 --- a/crates/utils/re_auth/src/oauth/api.rs +++ b/crates/utils/re_auth/src/oauth/api.rs @@ -296,3 +296,38 @@ impl From for crate::oauth::User { } } } + +pub struct GenerateToken<'a> { + pub server: url::Origin, + pub token: &'a str, + pub expiration: jiff::Span, +} + +#[derive(serde::Deserialize)] +pub struct GenerateTokenResponse { + pub token: String, +} + +impl IntoRequest for GenerateToken<'_> { + type Res = GenerateTokenResponse; + + fn into_request(self) -> Result { + #[derive(serde::Serialize)] + struct Body<'a> { + token: &'a str, + expiration: jiff::Span, + } + + ehttp::Request::json( + format_args!( + "{origin}/generate-token", + origin = self.server.ascii_serialization() + ), + &Body { + token: self.token, + expiration: self.expiration, + }, + ) + .map_err(Error::Serialize) + } +} diff --git a/docs/content/reference/cli.md b/docs/content/reference/cli.md index 649877b4bf1a..ca7f29e635a2 100644 --- a/docs/content/reference/cli.md +++ b/docs/content/reference/cli.md @@ -242,6 +242,7 @@ Authentication with the redap. * `login`: Log into Rerun. * `token`: Retrieve the stored access token. +* `generate-token`: Generate a fresh token. ## rerun auth login @@ -267,6 +268,20 @@ To sign up, contact us through the form linked at > [Default: `false`] +## rerun auth generate-token + +Generate a fresh token. + +**Usage**: `rerun auth generate-token --server --expiration ` + +**Options** + +* `--server ` +> Origin of the server to request the token from. + +* `--expiration ` +> Duration of the token, either in: - "human time", e.g. `1 day`, or - ISO 8601 duration format, e.g. `P1D`. + ## rerun mcap Manipulate the contents of .mcap files.