use crate::RestError;
use snarkvm::prelude::{Address, Network};
use anyhow::{anyhow, Result};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use once_cell::sync::OnceCell;
use rand::{thread_rng, Rng};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use warp::{reject, Filter, Rejection};
pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; fn jwt_secret() -> &'static Vec<u8> {
static SECRET: OnceCell<Vec<u8>> = OnceCell::new();
SECRET.get_or_init(|| {
let seed: [u8; 16] = thread_rng().gen();
seed.to_vec()
})
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Claims {
sub: String,
iat: i64,
exp: i64,
}
impl Claims {
pub fn new<N: Network>(address: Address<N>) -> Self {
let issued_at = OffsetDateTime::now_utc().unix_timestamp();
let expiration = issued_at.saturating_add(EXPIRATION);
Self { sub: address.to_string(), iat: issued_at, exp: expiration }
}
pub fn is_expired(&self) -> bool {
OffsetDateTime::now_utc().unix_timestamp() >= self.exp
}
pub fn to_jwt_string(&self) -> Result<String> {
encode(&Header::default(), &self, &EncodingKey::from_secret(jwt_secret())).map_err(|e| anyhow!(e))
}
}
pub fn with_auth() -> impl Filter<Extract = ((),), Error = Rejection> + Clone {
warp::header::<String>("authorization").and_then(|token: String| async move {
if !token.starts_with("Bearer ") {
return Err(reject::custom(RestError::Request("Invalid authorization header.".to_string())));
}
match decode::<Claims>(
token.trim_start_matches("Bearer "),
&DecodingKey::from_secret(jwt_secret()),
&Validation::new(Algorithm::HS256),
) {
Ok(decoded) => {
let claims = decoded.claims;
if claims.is_expired() {
return Err(reject::custom(RestError::Request("Expired JSON Web Token.".to_string())));
}
Ok(())
}
Err(_) => Err(reject::custom(RestError::Request("Unauthorized caller.".to_string()))),
}
})
}