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:
@@ -17,7 +17,6 @@ use std::path::{Path, PathBuf};
|
||||
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::auth::ForceLoginTeam;
|
||||
use kigi_tools::types::config_source::ConfigSource;
|
||||
use kigi_tools::util::truncate::estimate_tokens;
|
||||
|
||||
@@ -63,7 +62,6 @@ pub struct InspectReport {
|
||||
pub project_trusted: bool,
|
||||
pub project_instructions: Vec<InstructionFile>,
|
||||
pub permissions: PermissionsReport,
|
||||
pub login_policy: LoginPolicyReport,
|
||||
pub hooks: Vec<HookEntry>,
|
||||
pub skills: Vec<SkillEntry>,
|
||||
pub agents: Vec<AgentEntry>,
|
||||
@@ -139,20 +137,6 @@ pub struct SkippedRule {
|
||||
pub reason: String,
|
||||
}
|
||||
|
||||
/// Enterprise login-hardening policy resolved from `[grok_com_config]`
|
||||
/// (TOML + env). Surfaced so admins can verify the deployment loaded it.
|
||||
/// The team pin is admin policy, not a secret, so it is shown verbatim.
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct LoginPolicyReport {
|
||||
/// Raw `disable_api_key_auth` knob (env `KIGI_DISABLE_API_KEY_AUTH`).
|
||||
pub disable_api_key_auth: Option<bool>,
|
||||
/// Configured team pin: single string, list, or null when unset.
|
||||
pub force_login_team_uuid: Option<ForceLoginTeam>,
|
||||
/// Resolved verdict — true when either knob forces first-party login.
|
||||
pub api_key_auth_disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct HookEntry {
|
||||
@@ -395,7 +379,6 @@ async fn build_report(cwd: &Path) -> InspectReport {
|
||||
project_trusted,
|
||||
project_instructions: instructions,
|
||||
permissions,
|
||||
login_policy: login_policy_report(parsed_config.as_ref()),
|
||||
hooks,
|
||||
skills,
|
||||
agents,
|
||||
@@ -615,20 +598,6 @@ async fn list_permissions(cwd: &Path) -> PermissionsReport {
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolves the enterprise login-hardening knobs from the merged config
|
||||
/// (`[grok_com_config]`, the `[auth]` alias, and env overrides) so admins can
|
||||
/// confirm the deployment's auth policy actually loaded.
|
||||
fn login_policy_report(config: Option<&crate::agent::config::Config>) -> LoginPolicyReport {
|
||||
let grok_com_config = config
|
||||
.map(|c| c.grok_com_config.clone())
|
||||
.unwrap_or_default();
|
||||
LoginPolicyReport {
|
||||
api_key_auth_disabled: grok_com_config.api_key_auth_disabled(),
|
||||
disable_api_key_auth: grok_com_config.disable_api_key_auth,
|
||||
force_login_team_uuid: grok_com_config.force_login_team_uuid,
|
||||
}
|
||||
}
|
||||
|
||||
/// Discovers hooks with every vendor enabled so compatibility can be annotated later.
|
||||
fn list_hooks(
|
||||
git_root: Option<&Path>,
|
||||
@@ -1174,19 +1143,6 @@ fn print_columns<T>(
|
||||
}
|
||||
}
|
||||
|
||||
/// Render the team pin for the human view: single value, comma-joined list,
|
||||
/// or an explicit empty-list marker (which fails closed at login).
|
||||
fn format_force_login_team(team: &Option<ForceLoginTeam>) -> String {
|
||||
match team {
|
||||
None => "(none)".to_string(),
|
||||
Some(ForceLoginTeam::Single(s)) => s.clone(),
|
||||
Some(ForceLoginTeam::AnyOf(list)) if list.is_empty() => {
|
||||
"(empty -- fail closed)".to_string()
|
||||
}
|
||||
Some(ForceLoginTeam::AnyOf(list)) => list.join(", "),
|
||||
}
|
||||
}
|
||||
|
||||
/// Human label for an enforced setting. Uses product vocabulary, not the
|
||||
/// internal field names (no `ui.yolo` / `--yolo` / `permission_mode`).
|
||||
fn enforced_label(p: &EnforcedPolicy) -> String {
|
||||
@@ -1342,24 +1298,6 @@ fn print_human(r: &InspectReport) {
|
||||
}
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(" Login Policy");
|
||||
println!(
|
||||
" {TREE} disable_api_key_auth: {}",
|
||||
match r.login_policy.disable_api_key_auth {
|
||||
Some(v) => v.to_string(),
|
||||
None => "(unset)".to_string(),
|
||||
}
|
||||
);
|
||||
println!(
|
||||
" {TREE} force_login_team_uuid: {}",
|
||||
format_force_login_team(&r.login_policy.force_login_team_uuid)
|
||||
);
|
||||
println!(
|
||||
" {TREE} api_key_auth_disabled: {}",
|
||||
r.login_policy.api_key_auth_disabled
|
||||
);
|
||||
|
||||
print_columns(
|
||||
"Skills",
|
||||
&r.skills,
|
||||
|
||||
Reference in New Issue
Block a user