Skip to main content

deslicer_cli/commands/auth/
status.rs

1use 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}