aleo_development_server/helpers/
auth.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the Aleo SDK library.
3
4// The Aleo SDK library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Aleo SDK library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Aleo SDK library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::RestError;
18
19use snarkvm::prelude::{Address, Network};
20
21use anyhow::{anyhow, Result};
22use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
23use once_cell::sync::OnceCell;
24use rand::{thread_rng, Rng};
25use serde::{Deserialize, Serialize};
26use time::OffsetDateTime;
27use warp::{reject, Filter, Rejection};
28
29/// The time a jwt token is valid for.
30pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; // 10 years.
31
32/// Returns the JWT secret for the node instance.
33fn jwt_secret() -> &'static Vec<u8> {
34    static SECRET: OnceCell<Vec<u8>> = OnceCell::new();
35    SECRET.get_or_init(|| {
36        let seed: [u8; 16] = thread_rng().gen();
37        seed.to_vec()
38    })
39}
40
41/// The Json web token claims.
42#[derive(Debug, Deserialize, Serialize)]
43pub struct Claims {
44    /// The subject (user).
45    sub: String,
46    /// The UTC timestamp the token was issued at.
47    iat: i64,
48    /// Expiration time (as UTC timestamp).
49    exp: i64,
50}
51
52impl Claims {
53    pub fn new<N: Network>(address: Address<N>) -> Self {
54        let issued_at = OffsetDateTime::now_utc().unix_timestamp();
55        let expiration = issued_at.saturating_add(EXPIRATION);
56
57        Self { sub: address.to_string(), iat: issued_at, exp: expiration }
58    }
59
60    /// Returns true if the token is expired.
61    pub fn is_expired(&self) -> bool {
62        OffsetDateTime::now_utc().unix_timestamp() >= self.exp
63    }
64
65    /// Returns the json web token string.
66    pub fn to_jwt_string(&self) -> Result<String> {
67        encode(&Header::default(), &self, &EncodingKey::from_secret(jwt_secret())).map_err(|e| anyhow!(e))
68    }
69}
70
71/// Checks the authorization header for a valid token.
72pub fn with_auth() -> impl Filter<Extract = ((),), Error = Rejection> + Clone {
73    warp::header::<String>("authorization").and_then(|token: String| async move {
74        if !token.starts_with("Bearer ") {
75            return Err(reject::custom(RestError::Request("Invalid authorization header.".to_string())));
76        }
77
78        // Decode the claims from the token.
79        match decode::<Claims>(
80            token.trim_start_matches("Bearer "),
81            &DecodingKey::from_secret(jwt_secret()),
82            &Validation::new(Algorithm::HS256),
83        ) {
84            Ok(decoded) => {
85                let claims = decoded.claims;
86                if claims.is_expired() {
87                    return Err(reject::custom(RestError::Request("Expired JSON Web Token.".to_string())));
88                }
89
90                Ok(())
91            }
92            Err(_) => Err(reject::custom(RestError::Request("Unauthorized caller.".to_string()))),
93        }
94    })
95}