Skip to main content

pas_client/
well_known.rs

1use serde::{Deserialize, Serialize};
2
3use crate::types::KeyId;
4
5#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6#[non_exhaustive]
7pub struct WellKnownPasetoDocument {
8    pub issuer: String,
9    pub version: String,
10    pub keys: Vec<WellKnownPasetoKey>,
11    pub cache_ttl_seconds: u64,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15#[non_exhaustive]
16pub struct WellKnownPasetoKey {
17    pub kid: KeyId,
18    pub public_key_hex: String,
19    pub status: WellKnownKeyStatus,
20    #[serde(with = "time::serde::rfc3339")]
21    pub created_at: time::OffsetDateTime,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
25#[serde(rename_all = "lowercase")]
26#[non_exhaustive]
27pub enum WellKnownKeyStatus {
28    Active,
29    Retiring,
30    Revoked,
31}
32
33#[cfg(test)]
34#[allow(clippy::unwrap_used)]
35mod tests {
36    use super::*;
37
38    const SAMPLE_JSON: &str = r#"{
39        "issuer": "accounts.ppoppo.com",
40        "version": "v4.public",
41        "keys": [
42            {
43                "kid": "key-001",
44                "public_key_hex": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
45                "status": "active",
46                "created_at": "2026-01-01T00:00:00Z"
47            },
48            {
49                "kid": "key-002",
50                "public_key_hex": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
51                "status": "retiring",
52                "created_at": "2025-06-01T00:00:00Z"
53            }
54        ],
55        "cache_ttl_seconds": 3600
56    }"#;
57
58    #[test]
59    fn deserialize_well_known_document() {
60        let doc: WellKnownPasetoDocument = serde_json::from_str(SAMPLE_JSON).unwrap();
61
62        assert_eq!(doc.issuer, "accounts.ppoppo.com");
63        assert_eq!(doc.version, "v4.public");
64        assert_eq!(doc.keys.len(), 2);
65        assert_eq!(doc.cache_ttl_seconds, 3600);
66    }
67
68    #[test]
69    fn deserialize_key_fields() {
70        let doc: WellKnownPasetoDocument = serde_json::from_str(SAMPLE_JSON).unwrap();
71
72        let key = &doc.keys[0];
73        assert_eq!(key.kid.to_string(), "key-001");
74        assert_eq!(key.status, WellKnownKeyStatus::Active);
75
76        let retiring = &doc.keys[1];
77        assert_eq!(retiring.status, WellKnownKeyStatus::Retiring);
78    }
79
80    #[test]
81    fn serde_roundtrip() {
82        let doc: WellKnownPasetoDocument = serde_json::from_str(SAMPLE_JSON).unwrap();
83        let json = serde_json::to_string(&doc).unwrap();
84        let doc2: WellKnownPasetoDocument = serde_json::from_str(&json).unwrap();
85        assert_eq!(doc, doc2);
86    }
87
88    #[test]
89    fn deserialize_revoked_status() {
90        let json = r#"{
91            "kid": "key-003",
92            "public_key_hex": "cc",
93            "status": "revoked",
94            "created_at": "2024-01-01T00:00:00Z"
95        }"#;
96        let key: WellKnownPasetoKey = serde_json::from_str(json).unwrap();
97        assert_eq!(key.status, WellKnownKeyStatus::Revoked);
98    }
99
100    #[cfg(feature = "token")]
101    #[test]
102    fn convert_well_known_key_to_public_key() {
103        use crate::token::PublicKey;
104
105        let doc: WellKnownPasetoDocument = serde_json::from_str(SAMPLE_JSON).unwrap();
106        let key = &doc.keys[0];
107
108        let result = PublicKey::try_from(key);
109        assert!(result.is_ok());
110        assert_eq!(result.unwrap().as_bytes().len(), 32);
111    }
112}