Skip to content

Commit 66f4e7b

Browse files
committed
feat: allow passing in 16 bytes for the JWT secret
1 parent dbc6cbc commit 66f4e7b

File tree

3 files changed

+34
-5
lines changed

3 files changed

+34
-5
lines changed

cli/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ workspace = true
4040
[dependencies.anstyle]
4141
version = "1"
4242

43+
[dependencies.base64]
44+
version = "0.22"
45+
4346
[dependencies.anyhow]
4447
version = "1.0.79"
4548

cli/src/commands/start.rs

+24-2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use snarkvm::{
3434

3535
use aleo_std::StorageMode;
3636
use anyhow::{Result, bail, ensure};
37+
use base64::prelude::*;
3738
use clap::Parser;
3839
use colored::Colorize;
3940
use core::str::FromStr;
@@ -169,6 +170,14 @@ pub struct Start {
169170
/// If development mode is enabled, specify the custom bonded balances as a JSON object (default: None)
170171
#[clap(long)]
171172
pub dev_bonded_balances: Option<BondedBalances>,
173+
/// Pass in an optional jwt secret for the node instance (16 bytes, base64 encoded) for keeping
174+
/// the JWT constant
175+
#[clap(long)]
176+
pub jwt_secret: Option<String>,
177+
/// Pass in an optional jwt creation timestamp for keeping the JWT constant. Can be any time in
178+
/// the last 10 years
179+
#[clap(long)]
180+
pub jwt_timestamp: Option<i64>,
172181
}
173182

174183
impl Start {
@@ -569,11 +578,24 @@ impl Start {
569578
);
570579

571580
// If the node is running a REST server, print the REST IP and JWT.
572-
if node_type.is_validator() {
581+
if node_type.is_validator() || node_type.is_client() {
573582
if let Some(rest_ip) = rest_ip {
574583
println!("🌐 Starting the REST server at {}.\n", rest_ip.to_string().bold());
575584

576-
if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address()).to_jwt_string() {
585+
let jwt_secret = if let Some(jwt_b64) = &self.jwt_secret {
586+
if self.jwt_timestamp.is_none() {
587+
bail!("The '--jwt-timestamp' flag must be set if the '--jwt-secret' flag is set");
588+
}
589+
let jwt_bytes = BASE64_STANDARD.decode(jwt_b64).map_err(|_| anyhow::anyhow!("Invalid JWT secret"))?;
590+
if jwt_bytes.len() != 16 {
591+
bail!("The JWT secret must be 16 bytes long");
592+
}
593+
Some(jwt_bytes)
594+
} else {
595+
None
596+
};
597+
598+
if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address(), jwt_secret, self.jwt_timestamp).to_jwt_string() {
577599
println!("🔑 Your one-time JWT token is {}\n", jwt_token.dimmed());
578600
}
579601
}

node/rest/src/helpers/auth.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,13 @@ use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, deco
3232
use once_cell::sync::OnceCell;
3333
use serde::{Deserialize, Serialize};
3434

35+
/// The JWT secret.
36+
static SECRET: OnceCell<Vec<u8>> = OnceCell::new();
3537
/// The time a jwt token is valid for.
3638
pub const EXPIRATION: i64 = 10 * 365 * 24 * 60 * 60; // 10 years.
3739

3840
/// Returns the JWT secret for the node instance.
3941
fn jwt_secret() -> &'static Vec<u8> {
40-
static SECRET: OnceCell<Vec<u8>> = OnceCell::new();
4142
SECRET.get_or_init(|| {
4243
let seed: [u8; 16] = ::rand::thread_rng().gen();
4344
seed.to_vec()
@@ -56,8 +57,11 @@ pub struct Claims {
5657
}
5758

5859
impl Claims {
59-
pub fn new<N: Network>(address: Address<N>) -> Self {
60-
let issued_at = OffsetDateTime::now_utc().unix_timestamp();
60+
pub fn new<N: Network>(address: Address<N>, jwt_secret: Option<Vec<u8>>, jwt_timestamp: Option<i64>) -> Self {
61+
if let Some(secret) = jwt_secret {
62+
SECRET.set(secret).expect("Failed to set JWT secret: already initialized");
63+
}
64+
let issued_at = jwt_timestamp.unwrap_or(OffsetDateTime::now_utc().unix_timestamp());
6165
let expiration = issued_at.saturating_add(EXPIRATION);
6266

6367
Self { sub: address.to_string(), iat: issued_at, exp: expiration }

0 commit comments

Comments
 (0)