fix(coder-lite): validate tokens before trusting them and avoid hyperlinks in login messages

f203128d5e68 · AtlantisPleb · · parent caac30fa8fd8

fix(coder-lite): validate tokens before trusting them and avoid hyperlinks in login messages

ensure_authenticated now verifies OPENAGENTS_API_KEY and any stored
token by calling /api/v1/user before skipping the login prompt. Invalid
credentials are cleared and the user is prompted to re-authorize.

do_login now verifies the freshly granted token and reports
'Authenticated as <login>.' instead of a bare URL, which removes the
OSC 8 hyperlink that caused the flickering underline.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By
Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified crates/coder-lite/src/interactive.rs

Diff

1 file changed, +34 -10

crates/coder-lite/src/interactive.rs modified +34 -10

@@ -25,8 +25,9 @@ use crossterm::{

25 25
    },
26 26
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
27 27
};
28
use openagents_cli::auth::{CredentialStore, DeviceClient, open_browser};
28
use openagents_cli::auth::{CredentialStore, DeviceClient, Secret, open_browser};
29 29
use openagents_cli::composer::complete::{Completion, complete};
30
use openagents_cli::repo::RepoClient;
30 31
use openagents_cli::composer::history::History;
31 32
use openagents_cli::composer::ComposerAction;
32 33
use openagents_cli::runtime::Lane;

@@ -208,18 +209,37 @@ pub async fn run_tui(options: SessionOptions) -> Result<(), Box<dyn std::error::

208 209
    Ok(())
209 210
}
210 211
211
/// If no credential is already in the environment or store, start a GitHub
212
/// If no valid credential is in the environment or store, start a GitHub
212 213
/// device-authorization flow and store the resulting token for the runtime to
213 214
/// spend. The TUI is not yet on the alternate screen, so this prints normally.
214 215
async fn ensure_authenticated() -> Result<(), Box<dyn std::error::Error>> {
215
    if std::env::var("OPENAGENTS_API_KEY").is_ok() {
216
        return Ok(());
217
    }
218
219 216
    let endpoint = openagents_cli::auth::resolve_endpoint(None, None)?;
220 217
    let store = CredentialStore::for_origin(&endpoint.origin);
221
    if store.find_token()?.is_some() {
222
        return Ok(());
218
219
    if let Ok(key) = std::env::var("OPENAGENTS_API_KEY") {
220
        let token = Secret::new(key);
221
        if RepoClient::new(&endpoint.origin, Some(token))
222
            .authenticated_user()
223
            .await
224
            .is_ok()
225
        {
226
            return Ok(());
227
        }
228
        eprintln!("OPENAGENTS_API_KEY did not authenticate; it will be ignored.");
229
        // SAFETY: this process owns the environment; the TUI has not started.
230
        unsafe { std::env::remove_var("OPENAGENTS_API_KEY") };
231
    }
232
233
    if let Some(held) = store.find_token()? {
234
        if RepoClient::new(&endpoint.origin, Some(held.token.clone()))
235
            .authenticated_user()
236
            .await
237
            .is_ok()
238
        {
239
            return Ok(());
240
        }
241
        eprintln!("Stored token did not authenticate; logging in again.");
242
        store.remove()?;
223 243
    }
224 244
225 245
    println!("Press Enter to log in with GitHub.");

@@ -237,7 +257,8 @@ async fn ensure_authenticated() -> Result<(), Box<dyn std::error::Error>> {

237 257
}
238 258
239 259
/// Start the GitHub device-authorization flow against the current endpoint,
240
/// open the approval URL in the browser, poll for the token, and store it.
260
/// open the approval URL in the browser, poll for the token, store it, and
261
/// verify it by reading the authenticated user.
241 262
pub async fn do_login() -> Result<String, Box<dyn std::error::Error>> {
242 263
    let endpoint = openagents_cli::auth::resolve_endpoint(None, None)?;
243 264
    let client = DeviceClient::new(&endpoint.origin);

@@ -250,7 +271,10 @@ pub async fn do_login() -> Result<String, Box<dyn std::error::Error>> {

250 271
    let store = CredentialStore::for_origin(&endpoint.origin);
251 272
    let _ = store.store(&token)?;
252 273
253
    Ok(format!("Authenticated for {}.", endpoint.origin))
274
    let user = RepoClient::new(&endpoint.origin, Some(token))
275
        .authenticated_user()
276
        .await?;
277
    Ok(format!("Authenticated as {}.", user.login))
254 278
}
255 279
256 280
/// The columns the composer soft-wraps to: the frame's width less its border

This page updates live while a promote is in flight · changelog