M1/F1: Kimi Code OAuth device-code flow
Replace the xAI OAuth stack with the Kimi device authorization grant:
- kimi_oauth.rs wire layer (device_authorization + token poll + refresh
against kigi_env::oauth_host(); client_id per PRD; retryable statuses
429/5xx with backoff; expired_token restarts authorization)
- X-Msh-Device-{Name,Model,Id} headers; device_id minted uuid4-hex at
~/.kigi/device_id (0600)
- Storage: system keyring service `kigi`, entry `oauth/kimi-code`
(macOS/Windows native backends), atomic-file fallback under ~/.kigi;
official client's keyring/~/.kimi never touched
- Refresh manager: 60s tick, threshold max(300, expires_in*0.5),
401-tombstone keyed by rejected refresh token with 300s cooldown and
rotation auto-clear, cross-process lock with sibling-adoption
triple-check, sleep/wake forced refresh
- Deleted xAI machinery: enterprise OIDC (PKCE/JWKS/teams), devbox login,
external auth provider, JWT tier gating + subscription paywall stack,
X-XAI-Token-Auth marker headers, ZDR gates, /user enrichment
- kigi login / TUI /login both drive the device flow; login-host display
now derives from kigi_env::oauth_host()
- 264 auth unit/wiremock tests; live contract probe of
auth.kimi.com/api/oauth/device_authorization matches the wire shapes
Gates: check/clippy --all-targets clean, fmt, deny ok, kigi-shell lib
5131 tests green.
This commit is contained in:
@@ -370,7 +370,7 @@ pub(crate) fn record_auth_401(
|
||||
/// This function performs **exactly one** read-side acquisition of
|
||||
/// [`AuthManager`]'s internal `RwLock` -- it calls
|
||||
/// [`AuthManager::current`] once and derives both `current_key_prefix`
|
||||
/// and the mint/expiry fields from the resulting `GrokAuth`.
|
||||
/// and the mint/expiry fields from the resulting `KimiAuth`.
|
||||
///
|
||||
/// `is_stale_snapshot` is `true` only when the live `current()` token
|
||||
/// differs from the bearer the client sent. When `current()` returns
|
||||
@@ -390,7 +390,7 @@ fn compute_attribution_payload(
|
||||
// query can break down on this).
|
||||
let sent_prefix = sent_bearer.map(token_suffix).unwrap_or("");
|
||||
|
||||
// Single read-lock acquisition: pull the live `GrokAuth` (or
|
||||
// Single read-lock acquisition: pull the live `KimiAuth` (or
|
||||
// `None`) once and derive every other field from it.
|
||||
let current_auth = auth_manager.current();
|
||||
let current_prefix_owned: Option<String> = current_auth
|
||||
@@ -411,7 +411,7 @@ fn compute_attribution_payload(
|
||||
//
|
||||
// TODO: mirror the full External-with-ttl branch from
|
||||
// `AuthManager::is_token_expired` (uses
|
||||
// `grok_com_config.auth_token_ttl` when `expires_at` is `None`
|
||||
// `kimi_code_config.auth_token_ttl` when `expires_at` is `None`
|
||||
// and `auth_mode == External`). The current 2-branch fallback
|
||||
// (`expires_at` if Some else `create_time + TOKEN_TTL`) is good
|
||||
// enough for diagnostic metadata; the External-ttl branch is
|
||||
@@ -441,7 +441,7 @@ mod tests {
|
||||
|
||||
use chrono::{Duration, Utc};
|
||||
|
||||
use crate::auth::{AuthManager, GrokAuth, GrokComConfig};
|
||||
use crate::auth::{AuthManager, KimiAuth, KimiCodeConfig};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -449,17 +449,17 @@ mod tests {
|
||||
/// nothing from a developer's actual `~/.kigi/auth.json` leaks in.
|
||||
fn empty_auth_manager() -> (tempfile::TempDir, AuthManager) {
|
||||
let dir = tempfile::tempdir().expect("tempdir");
|
||||
let cfg = GrokComConfig::default();
|
||||
let cfg = KimiCodeConfig::default();
|
||||
let am = AuthManager::new(dir.path(), cfg);
|
||||
(dir, am)
|
||||
}
|
||||
|
||||
fn fresh_auth(key: &str) -> GrokAuth {
|
||||
GrokAuth {
|
||||
fn fresh_auth(key: &str) -> KimiAuth {
|
||||
KimiAuth {
|
||||
key: key.to_string(),
|
||||
create_time: Utc::now(),
|
||||
expires_at: Some(Utc::now() + Duration::hours(1)),
|
||||
..GrokAuth::test_default()
|
||||
..KimiAuth::test_default()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -553,12 +553,12 @@ mod tests {
|
||||
#[test]
|
||||
fn legacy_token_uses_two_branch_fallback() {
|
||||
let (_dir, am) = empty_auth_manager();
|
||||
let auth = GrokAuth {
|
||||
let auth = KimiAuth {
|
||||
key: "k".into(),
|
||||
create_time: Utc::now() - Duration::seconds(60),
|
||||
// No expires_at => falls through to create_time + TOKEN_TTL
|
||||
// (= 30 days).
|
||||
..GrokAuth::test_default()
|
||||
..KimiAuth::test_default()
|
||||
};
|
||||
am.hot_swap(auth);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user