solid_pod_rs_forge/
auth.rs1use solid_pod_rs::auth::nip98;
18use solid_pod_rs::did_nostr_types::is_valid_hex_pubkey;
19
20use crate::ownership::ForgeAgent;
21use crate::request::ForgeRequest;
22
23#[must_use]
27pub fn resolve_agent(req: &ForgeRequest, token_key: &[u8], now: u64) -> ForgeAgent {
28 let Some(authz) = req.header("authorization") else {
29 return ForgeAgent::Anonymous;
30 };
31
32 if let Some(bearer) = authz.strip_prefix("Bearer ") {
34 let bearer = bearer.trim();
35 if bearer.starts_with("f1.") {
36 if let Ok(agent) = crate::token::verify(token_key, bearer, now) {
37 return agent;
38 }
39 return ForgeAgent::Anonymous;
40 }
41 }
42
43 if authz.starts_with(nip98_prefix()) {
45 let url = req.absolute_url();
46 let body = if req.raw_body.is_empty() {
47 None
48 } else {
49 Some(req.raw_body.as_ref())
50 };
51 if let Ok(v) = nip98::verify_at(authz, &url, &req.method.to_uppercase(), body, now) {
52 if is_valid_hex_pubkey(&v.pubkey) {
53 return ForgeAgent::Nostr {
54 pubkey_hex: v.pubkey,
55 };
56 }
57 }
58 }
59
60 ForgeAgent::Anonymous
61}
62
63fn nip98_prefix() -> &'static str {
64 "Nostr "
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use bytes::Bytes;
71
72 const KEY: &[u8] = b"forge-instance-hmac-key-0123456789";
73
74 fn req_with_auth(authz: Option<&str>) -> ForgeRequest {
75 ForgeRequest {
76 method: "GET".into(),
77 path: "/forge/x".into(),
78 query: String::new(),
79 headers: authz
80 .map(|a| vec![("authorization".to_string(), a.to_string())])
81 .unwrap_or_default(),
82 raw_body: Bytes::new(),
83 host_url: Some("https://pod.example".into()),
84 }
85 }
86
87 #[test]
88 fn no_header_is_anonymous() {
89 assert_eq!(
90 resolve_agent(&req_with_auth(None), KEY, 0),
91 ForgeAgent::Anonymous
92 );
93 }
94
95 #[test]
96 fn valid_forge_token_resolves() {
97 let agent = ForgeAgent::Nostr {
98 pubkey_hex: "c".repeat(64),
99 };
100 let tok = crate::token::mint(KEY, &agent, 1000, 3600).unwrap();
101 let req = req_with_auth(Some(&format!("Bearer {tok}")));
102 assert_eq!(resolve_agent(&req, KEY, 1500), agent);
103 }
104
105 #[test]
106 fn expired_or_tampered_token_is_anonymous() {
107 let agent = ForgeAgent::Nostr {
108 pubkey_hex: "c".repeat(64),
109 };
110 let tok = crate::token::mint(KEY, &agent, 1000, 10).unwrap();
111 let req = req_with_auth(Some(&format!("Bearer {tok}")));
112 assert_eq!(resolve_agent(&req, KEY, 9999), ForgeAgent::Anonymous);
114 assert_eq!(
116 resolve_agent(&req, b"different-key-abcdefghijklmn", 1500),
117 ForgeAgent::Anonymous
118 );
119 }
120
121 #[test]
122 fn non_forge_bearer_is_anonymous() {
123 let req = req_with_auth(Some("Bearer some-oauth-jwt"));
124 assert_eq!(resolve_agent(&req, KEY, 0), ForgeAgent::Anonymous);
125 }
126
127 #[test]
128 fn malformed_nip98_is_anonymous() {
129 let req = req_with_auth(Some("Nostr bm90LWEtdmFsaWQ="));
132 assert_eq!(resolve_agent(&req, KEY, 0), ForgeAgent::Anonymous);
133 }
134}