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 }