aauth_protocol/
resource.rs1use serde_json::{json, Value};
5
6use crate::budget::Budget;
7use crate::discovery::KeyResolver;
8use crate::error::{Error, Result};
9use crate::httpsig::{self, Incoming, Verified, VerifyOptions};
10use crate::identifier::is_server_identifier;
11use crate::keys::SigningKey;
12use crate::metadata::dwk;
13use crate::requirement::Requirement;
14use crate::token::{mint, verify_token, TokenKind, VerifiedToken, VerifyTokenOptions};
15
16pub const DEFAULT_RESOURCE_TOKEN_LIFETIME: i64 = 300;
19
20#[derive(Clone, Debug)]
22pub struct Authenticated {
23 pub signature: Verified,
25 pub token: VerifiedToken,
27}
28
29pub async fn authenticate(
38 request: &Incoming<'_>,
39 resource: &str,
40 accept: &[TokenKind],
41 options: &VerifyOptions,
42 resolver: &dyn KeyResolver,
43) -> Result<Authenticated> {
44 let signature = httpsig::verify(request, options, &crate::discovery::NoKeys).await?;
45 let Some(jwt) = signature.jwt() else {
46 return Err(crate::SignatureError::new("unsupported_scheme", "sign with sig=jwt").into());
47 };
48 let token = verify_token(
49 &VerifyTokenOptions {
50 now: options.now,
51 ..VerifyTokenOptions::new(jwt, &signature.thumbprint, resource, accept)
52 },
53 resolver,
54 )
55 .await?;
56 Ok(Authenticated { signature, token })
57}
58
59#[derive(Clone, Debug)]
62pub struct ResourceTokenParams<'a> {
63 pub resource: &'a str,
65 pub presented: &'a VerifiedToken,
67 pub agent_jkt: &'a str,
69 pub scope: &'a str,
71 pub budget: Option<&'a Budget>,
73 pub r3: Option<(&'a str, &'a str)>,
76 pub mission_expires_at: Option<i64>,
78 pub now: i64,
80 pub lifetime: i64,
82}
83
84impl<'a> ResourceTokenParams<'a> {
85 pub fn new(
88 resource: &'a str,
89 presented: &'a VerifiedToken,
90 agent_jkt: &'a str,
91 scope: &'a str,
92 ) -> Self {
93 Self {
94 resource,
95 presented,
96 agent_jkt,
97 scope,
98 budget: None,
99 r3: None,
100 mission_expires_at: None,
101 now: crate::now_unix(),
102 lifetime: DEFAULT_RESOURCE_TOKEN_LIFETIME,
103 }
104 }
105
106 pub fn budget(mut self, budget: &'a Budget) -> Self {
108 self.budget = Some(budget);
109 self
110 }
111
112 pub fn r3(mut self, uri: &'a str, s256: &'a str) -> Self {
114 self.r3 = Some((uri, s256));
115 self
116 }
117}
118
119pub fn resource_token(key: &SigningKey, params: &ResourceTokenParams<'_>) -> Result<String> {
123 if !is_server_identifier(params.resource) {
124 return Err(Error::Invalid(format!(
125 "{} is not a server identifier",
126 params.resource
127 )));
128 }
129 if params.scope.is_empty() {
130 return Err(Error::Invalid("a resource token needs a scope".into()));
131 }
132 let presented = params.presented;
133 let ps = presented.ps().unwrap_or(presented.iss.as_str());
135 let jti = presented.jti().ok_or_else(|| {
136 Error::Invalid("the presented token has no jti, so a resource token cannot name it".into())
137 })?;
138 let exp = match params.mission_expires_at {
139 Some(end) => (params.now + params.lifetime).min(end),
140 None => params.now + params.lifetime,
141 };
142 if exp <= params.now {
143 return Err(Error::Invalid("the mission has already ended".into()));
144 }
145 let mut claims = json!({
146 "iss": params.resource,
147 "dwk": dwk::RESOURCE,
148 "aud": ps,
149 "ps": ps,
150 "jti": crate::random_id(),
151 "sub": presented.sub,
152 "presented_jti": jti,
153 "person_token_jti": jti,
155 "agent_jkt": params.agent_jkt,
156 "scope": params.scope,
157 "iat": params.now,
158 "exp": exp,
159 });
160 if let Some(mission) = presented.mission_s256() {
161 claims["mission_s256"] = json!(mission);
162 }
163 if let Some(tenant) = presented.tenant() {
164 claims["tenant"] = json!(tenant);
165 }
166 if let Some(budget) = params.budget {
167 claims["budget"] = serde_json::to_value(budget).unwrap_or(Value::Null);
168 }
169 if let Some((uri, s256)) = params.r3 {
170 claims["r3_uri"] = json!(uri);
171 claims["r3_s256"] = json!(s256);
172 }
173 Ok(mint(key, TokenKind::Resource, &claims))
174}
175
176pub fn challenge(requirement: &Requirement) -> Vec<(String, String)> {
178 vec![(
179 crate::requirement::HEADER.to_string(),
180 requirement.to_header(),
181 )]
182}