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().r#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 if JWT_SECRET.get().is_none() {
79 return Ok(next.run(request).await);
80 }
81
82 let (mut parts, body) = request.into_parts();
84 let auth: TypedHeader<Authorization<Bearer>> =
85 parts.extract().await.map_err(|_| StatusCode::UNAUTHORIZED.into_response())?;
86
87 if let Err(err) = decode::<Claims>(
88 auth.token(),
89 &DecodingKey::from_secret(JWT_SECRET.get().unwrap()),
90 &Validation::new(Algorithm::HS256),
91 ) {
92 warn!("Request authorization error: {err}");
93 return Err(StatusCode::UNAUTHORIZED.into_response());
94 }
95
96 let request = Request::from_parts(parts, body);
98
99 Ok(next.run(request).await)
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use base64::prelude::*;
106 use snarkvm::prelude::{Address, MainnetV0};
107
108 #[test]
109 fn check_const_jwt_value() {
110 let secret = "FVPjEPVAKh2f0EkRCpQkqA==";
112 let timestamp = 174437065;
113
114 let secret_bytes = BASE64_STANDARD.decode(secret).unwrap();
115
116 let mut rng = TestRng::fixed(12345);
118 let pk = PrivateKey::<MainnetV0>::new(&mut rng).unwrap();
119 let addr = Address::try_from(pk).unwrap();
120
121 let claims = Claims::new(addr, Some(secret_bytes), Some(timestamp));
122 let jwt_str = claims.to_jwt_string().unwrap();
123
124 assert_eq!(
125 jwt_str,
126 "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.\
127 eyJzdWIiOiJhbGVvMTBrbmtlbHZuZDU1ZnNhYX\
128 JtMjV3Y2g3cDlzdWYydHFsZ3d5NWs0bnh3bXM2\
129 ZDI2Mnh5ZnFtMnRjY3IiLCJpYXQiOjE3NDQzNz\
130 A2NSwiZXhwIjo0ODk3OTcwNjV9.HcTvPC7jQyq\
131 NaPqsC2XHZl3Yji_OHxo5TyKLSKVxirI"
132 );
133 }
134}