1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// Copyright 2019-2024 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use crate::key_management::KeyInfo;
use crate::shim::crypto::SignatureType;
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, errors::Result as JWTResult, DecodingKey, EncodingKey, Header};
use rand::Rng;
use serde::{Deserialize, Serialize};
use thiserror::Error;

/// constant string that is used to identify the JWT secret key in `KeyStore`
pub const JWT_IDENTIFIER: &str = "auth-jwt-private";
/// Admin permissions
pub const ADMIN: &[&str] = &["read", "write", "sign", "admin"];
/// Signing permissions
pub const SIGN: &[&str] = &["read", "write", "sign"];
/// Writing permissions
pub const WRITE: &[&str] = &["read", "write"];
/// Reading permissions
pub const READ: &[&str] = &["read"];

/// Error enumeration for Authentication
#[derive(Debug, Error, Serialize, Deserialize)]
pub enum Error {
    /// Filecoin Method does not exist
    #[error("Filecoin method does not exist")]
    MethodParam,
    /// Invalid permissions to use specified method
    #[error("Incorrect permissions to access method")]
    InvalidPermissions,
    /// Missing authentication header
    #[error("Missing authentication header")]
    NoAuthHeader,
    #[error("{0}")]
    Other(String),
}

/// Claim structure for JWT Tokens
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
    #[serde(rename = "Allow")]
    allow: Vec<String>,
    // Expiration time (as UTC timestamp)
    exp: usize,
}

/// Create a new JWT Token
pub fn create_token(perms: Vec<String>, key: &[u8], token_exp: Duration) -> JWTResult<String> {
    let exp_time = Utc::now() + token_exp;
    let payload = Claims {
        allow: perms,
        exp: exp_time.timestamp() as usize,
    };
    encode(&Header::default(), &payload, &EncodingKey::from_secret(key))
}

/// Verify JWT Token and return the allowed permissions from token
pub fn verify_token(token: &str, key: &[u8]) -> JWTResult<Vec<String>> {
    let validation = jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::default());
    let token = decode::<Claims>(token, &DecodingKey::from_secret(key), &validation)?;
    Ok(token.claims.allow)
}

pub fn generate_priv_key() -> KeyInfo {
    let priv_key = rand::thread_rng().gen::<[u8; 32]>();
    // This is temporary use of bls key as placeholder, need to update keyinfo to use string
    // instead of keyinfo for key type
    KeyInfo::new(SignatureType::Bls, priv_key.to_vec())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn create_and_verify_token() {
        let perms_expected = vec![
            "Ph'nglui mglw'nafh Cthulhu".to_owned(),
            "R'lyeh wgah'nagl fhtagn".to_owned(),
        ];
        let key = generate_priv_key();

        // Token duration of 1 hour. Validation must pass.
        let token = create_token(
            perms_expected.clone(),
            key.private_key(),
            Duration::try_hours(1).expect("Infallible"),
        )
        .unwrap();
        let perms = verify_token(&token, key.private_key()).unwrap();
        assert_eq!(perms_expected, perms);

        // Token duration of -1 hour (already expired). Validation must fail.
        let token = create_token(
            perms_expected.clone(),
            key.private_key(),
            -Duration::try_hours(1).expect("Infallible"),
        )
        .unwrap();
        assert!(verify_token(&token, key.private_key()).is_err());

        // Token duration of -10 seconds (already expired, slightly). There is leeway of 60 seconds
        // by default, so validation must pass.
        let token = create_token(
            perms_expected.clone(),
            key.private_key(),
            -Duration::try_seconds(10).expect("Infallible"),
        )
        .unwrap();
        let perms = verify_token(&token, key.private_key()).unwrap();
        assert_eq!(perms_expected, perms);
    }
}