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:
@@ -141,7 +141,7 @@ pub struct SessionRegistryClient {
|
||||
raw_client: reqwest::Client,
|
||||
client: reqwest_middleware::ClientWithMiddleware,
|
||||
base_url: String,
|
||||
credentials: crate::util::grok_auth_credentials::GrokAuthCredentials,
|
||||
credentials: crate::util::kigi_auth_credentials::KigiAuthCredentials,
|
||||
session_id: Option<String>,
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ impl SessionRegistryClient {
|
||||
raw_client: http_client.clone(),
|
||||
client: reqwest_middleware::ClientBuilder::new(http_client).build(),
|
||||
base_url: base_url.into(),
|
||||
credentials: crate::util::grok_auth_credentials::GrokAuthCredentials::new(Some(
|
||||
credentials: crate::util::kigi_auth_credentials::KigiAuthCredentials::new(Some(
|
||||
user_token.into(),
|
||||
)),
|
||||
session_id: None,
|
||||
@@ -549,7 +549,7 @@ mod tests {
|
||||
/// Verify per-request auth resolve picks up rotated tokens.
|
||||
#[tokio::test]
|
||||
async fn session_registry_client_uses_active_auth_for_each_request() {
|
||||
use crate::auth::{AuthManager, AuthMode, GrokAuth, GrokComConfig};
|
||||
use crate::auth::{AuthManager, AuthMode, KimiAuth, KimiCodeConfig};
|
||||
use axum::{Router, response::IntoResponse, routing::post};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::net::SocketAddr;
|
||||
@@ -575,14 +575,14 @@ mod tests {
|
||||
tokio::spawn(async move { axum::serve(listener, router).await.unwrap() });
|
||||
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let am = Arc::new(AuthManager::new(dir.path(), GrokComConfig::default()));
|
||||
am.hot_swap(GrokAuth {
|
||||
let am = Arc::new(AuthManager::new(dir.path(), KimiCodeConfig::default()));
|
||||
am.hot_swap(KimiAuth {
|
||||
key: "fresh-from-auth-manager".into(),
|
||||
auth_mode: AuthMode::ApiKey,
|
||||
create_time: Utc::now(),
|
||||
user_id: "user-42".into(),
|
||||
expires_at: Some(Utc::now() + Duration::hours(1)),
|
||||
..GrokAuth::test_default()
|
||||
..KimiAuth::test_default()
|
||||
});
|
||||
|
||||
let client = SessionRegistryClient::new(format!("http://{addr}"), "STALE-build-time-token")
|
||||
|
||||
Reference in New Issue
Block a user