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:
@@ -45,13 +45,6 @@ async fn handle_share_session(agent: &MvpAgent, args: &acp::ExtRequest) -> ExtRe
|
||||
);
|
||||
}
|
||||
|
||||
// Only block for ZDR teams (hard data-retention policy), not for
|
||||
// coding-data-retention opt-out — sharing is user-initiated.
|
||||
if auth.is_zdr_team() {
|
||||
return Err(acp::Error::invalid_params()
|
||||
.data("Session sharing is disabled for your team's data retention policy"));
|
||||
}
|
||||
|
||||
// Find session info by searching through summaries
|
||||
let summaries = list_summaries(None).await.map_err(|e| {
|
||||
acp::Error::internal_error().data(format!("Failed to list sessions: {}", e))
|
||||
@@ -94,7 +87,7 @@ async fn handle_share_session(agent: &MvpAgent, args: &acp::ExtRequest) -> ExtRe
|
||||
|
||||
fn require_xai_auth_for_share(
|
||||
auth_manager: &crate::auth::AuthManager,
|
||||
) -> Result<crate::auth::GrokAuth, acp::Error> {
|
||||
) -> Result<crate::auth::KimiAuth, acp::Error> {
|
||||
super::auth_gate::require_xai_auth(
|
||||
auth_manager,
|
||||
"Authentication required to share session",
|
||||
@@ -105,8 +98,8 @@ fn require_xai_auth_for_share(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::auth::GrokComConfig;
|
||||
use crate::auth::{AuthMode, GrokAuth};
|
||||
use crate::auth::KimiCodeConfig;
|
||||
use crate::auth::{AuthMode, KimiAuth};
|
||||
use chrono::{Duration, Utc};
|
||||
use std::sync::Arc;
|
||||
use tempfile::tempdir;
|
||||
@@ -117,7 +110,7 @@ mod tests {
|
||||
let dir = tempdir().expect("tempdir for share auth test");
|
||||
let mgr = Arc::new(crate::auth::AuthManager::new(
|
||||
dir.path(),
|
||||
GrokComConfig::default(),
|
||||
KimiCodeConfig::default(),
|
||||
));
|
||||
|
||||
let expires_at = Utc::now() + ttl;
|
||||
@@ -126,9 +119,8 @@ mod tests {
|
||||
// Only OIDC tokens against https://auth.x.ai (or the local-dev equivalent)
|
||||
// return true from is_xai_auth(). This is required for the share tests to
|
||||
// exercise the happy path through require_xai_auth_for_share.
|
||||
let auth = GrokAuth {
|
||||
auth_mode: AuthMode::Oidc,
|
||||
oidc_issuer: Some("https://auth.x.ai".to_string()),
|
||||
let auth = KimiAuth {
|
||||
auth_mode: AuthMode::OAuth,
|
||||
key: "test-key".into(),
|
||||
expires_at: Some(expires_at),
|
||||
create_time: Utc::now() - Duration::hours(1),
|
||||
@@ -168,7 +160,7 @@ mod tests {
|
||||
let dir = tempdir().expect("tempdir");
|
||||
let mgr = Arc::new(crate::auth::AuthManager::new(
|
||||
dir.path(),
|
||||
GrokComConfig::default(),
|
||||
KimiCodeConfig::default(),
|
||||
));
|
||||
assert!(require_xai_auth_for_share(&mgr).is_err());
|
||||
}
|
||||
@@ -178,12 +170,12 @@ mod tests {
|
||||
let dir = tempdir().expect("tempdir");
|
||||
let mgr = Arc::new(crate::auth::AuthManager::new(
|
||||
dir.path(),
|
||||
GrokComConfig::default(),
|
||||
KimiCodeConfig::default(),
|
||||
));
|
||||
|
||||
// API key is the simplest non-xAI credential (External and enterprise OIDC
|
||||
// are also rejected the same way).
|
||||
let non_xai = GrokAuth {
|
||||
let non_xai = KimiAuth {
|
||||
auth_mode: AuthMode::ApiKey,
|
||||
key: "xai-test-key".into(),
|
||||
create_time: Utc::now(),
|
||||
|
||||
Reference in New Issue
Block a user