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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ members = [
opt-level = 1

[workspace.package]
version = "0.0.7-alpha"
version = "0.0.8-alpha"
edition = "2021"
license = "MIT"
repository = "https://github.com/lag-app/cli"
Expand Down
1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ tracing-subscriber = { workspace = true }
parking_lot = "0.12"
futures-util = "0.3"
rand = "0.8"
base64 = "0.22"

[target.'cfg(target_os = "macos")'.dependencies]
objc = "0.2"
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::time::{Duration, Instant};
// Refresh 5 minutes before expiry to avoid hitting 401
const REFRESH_BUFFER: Duration = Duration::from_secs(300);
// Supabase default token lifetime
const TOKEN_LIFETIME: Duration = Duration::from_secs(3600);
const TOKEN_LIFETIME: Duration = Duration::from_secs(28800);

pub struct ApiClient {
client: reqwest::Client,
Expand Down
41 changes: 39 additions & 2 deletions crates/cli/src/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,52 @@

use crate::config::{self, Credentials};
use anyhow::{anyhow, Result};
use base64::Engine;
use rand::Rng;
use url::Url;

const SUPABASE_URL: &str = "https://base.trylag.com";
const SUPABASE_ANON_KEY: &str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InVmeXlsZGp5c3pjamtsYW5ucnRzIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzEzNjQxNjYsImV4cCI6MjA4Njk0MDE2Nn0.WntE5XNUuzNs5j-OnK0ZMG2sxrfPTSCGi8dgdfWlCrw";
const WEB_URL: &str = "https://trylag.com";

pub fn require_auth() -> Result<Credentials> {
config::load_credentials().ok_or_else(|| anyhow!("Not logged in. Run `lag login` first."))
pub fn is_token_expired(token: &str) -> bool {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return true;
}
let payload = match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(parts[1]) {
Ok(bytes) => bytes,
Err(_) => return true,
};
let json: serde_json::Value = match serde_json::from_slice(&payload) {
Ok(v) => v,
Err(_) => return true,
};
let exp = match json.get("exp").and_then(|v| v.as_i64()) {
Some(e) => e,
None => return true,
};
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs() as i64;
now >= exp
}

pub async fn ensure_auth() -> Result<Credentials> {
if let Some(creds) = config::load_credentials() {
if !is_token_expired(&creds.access_token) {
return Ok(creds);
}
// Access token expired — try refreshing
match refresh_token(&creds.refresh_token).await {
Ok(refreshed) => return Ok(refreshed),
Err(_) => {
let _ = config::clear_credentials();
}
}
}
login_flow().await
}

pub async fn login_flow() -> Result<Credentials> {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crossterm::terminal;
use std::io::Write;

pub async fn run(action: Option<ChatAction>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

match action {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/dms.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crossterm::terminal;
use std::io::Write;

pub async fn run(action: Option<DmsAction>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

match action {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/friends.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::cli::FriendsAction;
use anyhow::Result;

pub async fn run(action: Option<FriendsAction>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

match action {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/join.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ pub async fn run(
output_device: Option<String>,
with_chat: bool,
) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

// Resolve server and room
Expand Down
22 changes: 17 additions & 5 deletions crates/cli/src/commands/login.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,23 @@ use crate::config;
use anyhow::Result;

pub async fn run() -> Result<()> {
if config::load_credentials().is_some() {
println!("Already logged in. Use `lag logout` first to switch accounts.");
return Ok(());
if let Some(creds) = config::load_credentials() {
if !auth::is_token_expired(&creds.access_token) {
println!("Already logged in. Use `lag logout` first to switch accounts.");
return Ok(());
}
// Access token expired — try refreshing
match auth::refresh_token(&creds.refresh_token).await {
Ok(_) => {
println!("Session refreshed. You are logged in.");
return Ok(());
}
Err(_) => {
let _ = config::clear_credentials();
println!("Session expired. Logging in again...");
}
}
}

auth::login_flow().await?;
Ok(())
}
Expand All @@ -23,7 +35,7 @@ pub async fn logout() -> Result<()> {
}

pub async fn whoami() -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

let user: serde_json::Value = api.get("/users/me").await?;
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use crossterm::terminal;
use std::io::{self, Write};

pub async fn run(name_or_id: Option<String>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;
let mut api = ApiClient::new(creds)?;

match name_or_id {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/commands/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::ws::{WsClient, WsClientMessage};
use anyhow::Result;

pub async fn run(status: Option<String>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;

match status {
Some(s) => {
Expand Down
2 changes: 1 addition & 1 deletion crates/cli/src/tui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use ratatui::prelude::*;
use std::io;

pub async fn run(_server: Option<String>) -> Result<()> {
let creds = auth::require_auth()?;
let creds = auth::ensure_auth().await?;

// Suggest setup on first run
let settings_path = crate::config::config_dir().join("audio-settings.json");
Expand Down
Loading