mirror of
https://github.com/ruvnet/RuView
synced 2026-08-11 20:41:44 +00:00
fix(cli): resolve scope from the token on read, and add whoami --refresh
Two gaps that only appear with a credential file written by an earlier build —
i.e. exactly the case a fresh-login test never exercises.
1. `whoami` printed "Scope: (not reported)" for an existing file. The previous
commit resolved scope from the token claim at WRITE time only, so files
written before it stayed blank forever. `effective_scope()` now falls back at
READ time, which fixes existing files and any client that stored only what
the token response carried. The token is authoritative either way; the stored
field is a convenience copy.
2. `whoami` said the token "will refresh on next use" — a promise nothing kept,
because no command called `ensure_fresh`. The refresh path was implemented
and unit-tested but never actually run. `--refresh` exercises it, and goes
through `Session::ensure_fresh` rather than reimplementing a second, subtly
different refresh, so it inherits the single-flight guarantee and the
persist-before-return ordering.
It is a flag, not silent behaviour: refreshing rotates the stored refresh
token — identity spends the old one — so it is a state change, not a read.
VERIFIED AGAINST PRODUCTION, end to end:
whoami on a pre-existing file -> Scope: sensing:read (was "not reported")
whoami --refresh -> both tokens rotated
refresh sha256[:12] 50cfa06b -> 2d37617a
access sha256[:12] 61eebe8d -> 14d8e8de
status: expired -> valid
refreshed token GET /api/v1/models -> 200
refreshed token POST /api/v1/train/start -> 401 (still read-scoped)
That exercises the rotating-refresh path against identity's real reuse
detection — the one place a bug costs the user their session rather than a
retry — and confirms the scope gate survives a refresh.
Tests: 82 with --features login, 44 default, 501 sensing-server.
Co-Authored-By: Ruflo & AQE
This commit is contained in:
@@ -93,6 +93,20 @@ impl StoredCredentials {
|
||||
}
|
||||
}
|
||||
|
||||
/// The granted scope, falling back to the access token's own claim.
|
||||
///
|
||||
/// Resolved at *read* time, not just at write time, so credential files
|
||||
/// written before the fallback existed — or by any client that stores only
|
||||
/// what the token response carried — still report correctly instead of
|
||||
/// showing "(not reported)" forever. The token is the authoritative source
|
||||
/// either way; the stored field is a convenience copy.
|
||||
pub fn effective_scope(&self) -> Option<String> {
|
||||
self.scope
|
||||
.clone()
|
||||
.filter(|s| !s.is_empty())
|
||||
.or_else(|| scope_from_access_token(&self.access_token))
|
||||
}
|
||||
|
||||
/// Does the access token need replacing?
|
||||
///
|
||||
/// A missing `expires_at` counts as expired. Guessing a lifetime here would
|
||||
|
||||
Reference in New Issue
Block a user