mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-27 02:39:38 +02:00
feat(cli): keyed credentials — servers:, the token chain, login/logout (RFC-007 PR 2)
The operator config gains servers: (name -> url; never a token). A remote command whose URL prefix-matches an operator server resolves its bearer token through the keyed chain first — OMNIGRAPH_TOKEN_<NAME> env, then the [<name>] section of ~/.omnigraph/credentials (created 0600 via temp+rename, #139 finding 7; group/world-readable files refused loudly) — falling through to the legacy chain unchanged. URL keying makes §D5 rule 3 structural: a token is only ever sent to the server it is keyed to. Longest-prefix matching with a path-boundary check (http://h:8080 never matches http://h:8080-evil). Inserting the keyed hop above the legacy chain is safe by construction — no existing setup can have servers: defined. omnigraph login <name> stores/rotates one section (token from --token or one stdin line — the pipe flow keeps secrets out of shell history); omnigraph logout removes it, idempotently; logging in before declaring the server warns instead of failing (the gh model). Coverage: URL-match/no-substring-trap, credentials round-trip preserving sibling sections, 0600 write + over-permissive refusal, env-name mapping; the legacy resolve test is now hermetic against a real ~/.omnigraph and asserts byte-identical legacy behavior with no servers defined; one spawned-binary e2e walks the whole lifecycle against an authed server: refusal -> wrong-token login (stdin) -> rotate (--token) -> authorized read -> env-beats-file -> non-matching-URL negative -> logout revokes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5db42fb660
commit
a819ab500e
10 changed files with 603 additions and 4 deletions
|
|
@ -73,6 +73,29 @@ async fn main() -> Result<()> {
|
|||
};
|
||||
let http_client = build_http_client()?;
|
||||
match cli.command {
|
||||
Command::Login { name, token, json } => {
|
||||
let token = match token {
|
||||
Some(token) => token,
|
||||
None => {
|
||||
let mut line = String::new();
|
||||
std::io::stdin().read_line(&mut line)?;
|
||||
line
|
||||
}
|
||||
};
|
||||
let Some(token) = normalize_bearer_token(Some(token)) else {
|
||||
color_eyre::eyre::bail!(
|
||||
"no token provided: pass --token <TOKEN> or pipe it on stdin (echo $TOKEN | omnigraph login {name})"
|
||||
);
|
||||
};
|
||||
let operator_config = crate::operator::load_operator_config()?;
|
||||
let declared = operator_config.servers.contains_key(&name);
|
||||
let path = crate::operator::write_credential(&name, &token)?;
|
||||
finish_login(&name, &path, declared, json)?;
|
||||
}
|
||||
Command::Logout { name, json } => {
|
||||
let path = crate::operator::remove_credential(&name)?;
|
||||
finish_logout(&name, &path, json)?;
|
||||
}
|
||||
Command::Version => {
|
||||
println!("omnigraph {}", env!("CARGO_PKG_VERSION"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue