From c121616c600dde4807c0d54e1e52020703f64c69 Mon Sep 17 00:00:00 2001 From: Marc 'risson' Schmitt Date: Wed, 17 Jun 2026 16:26:09 +0200 Subject: [PATCH] add claims structs Signed-off-by: Marc 'risson' Schmitt --- WIP.md | 3 +- src/outpost/proxy/claims.rs | 88 +++++++++++++++++++++++++++++++++++++ src/outpost/proxy/mod.rs | 1 + 3 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/outpost/proxy/claims.rs diff --git a/WIP.md b/WIP.md index f6519d5bda..fc10ecc007 100644 --- a/WIP.md +++ b/WIP.md @@ -62,8 +62,9 @@ step below is meant to be one focused, compilable, testable commit. ### Phase A — pure types & crypto primitives (unit-testable, no axum) -- [ ] **A1.** `Claims` + `ProxyClaims` serde types (mirror `types/claims.go`; `groups`/ +- [x] **A1.** `Claims` + `ProxyClaims` serde types (mirror `types/claims.go`; `groups`/ `entitlements` default-empty `Vec`, `raw_token`, `ak_proxy`). Round-trip JSON test. + Done in `src/outpost/proxy/claims.rs` (container-level `#[serde(default)]`; 3 tests). - [ ] **A2.** Add `jsonwebtoken` (`aws_lc_rs` feature). `OAuthState` type + HS256 encode/decode signed with `cookie_secret`; **disable exp/aud validation**, enforce issuer. Tests: round-trip + issuer-mismatch rejection. diff --git a/src/outpost/proxy/claims.rs b/src/outpost/proxy/claims.rs new file mode 100644 index 0000000000..cc5c5685c7 --- /dev/null +++ b/src/outpost/proxy/claims.rs @@ -0,0 +1,88 @@ +//! Claims carried in the OIDC ID token and persisted in the session. + +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; + +/// Proxy-specific claims. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub(crate) struct ProxyClaims { + pub(crate) user_attributes: HashMap, + pub(crate) backend_override: String, + pub(crate) host_header: String, + pub(crate) is_superuser: bool, +} + +/// Claims extracted from a verified ID token. +#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)] +#[serde(default)] +pub(crate) struct Claims { + pub(crate) sub: String, + pub(crate) exp: i64, + pub(crate) email: String, + pub(crate) email_verified: bool, + pub(crate) name: String, + pub(crate) preferred_username: String, + pub(crate) groups: Vec, + pub(crate) entitlements: Vec, + pub(crate) sid: String, + pub(crate) ak_proxy: Option, + pub(crate) raw_token: String, +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use serde_json::json; + + use super::{Claims, ProxyClaims}; + + #[test] + fn round_trip() { + let claims = Claims { + sub: "user-uuid".to_owned(), + exp: 1_700_000_000_i64, + email: "user@example.com".to_owned(), + email_verified: true, + name: "Example User".to_owned(), + preferred_username: "akadmin".to_owned(), + groups: vec!["authentik Admins".to_owned(), "users".to_owned()], + entitlements: vec!["can_do_thing".to_owned()], + sid: "session-id".to_owned(), + ak_proxy: Some(ProxyClaims { + user_attributes: HashMap::from([( + "additionalHeaders".to_owned(), + json!({"X-Foo": "bar"}), + )]), + backend_override: "http://backend:8080".to_owned(), + host_header: "internal.example.com".to_owned(), + is_superuser: true, + }), + raw_token: "the.raw.jwt".to_owned(), + }; + + let serialized = serde_json::to_string(&claims).expect("failed to serialize claims"); + let deserialized: Claims = + serde_json::from_str(&serialized).expect("failed to deserialize claims"); + + assert_eq!(claims, deserialized); + } + + #[test] + fn missing_fields_default() { + // A minimal ID token may omit most claims; they should default rather + // than fail to deserialize (mirrors Go's `encoding/json`). + let claims: Claims = serde_json::from_value(json!({ + "sub": "user-uuid", + })) + .expect("failed to deserialize minimal claims"); + + assert_eq!(claims.sub, "user-uuid"); + assert!(claims.groups.is_empty()); + assert!(claims.entitlements.is_empty()); + assert!(claims.ak_proxy.is_none()); + assert_eq!(claims.exp, 0_i64); + } +} diff --git a/src/outpost/proxy/mod.rs b/src/outpost/proxy/mod.rs index c8447a28b8..b7a5c1c636 100644 --- a/src/outpost/proxy/mod.rs +++ b/src/outpost/proxy/mod.rs @@ -23,6 +23,7 @@ use tracing::{debug, error, info, instrument, warn}; use crate::outpost::{Outpost, OutpostController, proxy::application::Application}; mod application; +mod claims; mod handlers; #[derive(Debug, Default, FromArgs, PartialEq, Eq)]