snarkos_node_rest/helpers/
auth.rs1use snarkvm::prelude::*;
17
18use ::time::OffsetDateTime;
19use anyhow::{Result, anyhow};
20use axum::{
21 RequestPartsExt,
22 body::Body,
23 http::{Request, StatusCode},
24 middleware::Next,
25 response::{IntoResponse, Response},
26};
27use axum_extra::{
28 TypedHeader,
29 headers::authorization::{Authorization, Bearer},
30};
31use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, decode, encode};
32use once_cell::sync::OnceCell;
33use serde::{Deserialize, Serialize};
34
35pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; static JWT_SECRET: OnceCell<Vec<u8>> = OnceCell::new();
40
41#[derive(Debug, Deserialize, Serialize)]
43pub struct Claims {
44 sub: String,
46 iat: i64,
48 exp: i64,
50}
51
52impl Claims {
53 pub fn new<N: Network>(address: Address<N>, jwt_secret: Option<Vec<u8>>, jwt_timestamp: Option<i64>) -> Self {
54 if let Some(secret) = jwt_secret {
55 JWT_SECRET.set(secret)
56 } else {
57 JWT_SECRET.set({
58 let seed: [u8; 16] = ::rand::thread_rng().gen();
59 seed.to_vec()
60 })
61 }
62 .expect("Failed to set JWT secret: already initialized");
63
64 let issued_at = jwt_timestamp.unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp());
65 let expiration = issued_at.saturating_add(EXPIRATION);
66
67 Self { sub: address.to_string(), iat: issued_at, exp: expiration }
68 }
69
70 pub fn to_jwt_string(&self) -> Result<String> {
72 encode(&Header::default(), &self, &EncodingKey::from_secret(JWT_SECRET.get().unwrap())).map_err(|e| anyhow!(e))
73 }
74}
75
76pub async fn auth_middleware(request: Request<Body>, next: Next) -> Result<Response, Response> {
77 let (mut parts, body) = request.into_parts();
79 let auth: TypedHeader<Authorization<Bearer>> =
80 parts.extract().await.map_err(|_| StatusCode::UNAUTHORIZED.into_response())?;
81
82 if let Err(err) = decode::<Claims>(
83 auth.token(),
84 &DecodingKey::from_secret(JWT_SECRET.get().unwrap()),
85 &Validation::new(Algorithm::HS256),
86 ) {
87 warn!("Request authorization error: {err}");
88 return Err(StatusCode::UNAUTHORIZED.into_response());
89 }
90
91 let request = Request::from_parts(parts, body);
93
94 Ok(next.run(request).await)
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use base64::prelude::*;
101 use snarkvm::prelude::{Address, MainnetV0};
102
103 #[test]
104 fn check_const_jwt_value() {
105 let secret = "FVPjEPVAKh2f0EkRCpQkqA==";
107 let timestamp = 174437065;
108
109 let secret_bytes = BASE64_STANDARD.decode(secret).unwrap();
110
111 let mut rng = TestRng::fixed(12345);
113 let pk = PrivateKey::<MainnetV0>::new(&mut rng).unwrap();
114 let addr = Address::try_from(pk).unwrap();
115
116 let claims = Claims::new(addr, Some(secret_bytes), Some(timestamp));
117 let jwt_str = claims.to_jwt_string().unwrap();
118
119 assert_eq!(
120 jwt_str,
121 "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.\
122 eyJzdWIiOiJhbGVvMTBrbmtlbHZuZDU1ZnNhYX\
123 JtMjV3Y2g3cDlzdWYydHFsZ3d5NWs0bnh3bXM2\
124 ZDI2Mnh5ZnFtMnRjY3IiLCJpYXQiOjE3NDQzNz\
125 A2NSwiZXhwIjo0ODk3OTcwNjV9.HcTvPC7jQyq\
126 NaPqsC2XHZl3Yji_OHxo5TyKLSKVxirI"
127 );
128 }
129}