Skip to main content

codex_cli/
jwt.rs

1use base64::Engine;
2use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
3use serde_json::Value;
4
5use crate::json::strip_newlines;
6
7pub fn decode_payload(token: &str) -> Option<String> {
8    let payload = token.split('.').nth(1)?;
9    if payload.is_empty() {
10        return None;
11    }
12
13    let decoded = URL_SAFE_NO_PAD
14        .decode(payload)
15        .or_else(|_| URL_SAFE.decode(payload))
16        .ok()?;
17    String::from_utf8(decoded).ok()
18}
19
20pub fn decode_payload_json(token: &str) -> Option<Value> {
21    let payload = decode_payload(token)?;
22    serde_json::from_str(&payload).ok()
23}
24
25pub fn identity_from_payload(payload: &Value) -> Option<String> {
26    nested_string(payload, "https://api.openai.com/auth", "chatgpt_user_id")
27        .or_else(|| nested_string(payload, "https://api.openai.com/auth", "user_id"))
28        .or_else(|| top_level_string(payload, "sub"))
29        .or_else(|| top_level_string(payload, "email"))
30}
31
32pub fn email_from_payload(payload: &Value) -> Option<String> {
33    top_level_string(payload, "email")
34        .or_else(|| nested_string(payload, "https://api.openai.com/auth", "email"))
35}
36
37fn nested_string(payload: &Value, parent: &str, key: &str) -> Option<String> {
38    payload
39        .get(parent)
40        .and_then(|value| value.get(key))
41        .and_then(|value| value.as_str())
42        .map(strip_newlines)
43}
44
45fn top_level_string(payload: &Value, key: &str) -> Option<String> {
46    payload
47        .get(key)
48        .and_then(|value| value.as_str())
49        .map(strip_newlines)
50}