iss/sub/aud/sid/exp claims for the broker-mediated attach flow. "Pair-scoped" means the credential identifies which home daemon (sub) may attach to which target daemon (aud) for which session (sid) — a stolen token only works between the two daemons it was issued for. Existing JwtClaims stays as a compat path; NAP-243 wires the new claims into the daemon's verifier and the registry's issuer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -300,6 +300,9 @@ pub enum ClientControl {
|
|||||||
/// Issued by the registry, validated locally by the daemon. Short-lived
|
/// Issued by the registry, validated locally by the daemon. Short-lived
|
||||||
/// (~5 min). The daemon checks `daemon_id` matches itself and `session_id`
|
/// (~5 min). The daemon checks `daemon_id` matches itself and `session_id`
|
||||||
/// is one of its live sessions.
|
/// is one of its live sessions.
|
||||||
|
///
|
||||||
|
/// Superseded by [`PairAttachClaims`] for the broker-mediated attach flow
|
||||||
|
/// (NAP-242). Retained as a compat path while clients migrate.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct JwtClaims {
|
pub struct JwtClaims {
|
||||||
pub session_id: Uuid,
|
pub session_id: Uuid,
|
||||||
@@ -311,6 +314,27 @@ pub struct JwtClaims {
|
|||||||
pub iat: Option<i64>,
|
pub iat: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pair-scoped attach credential. Issued by the registry inside
|
||||||
|
/// [`AttachCandidates::jwt`]; verified by the target daemon for direct
|
||||||
|
/// transports (T1/T2) and gated by `allow_inbound` after JWT verification.
|
||||||
|
///
|
||||||
|
/// "Pair-scoped" means: `sub` (home daemon) may initiate one attach to
|
||||||
|
/// `aud` (target daemon) for session `sid`, valid until `exp`. A stolen
|
||||||
|
/// token still only works between the two daemons it was issued for.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct PairAttachClaims {
|
||||||
|
/// Issuer — registry id.
|
||||||
|
pub iss: String,
|
||||||
|
/// Subject — home daemon-id (the daemon initiating attach).
|
||||||
|
pub sub: String,
|
||||||
|
/// Audience — target daemon-id (the daemon owning the session).
|
||||||
|
pub aud: String,
|
||||||
|
/// Session id this credential is scoped to.
|
||||||
|
pub sid: Uuid,
|
||||||
|
/// Expiry (unix seconds). Short-lived; v1 default 60s.
|
||||||
|
pub exp: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@@ -762,4 +786,37 @@ mod tests {
|
|||||||
let parsed: JwtClaims = serde_json::from_value(no_iat).unwrap();
|
let parsed: JwtClaims = serde_json::from_value(no_iat).unwrap();
|
||||||
assert!(parsed.iat.is_none());
|
assert!(parsed.iat.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pair_attach_claims_round_trip() {
|
||||||
|
let c = PairAttachClaims {
|
||||||
|
iss: "registry.naps62".into(),
|
||||||
|
sub: "laptop".into(),
|
||||||
|
aud: "konishi".into(),
|
||||||
|
sid: Uuid::from_u128(7),
|
||||||
|
exp: 1_700_000_060,
|
||||||
|
};
|
||||||
|
assert_eq!(c, round_trip(&c));
|
||||||
|
|
||||||
|
// Standard JWT short claim names — keep them on the wire as-is so the
|
||||||
|
// existing `jsonwebtoken` validators don't need a custom name map.
|
||||||
|
let v = serde_json::to_value(&c).unwrap();
|
||||||
|
assert_eq!(v["iss"], "registry.naps62");
|
||||||
|
assert_eq!(v["sub"], "laptop");
|
||||||
|
assert_eq!(v["aud"], "konishi");
|
||||||
|
assert_eq!(v["exp"], 1_700_000_060_i64);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn pair_attach_claims_reject_missing_audience() {
|
||||||
|
// aud is mandatory — without it a token isn't pair-scoped at all and
|
||||||
|
// the target daemon couldn't verify itself as the intended recipient.
|
||||||
|
let bad = serde_json::json!({
|
||||||
|
"iss": "registry.naps62",
|
||||||
|
"sub": "laptop",
|
||||||
|
"sid": Uuid::from_u128(7),
|
||||||
|
"exp": 1_700_000_060_i64,
|
||||||
|
});
|
||||||
|
assert!(serde_json::from_value::<PairAttachClaims>(bad).is_err());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user