Skip to content

feat: allow keeping the JWT secret constant #3592

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ workspace = true
[dependencies.anstyle]
version = "1"

[dependencies.base64]
version = "0.22"

[dependencies.anyhow]
version = "1.0.79"

Expand Down
26 changes: 24 additions & 2 deletions cli/src/commands/start.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ use snarkvm::{

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

impl Start {
Expand Down Expand Up @@ -569,11 +578,24 @@ impl Start {
);

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

if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address()).to_jwt_string() {
let jwt_secret = if let Some(jwt_b64) = &self.jwt_secret {
if self.jwt_timestamp.is_none() {
bail!("The '--jwt-timestamp' flag must be set if the '--jwt-secret' flag is set");
}
let jwt_bytes = BASE64_STANDARD.decode(jwt_b64).map_err(|_| anyhow::anyhow!("Invalid JWT secret"))?;
if jwt_bytes.len() != 16 {
bail!("The JWT secret must be 16 bytes long");
}
Some(jwt_bytes)
} else {
None
};

if let Ok(jwt_token) = snarkos_node_rest::Claims::new(account.address(), jwt_secret, self.jwt_timestamp).to_jwt_string() {
println!("🔑 Your one-time JWT token is {}\n", jwt_token.dimmed());
}
}
Expand Down
10 changes: 7 additions & 3 deletions node/rest/src/helpers/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,13 @@ use jsonwebtoken::{Algorithm, DecodingKey, EncodingKey, Header, Validation, deco
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};

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

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

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

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