nils_common/provider_runtime/
jwt.rs1use base64::Engine;
2use base64::engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD};
3use serde_json::Value;
4
5use super::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}