/ node / rest / src / helpers / auth.rs
auth.rs
  1  // Copyright (c) 2025-2026 ACDC Network
  2  // This file is part of the alphaos library.
  3  //
  4  // Alpha Chain | Delta Chain Protocol
  5  // International Monetary Graphite.
  6  //
  7  // Derived from Aleo (https://aleo.org) and ProvableHQ (https://provable.com).
  8  // They built world-class ZK infrastructure. We installed the EASY button.
  9  // Their cryptography: elegant. Our modifications: bureaucracy-compatible.
 10  // Original brilliance: theirs. Robert's Rules: ours. Bugs: definitely ours.
 11  //
 12  // Original Aleo/ProvableHQ code subject to Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
 13  // All modifications and new work: CC0 1.0 Universal Public Domain Dedication.
 14  // No rights reserved. No permission required. No warranty. No refunds.
 15  //
 16  // https://creativecommons.org/publicdomain/zero/1.0/
 17  // SPDX-License-Identifier: CC0-1.0
 18  
 19  use alphavm::prelude::*;
 20  
 21  use ::time::OffsetDateTime;
 22  use anyhow::{anyhow, Result};
 23  use axum::{
 24      body::Body,
 25      http::{Request, StatusCode},
 26      middleware::Next,
 27      response::{IntoResponse, Response},
 28      RequestPartsExt,
 29  };
 30  use axum_extra::{
 31      headers::authorization::{Authorization, Bearer},
 32      TypedHeader,
 33  };
 34  use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
 35  use once_cell::sync::OnceCell;
 36  use serde::{Deserialize, Serialize};
 37  
 38  /// The time a jwt token is valid for.
 39  pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; // 10 years.
 40  
 41  /// The JWT secret for the REST server.
 42  static JWT_SECRET: OnceCell<Vec<u8>> = OnceCell::new();
 43  
 44  /// The Json web token claims.
 45  #[derive(Debug, Deserialize, Serialize)]
 46  pub struct Claims {
 47      /// The subject (user).
 48      sub: String,
 49      /// The UTC timestamp the token was issued at.
 50      iat: i64,
 51      /// Expiration time (as UTC timestamp).
 52      exp: i64,
 53  }
 54  
 55  impl Claims {
 56      pub fn new<N: Network>(address: Address<N>, jwt_secret: Option<Vec<u8>>, jwt_timestamp: Option<i64>) -> Self {
 57          if let Some(secret) = jwt_secret {
 58              JWT_SECRET.set(secret)
 59          } else {
 60              JWT_SECRET.set({
 61                  let seed: [u8; 16] = ::rand::thread_rng().r#gen();
 62                  seed.to_vec()
 63              })
 64          }
 65          .expect("Failed to set JWT secret: already initialized");
 66  
 67          let issued_at = jwt_timestamp.unwrap_or_else(|| OffsetDateTime::now_utc().unix_timestamp());
 68          let expiration = issued_at.saturating_add(EXPIRATION);
 69  
 70          Self { sub: address.to_string(), iat: issued_at, exp: expiration }
 71      }
 72  
 73      /// Returns the json web token string.
 74      pub fn to_jwt_string(&self) -> Result<String> {
 75          encode(&Header::default(), &self, &EncodingKey::from_secret(JWT_SECRET.get().unwrap())).map_err(|e| anyhow!(e))
 76      }
 77  }
 78  
 79  pub async fn auth_middleware(request: Request<Body>, next: Next) -> Result<Response, Response> {
 80      // If the JWT secret is not set, skip authentication.
 81      if JWT_SECRET.get().is_none() {
 82          return Ok(next.run(request).await);
 83      }
 84  
 85      // Deconstruct the request to extract the auth token.
 86      let (mut parts, body) = request.into_parts();
 87      let auth: TypedHeader<Authorization<Bearer>> =
 88          parts.extract().await.map_err(|_| StatusCode::UNAUTHORIZED.into_response())?;
 89  
 90      if let Err(err) = decode::<Claims>(
 91          auth.token(),
 92          &DecodingKey::from_secret(JWT_SECRET.get().unwrap()),
 93          &Validation::new(Algorithm::HS256),
 94      ) {
 95          warn!("Request authorization error: {err}");
 96          return Err(StatusCode::UNAUTHORIZED.into_response());
 97      }
 98  
 99      // Reconstruct the request.
100      let request = Request::from_parts(parts, body);
101  
102      Ok(next.run(request).await)
103  }
104  
105  #[cfg(test)]
106  mod tests {
107      use super::*;
108      use alphavm::prelude::{Address, MainnetV0};
109      use base64::prelude::*;
110  
111      #[test]
112      fn check_const_jwt_value() {
113          // Arbitrary input values to check against the expected value.
114          let secret = "FVPjEPVAKh2f0EkRCpQkqA==";
115          let timestamp = 174437065;
116  
117          let secret_bytes = BASE64_STANDARD.decode(secret).unwrap();
118  
119          // A fixed seed, as the address also forms part of the JWT.
120          let mut rng = TestRng::fixed(12345);
121          let pk = PrivateKey::<MainnetV0>::new(&mut rng).unwrap();
122          let addr = Address::try_from(pk).unwrap();
123  
124          let claims = Claims::new(addr, Some(secret_bytes), Some(timestamp));
125          let jwt_str = claims.to_jwt_string().unwrap();
126  
127          assert_eq!(
128              jwt_str,
129              "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.\
130              eyJzdWIiOiJhbGVvMTBrbmtlbHZuZDU1ZnNhYX\
131              JtMjV3Y2g3cDlzdWYydHFsZ3d5NWs0bnh3bXM2\
132              ZDI2Mnh5ZnFtMnRjY3IiLCJpYXQiOjE3NDQzNz\
133              A2NSwiZXhwIjo0ODk3OTcwNjV9.HcTvPC7jQyq\
134              NaPqsC2XHZl3Yji_OHxo5TyKLSKVxirI"
135          );
136      }
137  }