Files
windmill/backend/windmill-common/src/jwt.rs
T
5b3913052e refactor: convert read-hot globals to AtomicBool/I64 and ArcSwap (#8815)
* refactor: extract load helpers from reload_setting family

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: convert atomic primitive globals to AtomicBool/AtomicI64

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: convert CRITICAL_*/HUB_API_SECRET/INSTANCE_EVENTS_WEBHOOK/JWT_SECRET to ArcSwap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: pin ee-repo-ref to arcswap-refactor EE branch commit

* refactor: convert BASE_URL/HUB_BASE_URL/MIN_VERSION/LICENSE_KEY*/LICENSE_KEY_ID to ArcSwap

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: convert worker hot-path globals to ArcSwap (WORKER_CONFIG et al)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: pin ee-repo-ref to combined arcswap-urls+worker EE commit

* chore: update ee-repo-ref to d8be8f88cb8898c8f6b27421989d53528223815d

This commit updates the EE repository reference after PR #532 was merged in windmill-ee-private.

Previous ee-repo-ref: c375aaaac9ec0fc0480993627d0defc8054c31a4

New ee-repo-ref: d8be8f88cb8898c8f6b27421989d53528223815d

Automated by sync-ee-ref workflow.

* fix: cleanup unused imports + fix 2 missed WORKER_CONFIG readers

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to ce0f8fbbbde09c4a858312d2d8716d224e99042c

This commit updates the EE repository reference after PR #534 was merged in windmill-ee-private.

Previous ee-repo-ref: 450b601b5aba0ca0b2045f4b5071aa8701b4bfb7

New ee-repo-ref: ce0f8fbbbde09c4a858312d2d8716d224e99042c

Automated by sync-ee-ref workflow.

* fix: secret_backend_integration test — BASE_URL.write().await → .store()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* refactor: convert APP_WORKSPACED_ROUTE to AtomicBool for symmetry with HTTP_ROUTE_WORKSPACED_ROUTE

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to e587df8 (post-#535 merge)

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-04-14 00:04:10 +00:00

77 lines
2.5 KiB
Rust

use crate::error::{self, to_anyhow, Error};
use base64::{engine::general_purpose::URL_SAFE_NO_PAD, Engine};
use hmac::{Hmac, Mac};
use serde::{de::DeserializeOwned, Serialize};
use sha2::Sha256;
use std::collections::HashSet;
lazy_static::lazy_static! {
pub static ref JWT_SECRET: arc_swap::ArcSwap<String> = arc_swap::ArcSwap::from_pointee("".to_string());
}
pub async fn encode_with_internal_secret<T: Serialize>(claims: T) -> error::Result<String> {
let jwt_secret = JWT_SECRET.load();
if jwt_secret.is_empty() {
return Err(Error::internal_err("JWT secret is not set".to_string()));
}
let token = jsonwebtoken::encode(
&jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256),
&claims,
&jsonwebtoken::EncodingKey::from_secret(jwt_secret.as_bytes()),
)
.map_err(to_anyhow)?;
Ok(token)
}
pub async fn decode_with_internal_secret<T: DeserializeOwned>(token: &str) -> error::Result<T> {
let jwt_secret = JWT_SECRET.load();
if jwt_secret.is_empty() {
return Err(Error::internal_err("JWT secret is not set".to_string()));
}
let result = jsonwebtoken::decode::<T>(
token,
&jsonwebtoken::DecodingKey::from_secret(jwt_secret.as_bytes()),
&jsonwebtoken::Validation::new(jsonwebtoken::Algorithm::HS256),
)
.map_err(to_anyhow)?;
Ok(result.claims)
}
pub fn decode_without_verify<T: DeserializeOwned>(token: &str) -> anyhow::Result<T> {
// Create a validation that skips all checks
let mut validation = jsonwebtoken::Validation::default();
validation.insecure_disable_signature_validation();
validation.validate_exp = false;
validation.validate_nbf = false;
validation.required_spec_claims = HashSet::new();
// Use an empty key since we're not verifying
let key = jsonwebtoken::DecodingKey::from_secret(&[]);
// Decode the token
let token_data = jsonwebtoken::decode::<T>(token, &key, &validation)?;
Ok(token_data.claims)
}
// header_and_payload: `{header}.{payload}`
pub async fn generate_signature(header_and_payload: &str) -> anyhow::Result<String> {
let header_and_payload = header_and_payload.trim_start_matches("jwt_ext_");
let header_and_payload = header_and_payload.trim_start_matches("jwt_");
let secret = JWT_SECRET.load();
// Create HMAC-SHA256
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())?;
mac.update(header_and_payload.as_bytes());
// Finalize and encode
let result = mac.finalize().into_bytes();
Ok(URL_SAFE_NO_PAD.encode(result))
}