Skip to content

Commit 01c3ab8

Browse files
committed
Add auth command with login, logout, list, and switch
- Add 'auth' subcommand to CLI with: - 'auth status': Show current authentication and selected context - 'auth login': Log in via browser OAuth with account picker - 'auth logout': Log out and clear credentials - 'auth list': Show current authenticated user - 'auth switch': Log out current user and prompt for new login Also add is_authenticated(), login(), logout() methods to DatumCloudClient.
1 parent d55d8f4 commit 01c3ab8

File tree

3 files changed

+80
-0
lines changed

3 files changed

+80
-0
lines changed

cli/src/main.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ enum Commands {
5050
#[clap(subcommand, alias = "ls")]
5151
Add(AddCommands),
5252

53+
/// Authenticate with Datum Cloud (login, logout, status).
54+
#[clap(subcommand)]
55+
Auth(AuthCommands),
56+
5357
/// Manage tunnels (create, list, update, delete) that expose local services to public hostnames.
5458
#[clap(subcommand)]
5559
Tunnel(TunnelCommands),
@@ -184,6 +188,24 @@ pub enum DiscoveryModeArg {
184188
Hybrid,
185189
}
186190

191+
#[derive(Subcommand, Debug)]
192+
pub enum AuthCommands {
193+
/// Show current authentication status.
194+
Status,
195+
196+
/// Log in to Datum Cloud (opens browser for OAuth).
197+
Login,
198+
199+
/// Log out and clear stored credentials.
200+
Logout,
201+
202+
/// List all locally authenticated users.
203+
List,
204+
205+
/// Switch to a different authenticated user (clears current and prompts for new login).
206+
Switch,
207+
}
208+
187209
#[derive(Subcommand, Debug)]
188210
pub enum TunnelCommands {
189211
/// List all tunnels in the current project.
@@ -297,6 +319,50 @@ async fn main() -> n0_error::Result<()> {
297319
.await?;
298320
println!("OK.");
299321
}
322+
Commands::Auth(args) => {
323+
let datum = DatumCloudClient::with_repo(ApiEnv::default(), repo.clone()).await?;
324+
match args {
325+
AuthCommands::Status => {
326+
if datum.is_authenticated().await? {
327+
println!("Authenticated");
328+
if let Some(ctx) = datum.selected_context() {
329+
println!(" org: {}", ctx.org_id);
330+
println!(" project: {}", ctx.project_id);
331+
}
332+
} else {
333+
println!("Not authenticated");
334+
}
335+
}
336+
AuthCommands::Login => {
337+
datum.login().await?;
338+
println!("Login successful");
339+
}
340+
AuthCommands::Logout => {
341+
datum.logout().await?;
342+
println!("Logged out");
343+
}
344+
AuthCommands::List => {
345+
let is_auth = datum.is_authenticated().await?;
346+
if is_auth {
347+
println!("Current user (active):");
348+
if let Some(ctx) = datum.selected_context() {
349+
println!(" org: {}", ctx.org_id);
350+
println!(" project: {}", ctx.project_id);
351+
}
352+
} else {
353+
println!("No authenticated users");
354+
}
355+
println!();
356+
println!("Note: Multi-user storage not yet implemented. Use 'auth switch' to log in as a different user.");
357+
}
358+
AuthCommands::Switch => {
359+
datum.logout().await?;
360+
println!("Switching users...");
361+
datum.login().await?;
362+
println!("Switched to new user");
363+
}
364+
}
365+
}
300366
Commands::Serve => {
301367
let node = ListenNode::new(repo).await?;
302368
let endpoint_id = node.endpoint_id();

lib/src/datum_cloud.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,19 @@ impl DatumCloudClient {
9696
self.auth.load()
9797
}
9898

99+
pub async fn is_authenticated(&self) -> Result<bool> {
100+
let state = self.auth.load_refreshed().await?;
101+
Ok(state.get().is_ok())
102+
}
103+
104+
pub async fn login(&self) -> Result<()> {
105+
self.auth.login().await
106+
}
107+
108+
pub async fn logout(&self) -> Result<()> {
109+
self.auth.logout().await
110+
}
111+
99112
pub fn selected_context(&self) -> Option<SelectedContext> {
100113
self.session.selected_context()
101114
}

lib/src/datum_cloud/auth.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ impl StatelessClient {
190190
.add_scope(Scope::new("profile".to_string()))
191191
.add_scope(Scope::new("email".to_string()))
192192
.add_scope(Scope::new("offline_access".to_string()))
193+
.add_extra_param("prompt", "select_account")
193194
.set_pkce_challenge(pkce_challenge)
194195
.url();
195196
debug!(auth_uri=%self.oidc.auth_uri(), "attempting login");

0 commit comments

Comments
 (0)