use solid_pod_rs::auth::nip98;
use solid_pod_rs::did_nostr_types::is_valid_hex_pubkey;
use crate::ownership::ForgeAgent;
use crate::request::ForgeRequest;
#[must_use]
pub fn resolve_agent(req: &ForgeRequest, token_key: &[u8], now: u64) -> ForgeAgent {
let Some(authz) = req.header("authorization") else {
return ForgeAgent::Anonymous;
};
if let Some(bearer) = authz.strip_prefix("Bearer ") {
let bearer = bearer.trim();
if bearer.starts_with("f1.") {
if let Ok(agent) = crate::token::verify(token_key, bearer, now) {
return agent;
}
return ForgeAgent::Anonymous;
}
}
if authz.starts_with(nip98_prefix()) {
let url = req.absolute_url();
let body = if req.raw_body.is_empty() {
None
} else {
Some(req.raw_body.as_ref())
};
if let Ok(v) = nip98::verify_at(authz, &url, &req.method.to_uppercase(), body, now) {
if is_valid_hex_pubkey(&v.pubkey) {
return ForgeAgent::Nostr {
pubkey_hex: v.pubkey,
};
}
}
}
ForgeAgent::Anonymous
}
fn nip98_prefix() -> &'static str {
"Nostr "
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Bytes;
const KEY: &[u8] = b"forge-instance-hmac-key-0123456789";
fn req_with_auth(authz: Option<&str>) -> ForgeRequest {
ForgeRequest {
method: "GET".into(),
path: "/forge/x".into(),
query: String::new(),
headers: authz
.map(|a| vec![("authorization".to_string(), a.to_string())])
.unwrap_or_default(),
raw_body: Bytes::new(),
host_url: Some("https://pod.example".into()),
}
}
#[test]
fn no_header_is_anonymous() {
assert_eq!(
resolve_agent(&req_with_auth(None), KEY, 0),
ForgeAgent::Anonymous
);
}
#[test]
fn valid_forge_token_resolves() {
let agent = ForgeAgent::Nostr {
pubkey_hex: "c".repeat(64),
};
let tok = crate::token::mint(KEY, &agent, 1000, 3600).unwrap();
let req = req_with_auth(Some(&format!("Bearer {tok}")));
assert_eq!(resolve_agent(&req, KEY, 1500), agent);
}
#[test]
fn expired_or_tampered_token_is_anonymous() {
let agent = ForgeAgent::Nostr {
pubkey_hex: "c".repeat(64),
};
let tok = crate::token::mint(KEY, &agent, 1000, 10).unwrap();
let req = req_with_auth(Some(&format!("Bearer {tok}")));
assert_eq!(resolve_agent(&req, KEY, 9999), ForgeAgent::Anonymous);
assert_eq!(
resolve_agent(&req, b"different-key-abcdefghijklmn", 1500),
ForgeAgent::Anonymous
);
}
#[test]
fn non_forge_bearer_is_anonymous() {
let req = req_with_auth(Some("Bearer some-oauth-jwt"));
assert_eq!(resolve_agent(&req, KEY, 0), ForgeAgent::Anonymous);
}
#[test]
fn malformed_nip98_is_anonymous() {
let req = req_with_auth(Some("Nostr bm90LWEtdmFsaWQ="));
assert_eq!(resolve_agent(&req, KEY, 0), ForgeAgent::Anonymous);
}
}