deslicer_cli/commands/auth/
status.rs1use base64::Engine;
2use clap::Args as ClapArgs;
3use serde_json::{json, Value};
4
5use crate::ci::{self, CiPlatform, OidcError};
6use crate::Ctx;
7
8#[derive(ClapArgs)]
9pub struct Args {
10 #[arg(long)]
11 pub environment: Option<String>,
12}
13
14pub async fn run(ctx: Ctx, args: Args) -> i32 {
15 let platform = ci::detect_platform(ctx.ci_override);
16 let audience = ci::AUDIENCE;
17
18 let token_result = ci::provider_for(platform).fetch_token(audience).await;
19
20 let (jwt_header, jwt_claims) = match &token_result {
21 Ok(jwt) => decode_jwt_parts(jwt),
22 Err(OidcError::MissingEnv(msg)) if platform == CiPlatform::Local => {
23 eprintln!("{msg}");
24 (Value::Null, Value::Null)
25 }
26 Err(err) => {
27 eprintln!("failed to fetch OIDC token: {err}");
28 (Value::Null, Value::Null)
29 }
30 };
31
32 let resolved_backend = match &token_result {
33 Ok(jwt) => {
34 match crate::resolver::resolve(&ctx, jwt, platform, args.environment.as_deref(), None)
35 .await
36 {
37 Ok(backend) => json!({
38 "observer_api_url": backend.observer_api_url.as_str(),
39 "resolution_path": backend.resolution_path,
40 "audience": backend.audience,
41 }),
42 Err(err) => json!(err.to_string()),
43 }
44 }
45 Err(_) => Value::Null,
46 };
47
48 let audit = if std::env::var("DESLICER_DEV_TOKEN").is_ok() {
49 json!("not configured")
50 } else {
51 Value::Null
52 };
53
54 let output = json!({
55 "platform": platform.header_value(),
56 "audience": audience,
57 "jwt_header": jwt_header,
58 "jwt_claims": jwt_claims,
59 "resolved_backend": resolved_backend,
60 "audit": audit,
61 });
62
63 let text = match serde_json::to_string_pretty(&output) {
64 Ok(s) => s,
65 Err(_) => output.to_string(),
66 };
67 println!("{text}");
68 0
69}
70
71fn decode_jwt_parts(jwt: &str) -> (Value, Value) {
72 let mut parts = jwt.split('.');
73 let header = parts
74 .next()
75 .and_then(decode_jwt_segment)
76 .unwrap_or(Value::Null);
77 let mut claims = parts
78 .next()
79 .and_then(decode_jwt_segment)
80 .unwrap_or(Value::Null);
81 if !claims.is_null() {
82 redact_sensitive_claims(&mut claims);
83 }
84 (header, claims)
85}
86
87fn decode_jwt_segment(segment: &str) -> Option<Value> {
88 let bytes = base64::engine::general_purpose::URL_SAFE_NO_PAD
89 .decode(segment)
90 .ok()?;
91 serde_json::from_slice(&bytes).ok()
92}
93
94fn redact_sensitive_claims(value: &mut Value) {
95 match value {
96 Value::Object(map) => {
97 for (key, val) in map.iter_mut() {
98 let key_lower = key.to_ascii_lowercase();
99 if key_lower.contains("token")
100 || key_lower.contains("secret")
101 || key_lower.contains("key")
102 {
103 *val = Value::String("REDACTED".to_string());
104 } else {
105 redact_sensitive_claims(val);
106 }
107 }
108 }
109 Value::Array(arr) => {
110 for item in arr.iter_mut() {
111 redact_sensitive_claims(item);
112 }
113 }
114 _ => {}
115 }
116}