use aitp_core::{Aid, Timestamp};
use serde::{Deserialize, Serialize};
use uuid::Uuid;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct Cnf {
pub jkt: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct TctClaims {
pub ver: String,
pub jti: Uuid,
pub iss: Aid,
pub sub: Aid,
pub aud: Aid,
pub iat: Timestamp,
pub exp: Timestamp,
pub grants: Vec<String>,
pub cnf: Cnf,
#[serde(skip_serializing_if = "Option::is_none")]
pub ext: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct GrantVoucherClaims {
pub ver: String,
pub iss: Aid,
pub sub: Aid,
pub grants: Vec<String>,
pub iat: Timestamp,
pub exp: Timestamp,
pub src_jti: Uuid,
#[serde(skip_serializing_if = "Option::is_none")]
pub ext: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IssuedTct {
pub token: String,
pub claims: TctClaims,
pub voucher: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct VerifiedTct {
pub token: String,
pub claims: TctClaims,
}
#[cfg(feature = "experimental-renewal")]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct TctRenewalPayload {
pub current_tct: String,
pub pop_nonce: String,
pub pop_signature: String,
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn sample_claims_json() -> serde_json::Value {
json!({
"ver": "aitp/0.2",
"jti": "550e8400-e29b-41d4-a716-446655440000",
"iss": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"sub": "aid:pubkey:A6EHv_POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg",
"aud": "aid:pubkey:A6EHv_POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg",
"iat": 1_711_900_000,
"exp": 1_711_903_600,
"grants": ["macp.mode.task.v1"],
"cnf": { "jkt": "1IG2tMH7J2wbJZnOf8LJzQitKf7LMvoAElsuDMVM54Y" },
})
}
#[test]
fn claims_round_trip() {
let claims: TctClaims = serde_json::from_value(sample_claims_json()).unwrap();
let back = serde_json::to_value(&claims).unwrap();
assert_eq!(back, sample_claims_json());
}
#[test]
fn unknown_claim_rejected_outside_ext() {
let mut v = sample_claims_json();
v.as_object_mut().unwrap().insert("rogue".into(), json!(1));
assert!(serde_json::from_value::<TctClaims>(v).is_err());
}
#[test]
fn unknown_keys_inside_ext_are_carried() {
let mut v = sample_claims_json();
v.as_object_mut()
.unwrap()
.insert("ext".into(), json!({"x-foo": {"deep": true}}));
let claims: TctClaims = serde_json::from_value(v).unwrap();
assert!(claims.ext.unwrap().contains_key("x-foo"));
}
#[test]
fn unknown_cnf_member_rejected() {
let mut v = sample_claims_json();
v["cnf"]
.as_object_mut()
.unwrap()
.insert("jwk".into(), json!({}));
assert!(serde_json::from_value::<TctClaims>(v).is_err());
}
#[test]
fn voucher_claims_round_trip() {
let v = json!({
"ver": "aitp/0.2",
"iss": "aid:pubkey:O2onvM62pC1io6jQKm8Nc2UyFXcd4kOmOsBIoYtZ2ik",
"sub": "aid:pubkey:A6EHv_POEL4dcN0Y50vAmWfk1jCbpQ1fHdyGZBJVMbg",
"grants": ["macp.mode.task.v1"],
"iat": 1_711_900_000,
"exp": 1_711_903_600,
"src_jti": "550e8400-e29b-41d4-a716-446655440001",
});
let claims: GrantVoucherClaims = serde_json::from_value(v.clone()).unwrap();
assert_eq!(serde_json::to_value(&claims).unwrap(), v);
}
}