snarkos_node_rest/helpers/
auth.rs

1// Copyright 2024 Aleo Network Foundation
2// This file is part of the snarkOS library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use 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
35/// The time a jwt token is valid for.
36pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; // 10 years.
37
38/// Returns the JWT secret for the node instance.
39fn jwt_secret() -> &'static Vec<u8> {
40    static SECRET: OnceCell<Vec<u8>> = OnceCell::new();
41    SECRET.get_or_init(|| {
42        let seed: [u8; 16] = ::rand::thread_rng().gen();
43        seed.to_vec()
44    })
45}
46
47/// The Json web token claims.
48#[derive(Debug, Deserialize, Serialize)]
49pub struct Claims {
50    /// The subject (user).
51    sub: String,
52    /// The UTC timestamp the token was issued at.
53    iat: i64,
54    /// Expiration time (as UTC timestamp).
55    exp: i64,
56}
57
58impl Claims {
59    pub fn new<N: Network>(address: Address<N>) -> Self {
60        let issued_at = OffsetDateTime::now_utc().unix_timestamp();
61        let expiration = issued_at.saturating_add(EXPIRATION);
62
63        Self { sub: address.to_string(), iat: issued_at, exp: expiration }
64    }
65
66    /// Returns true if the token is expired.
67    pub fn is_expired(&self) -> bool {
68        OffsetDateTime::now_utc().unix_timestamp() >= self.exp
69    }
70
71    /// Returns the json web token string.
72    pub fn to_jwt_string(&self) -> Result<String> {
73        encode(&Header::default(), &self, &EncodingKey::from_secret(jwt_secret())).map_err(|e| anyhow!(e))
74    }
75}
76
77pub async fn auth_middleware(request: Request<Body>, next: Next) -> Result<Response, Response> {
78    // Deconstruct the request to extract the auth token.
79    let (mut parts, body) = request.into_parts();
80    let auth: TypedHeader<Authorization<Bearer>> =
81        parts.extract().await.map_err(|_| StatusCode::UNAUTHORIZED.into_response())?;
82
83    match decode::<Claims>(auth.token(), &DecodingKey::from_secret(jwt_secret()), &Validation::new(Algorithm::HS256)) {
84        Ok(decoded) => {
85            let claims = decoded.claims;
86            if claims.is_expired() {
87                return Err((StatusCode::UNAUTHORIZED, "Expired JSON Web Token".to_owned()).into_response());
88            }
89        }
90
91        Err(_) => {
92            return Err(StatusCode::UNAUTHORIZED.into_response());
93        }
94    }
95
96    // Reconstruct the request.
97    let request = Request::from_parts(parts, body);
98
99    Ok(next.run(request).await)
100}