From 0355ffc8cdd086e90351da25a8084f4c3f365756 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Tue, 7 Apr 2026 12:02:16 +0100 Subject: [PATCH 1/3] feat: new Message type that supports encryption Signed-off-by: Aminu Oluwaseun Joshua --- Cargo.lock | 198 ++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 + src/agent.rs | 7 +- src/error.rs | 48 +++++----- src/jwt.rs | 25 +++++ src/lib.rs | 30 ++++++ src/message.rs | 35 +++++-- src/tests/jwt.rs | 63 +++++++++++++ src/tests/mod.rs | 1 + src/tests/unit_error.rs | 4 +- 10 files changed, 375 insertions(+), 41 deletions(-) create mode 100644 src/jwt.rs create mode 100644 src/tests/jwt.rs diff --git a/Cargo.lock b/Cargo.lock index 39dbbc6..34d6d22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -229,6 +229,15 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", +] + [[package]] name = "digest" version = "0.10.7" @@ -799,6 +808,21 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a87cc7a48537badeae96744432de36f4be2b4a34a05a5ef32e9dd8a1c169dde" +dependencies = [ + "base64", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -817,6 +841,15 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -887,6 +920,31 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -952,6 +1010,39 @@ dependencies = [ "num-traits", ] +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -999,6 +1090,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1147,6 +1244,15 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "ref-cast" version = "1.0.25" @@ -1264,10 +1370,12 @@ version = "0.1.4" dependencies = [ "async-trait", "futures", + "jsonwebtoken", "rig-core", "schemars", "serde", "serde_json", + "serial_test", "tarpc", "thiserror 2.0.18", "tokio", @@ -1369,6 +1477,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46e6f046b7fef48e2660c57ed794263155d713de679057f2d0c169bfc6e756cc" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" version = "0.1.29" @@ -1403,6 +1520,18 @@ dependencies = [ "syn", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sdd" +version = "3.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" + [[package]] name = "security-framework" version = "3.7.0" @@ -1480,6 +1609,32 @@ dependencies = [ "zmij", ] +[[package]] +name = "serial_test" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "911bd979bf1070a3f3aa7b691a3b3e9968f339ceeec89e08c280a8a22207a32f" +dependencies = [ + "futures-executor", + "futures-util", + "log", + "once_cell", + "parking_lot", + "scc", + "serial_test_derive", +] + +[[package]] +name = "serial_test_derive" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a7d91949b85b0d2fb687445e448b40d322b6b3e4af6b44a29b21d9a5f33e6d9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sha1" version = "0.10.6" @@ -1506,6 +1661,18 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "simple_asn1" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d585997b0ac10be3c5ee635f1bab02d512760d14b7c468801ac8a01d9ae5f1d" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror 2.0.18", + "time", +] + [[package]] name = "slab" version = "0.4.12" @@ -1683,6 +1850,37 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index 4d1d3c1..4b0121b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,3 +22,8 @@ serde_json = "1.0.117" schemars = "1.2.1" thiserror = "2.0.18" + +jsonwebtoken = "9.3.1" + +[dev-dependencies] +serial_test = "3.4.0" diff --git a/src/agent.rs b/src/agent.rs index 1a6a489..03a2bfd 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -78,10 +78,7 @@ impl AgentWorker for AgentServer { _context: ::tarpc::context::Context, user_message: Message, ) -> Result { - println!("Message received"); - self.providers - .chat(&user_message.to_string()) - .await - .map_err(ApiError::from) + let prompt: String = user_message.try_into()?; + self.providers.chat(&prompt).await.map_err(ApiError::from) } } diff --git a/src/error.rs b/src/error.rs index c29e3c2..0f0be31 100644 --- a/src/error.rs +++ b/src/error.rs @@ -24,6 +24,26 @@ pub enum Error { /// RPC error: a remote procedure call error occurred. #[error("rpc error: {0}")] RpcError(#[from] tarpc::client::RpcError), + /// Invalid JWT credentials: the JWT token is invalid or expired. + #[error("invalid jwt credentials: {0}")] + InvalidJWTCredentials(String), + /// No JWT secret found: the JWT secret is not configured. + #[error("no jwt secret found")] + NoJWTSecretFound, +} + +impl Error { + fn status(&self) -> u16 { + match self { + Error::AuthenticationError(_) | Error::InvalidJWTCredentials(_) => 401, + Error::HttpError(_) + | Error::Io(_) + | Error::PromptError(_) + | Error::RpcError(_) + | Error::ProviderError(_) + | Error::NoJWTSecretFound => 500, + } + } } /// API error type used for provider-specific error responses. @@ -41,31 +61,9 @@ impl Display for ApiError { impl From for ApiError { fn from(value: Error) -> Self { - match value { - Error::ProviderError(e) => ApiError { - status: 500, - message: e, - }, - Error::HttpError(error) => ApiError { - status: 500, - message: error.to_string(), - }, - Error::PromptError(prompt_error) => ApiError { - status: 500, - message: prompt_error.to_string(), - }, - Error::Io(error) => ApiError { - status: 500, - message: error.to_string(), - }, - Error::AuthenticationError(e) => ApiError { - status: 401, - message: e, - }, - Error::RpcError(server_error) => ApiError { - status: 500, - message: server_error.to_string(), - }, + Self { + status: value.status(), + message: value.to_string(), } } } diff --git a/src/jwt.rs b/src/jwt.rs new file mode 100644 index 0000000..074850c --- /dev/null +++ b/src/jwt.rs @@ -0,0 +1,25 @@ +use jsonwebtoken::Algorithm; +use serde::{Deserialize, Serialize}; + +use crate::error::Error; + +/// JWT token decoder +/// Decodes a JWT token and returns the claims if valid. +pub(crate) fn decode_jwt(token: &str, hmac_secret: &str) -> Result { + let validation = jsonwebtoken::Validation::new(Algorithm::HS256); + + jsonwebtoken::decode::( + token, + &jsonwebtoken::DecodingKey::from_secret(hmac_secret.as_bytes()), + &validation, + ) + .map(|data| data.claims) + .map_err(|e| Error::InvalidJWTCredentials(e.to_string())) +} + +/// JWT claims +#[derive(Deserialize, Serialize)] +pub(crate) struct Claims { + pub prompt: String, + pub exp: usize, +} diff --git a/src/lib.rs b/src/lib.rs index 5ca82c7..aefa4cc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,10 +4,40 @@ //! It exposes core components for agent management, tool integration, error handling, //! and provider abstraction. Use the [`AgentServerBuilder`] to configure and launch //! your own agent server with custom tools and providers. +//! +//! ## Example +//! +//! Here is a minimal example of how to set up and run an Ollama RPC agent: +//! +//!```rust,ignore +//!use rpc_agent::Providers; +//! +//!#[tokio::main] +//!async fn main() -> Result<(), Box> { +//! let builder = rpc_agent::AgentServerBuilder::new( +//! 5500, +//! Providers::Ollama, +//! "You're a friendly assistant", +//! "gpt-oss:20b", +//! ); +//! +//! let server = builder.build()?; +//! +//! server.run().await?; +//! +//! Ok(()) +//!} +//!``` +//! +//! ## Feature Flags +//! +//! * **`prompt_encryption`**: Enables prompt encryption for securely transmitting prompts over the network. It requires a JWT_SECRET environment variable to be set. mod agent; mod builder; pub mod error; +mod jwt; + mod message; mod providers; mod tools; diff --git a/src/message.rs b/src/message.rs index 6f8e2de..e35fbca 100644 --- a/src/message.rs +++ b/src/message.rs @@ -1,25 +1,42 @@ -use std::fmt::Display; - use serde_json::Value; +use crate::error::{ApiError, Error}; +use crate::jwt::decode_jwt; + /// Represents a message to be sent to the AI provider. #[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum Message { + /// A plain text message. Text(String), + /// A structured message. Struct(Value), + /// An encrypted message that will be decrypted before being sent. + /// + /// Note: It requires a JWT_SECRET environment variable to be set. + Encrypted(String), } -impl Display for Message { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Message::Text(text) => write!(f, "{text}"), +impl TryFrom for String { + type Error = ApiError; + + fn try_from(message: Message) -> Result { + match message { + Message::Text(text) => Ok(text), Message::Struct(value) => { - if let Ok(json) = serde_json::to_string_pretty(value) { - write!(f, "{json}") + if let Ok(json) = serde_json::to_string_pretty(&value) { + Ok(json) } else { - write!(f, "{:?}", value) + Ok(value.to_string()) } } + Message::Encrypted(token) => { + let hmac_secret = + std::env::var("JWT_SECRET").map_err(|_| Error::NoJWTSecretFound)?; + let claims = decode_jwt(&token, &hmac_secret) + .map_err(|e| Error::InvalidJWTCredentials(e.to_string()))?; + + Ok(claims.prompt) + } } } } diff --git a/src/tests/jwt.rs b/src/tests/jwt.rs new file mode 100644 index 0000000..1558277 --- /dev/null +++ b/src/tests/jwt.rs @@ -0,0 +1,63 @@ +use crate::error::{ApiError, Error}; +use crate::jwt::{Claims, decode_jwt}; +use crate::message::Message; +use std::env; + +#[test] +#[serial_test::serial] +fn decode_jwt_valid_token() { + let secret = "mysecret"; + let claims = Claims { + prompt: "test prompt".to_string(), + exp: 9999999999, + }; + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(secret.as_bytes()), + ) + .unwrap(); + let decoded = decode_jwt(&token, secret).unwrap(); + assert_eq!(decoded.prompt, "test prompt"); +} + +#[test] +fn decode_jwt_invalid_token() { + let secret = "mysecret"; + let token = "invalid.token.value"; + let result = decode_jwt(token, secret); + assert!(matches!(result, Err(Error::InvalidJWTCredentials(_)))); +} + +#[test] +#[serial_test::serial] +fn message_encrypted_variant_decrypts() { + let secret = "testsecret"; + unsafe { env::set_var("JWT_SECRET", secret) }; + let claims = Claims { + prompt: "encrypted prompt".to_string(), + exp: 9999999999, + }; + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(secret.as_bytes()), + ) + .unwrap(); + let msg = Message::Encrypted(token); + let result: String = msg.try_into().unwrap(); + assert_eq!(result, "encrypted prompt"); + unsafe { env::remove_var("JWT_SECRET") }; +} + +#[test] +#[serial_test::serial] +fn message_encrypted_variant_no_secret() { + unsafe { env::remove_var("JWT_SECRET") }; + let msg = Message::Encrypted("sometoken".to_string()); + let result: Result = msg.try_into(); + assert_eq!( + result.unwrap_err().to_string(), + ApiError::from(Error::NoJWTSecretFound).to_string() + ); // NoJWTSecretFound maps to 500 +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 508b2d7..94da536 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,3 +1,4 @@ mod mock_test; mod unit; mod unit_error; +mod jwt; diff --git a/src/tests/unit_error.rs b/src/tests/unit_error.rs index 69fea7d..d00912f 100644 --- a/src/tests/unit_error.rs +++ b/src/tests/unit_error.rs @@ -4,12 +4,12 @@ use crate::error::{ApiError, Error}; fn api_error_from_provider_error() { let err = Error::ProviderError("fail".to_string()); let api: ApiError = err.into(); - assert_eq!(api.to_string(), "500: fail"); + assert_eq!(api.to_string(), "500: provider error: fail"); } #[test] fn api_error_from_auth_error() { let err = Error::AuthenticationError("bad auth".to_string()); let api: ApiError = err.into(); - assert_eq!(api.to_string(), "401: bad auth"); + assert_eq!(api.to_string(), "401: authentication error: bad auth"); } From c9f5bca02acd823312d71c869dc486ff27154bce Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Tue, 7 Apr 2026 12:26:15 +0100 Subject: [PATCH 2/3] improved test coverage + safer testing Signed-off-by: Aminu Oluwaseun Joshua --- src/jwt.rs | 2 +- src/lib.rs | 4 ---- src/message.rs | 12 +++++++----- src/tests/jwt.rs | 39 ++++++++++++++++++++++++++++++++++++--- src/tests/mod.rs | 2 +- 5 files changed, 45 insertions(+), 14 deletions(-) diff --git a/src/jwt.rs b/src/jwt.rs index 074850c..d76ea15 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -21,5 +21,5 @@ pub(crate) fn decode_jwt(token: &str, hmac_secret: &str) -> Result for String { Message::Encrypted(token) => { let hmac_secret = std::env::var("JWT_SECRET").map_err(|_| Error::NoJWTSecretFound)?; - let claims = decode_jwt(&token, &hmac_secret) - .map_err(|e| Error::InvalidJWTCredentials(e.to_string()))?; - Ok(claims.prompt) + let prompt = decode_jwt(&token, &hmac_secret).map(|c| c.prompt)?; + + Ok(prompt) } } } diff --git a/src/tests/jwt.rs b/src/tests/jwt.rs index 1558277..51c3a64 100644 --- a/src/tests/jwt.rs +++ b/src/tests/jwt.rs @@ -3,6 +3,40 @@ use crate::jwt::{Claims, decode_jwt}; use crate::message::Message; use std::env; +/// Guard to ensure JWT_SECRET is removed from the environment after test, even on panic. +struct JwtSecretGuard; + +impl JwtSecretGuard { + fn set(secret: &str) -> Self { + unsafe { env::set_var("JWT_SECRET", secret) }; + JwtSecretGuard + } +} + +impl Drop for JwtSecretGuard { + fn drop(&mut self) { + unsafe { env::remove_var("JWT_SECRET") }; + } +} + +#[test] +#[serial_test::serial] +fn decode_jwt_expired_token() { + let secret = "mysecret"; + let claims = Claims { + prompt: "expired prompt".to_string(), + exp: 1, // Expired long ago (Unix epoch) + }; + let token = jsonwebtoken::encode( + &jsonwebtoken::Header::default(), + &claims, + &jsonwebtoken::EncodingKey::from_secret(secret.as_bytes()), + ) + .unwrap(); + let result = decode_jwt(&token, secret); + assert!(matches!(result, Err(Error::InvalidJWTCredentials(_)))); +} + #[test] #[serial_test::serial] fn decode_jwt_valid_token() { @@ -33,7 +67,7 @@ fn decode_jwt_invalid_token() { #[serial_test::serial] fn message_encrypted_variant_decrypts() { let secret = "testsecret"; - unsafe { env::set_var("JWT_SECRET", secret) }; + let _guard = JwtSecretGuard::set(secret); let claims = Claims { prompt: "encrypted prompt".to_string(), exp: 9999999999, @@ -47,7 +81,6 @@ fn message_encrypted_variant_decrypts() { let msg = Message::Encrypted(token); let result: String = msg.try_into().unwrap(); assert_eq!(result, "encrypted prompt"); - unsafe { env::remove_var("JWT_SECRET") }; } #[test] @@ -59,5 +92,5 @@ fn message_encrypted_variant_no_secret() { assert_eq!( result.unwrap_err().to_string(), ApiError::from(Error::NoJWTSecretFound).to_string() - ); // NoJWTSecretFound maps to 500 + ); } diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 94da536..85c8cdc 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,4 +1,4 @@ +mod jwt; mod mock_test; mod unit; mod unit_error; -mod jwt; From 44018204004adea00dd6b745332b4bdddd4d019b Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Tue, 7 Apr 2026 12:41:39 +0100 Subject: [PATCH 3/3] fix test issue Signed-off-by: Aminu Oluwaseun Joshua --- src/error.rs | 2 +- src/jwt.rs | 2 +- src/tests/jwt.rs | 22 ++++++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/error.rs b/src/error.rs index 0f0be31..af8f863 100644 --- a/src/error.rs +++ b/src/error.rs @@ -26,7 +26,7 @@ pub enum Error { RpcError(#[from] tarpc::client::RpcError), /// Invalid JWT credentials: the JWT token is invalid or expired. #[error("invalid jwt credentials: {0}")] - InvalidJWTCredentials(String), + InvalidJWTCredentials(#[from] jsonwebtoken::errors::Error), /// No JWT secret found: the JWT secret is not configured. #[error("no jwt secret found")] NoJWTSecretFound, diff --git a/src/jwt.rs b/src/jwt.rs index d76ea15..5ea8824 100644 --- a/src/jwt.rs +++ b/src/jwt.rs @@ -14,7 +14,7 @@ pub(crate) fn decode_jwt(token: &str, hmac_secret: &str) -> Result, +} impl JwtSecretGuard { fn set(secret: &str) -> Self { + let previous = env::var("JWT_SECRET").ok(); unsafe { env::set_var("JWT_SECRET", secret) }; - JwtSecretGuard + JwtSecretGuard { previous } } -} + fn delete() -> Self { + let previous = env::var("JWT_SECRET").ok(); + unsafe { env::remove_var("JWT_SECRET") }; + JwtSecretGuard { previous } + } +} impl Drop for JwtSecretGuard { fn drop(&mut self) { - unsafe { env::remove_var("JWT_SECRET") }; + match &self.previous { + Some(previous) => unsafe { env::set_var("JWT_SECRET", previous) }, + None => unsafe { env::remove_var("JWT_SECRET") }, + } } } @@ -86,7 +96,7 @@ fn message_encrypted_variant_decrypts() { #[test] #[serial_test::serial] fn message_encrypted_variant_no_secret() { - unsafe { env::remove_var("JWT_SECRET") }; + let _guard = JwtSecretGuard::delete(); let msg = Message::Encrypted("sometoken".to_string()); let result: Result = msg.try_into(); assert_eq!(