Skip to content

Commit

Permalink
Extended JWT auth options along with JWT manager, completed iggy-rs#16 (
Browse files Browse the repository at this point in the history
iggy-rs#60)

* Extended JWT auth options along with JWT manager, completed iggy-rs#16

* Fixed username length serialization in update user command
  • Loading branch information
spetz authored Sep 19, 2023
1 parent d3f62b5 commit e84d240
Show file tree
Hide file tree
Showing 11 changed files with 228 additions and 32 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions configs/server.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,12 @@
"allow_private_network": false
},
"jwt": {
"secret": "top_secret$iggy.rs$_jwt_HS256_key#!",
"expiry": 3600
"algorithm": "HS256",
"audience": "iggy.rs",
"expiry": 3600,
"encoding_secret": "top_secret$iggy.rs$_jwt_HS256_key#!",
"decoding_secret": "top_secret$iggy.rs$_jwt_HS256_key#!",
"use_base64_secret": false
},
"tls": {
"enabled": false,
Expand Down
6 changes: 5 additions & 1 deletion configs/server.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ allow_credentials = false
allow_private_network = false

[http.jwt]
secret = "top_secret$iggy.rs$_jwt_HS256_key#!"
algorithm = "HS256"
audience = "iggy.rs"
expiry = 3600
encoding_secret = "top_secret$iggy.rs$_jwt_HS256_key#!"
decoding_secret = "top_secret$iggy.rs$_jwt_HS256_key#!"
use_base64_secret = false

[http.tls]
enabled = false
Expand Down
10 changes: 10 additions & 0 deletions iggy/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ pub enum Error {
CannotEncryptData,
#[error("Cannot decrypt data")]
CannotDecryptData,
#[error("Invalid JWT algorithm: {0}")]
InvalidJwtAlgorithm(String),
#[error("Invalid JWT secret.")]
InvalidJwtSecret,
#[error("Invalid command")]
InvalidCommand,
#[error("Invalid format")]
Expand Down Expand Up @@ -302,6 +306,8 @@ impl Error {
Error::InvalidEncryptionKey => 60,
Error::CannotEncryptData => 61,
Error::CannotDecryptData => 62,
Error::InvalidJwtAlgorithm(_) => 63,
Error::InvalidJwtSecret => 64,
Error::ClientNotFound(_) => 100,
Error::InvalidClientId => 101,
Error::IoError(_) => 200,
Expand Down Expand Up @@ -433,6 +439,8 @@ impl Error {
60 => "invalid_encryption_key",
61 => "cannot_encrypt_data",
62 => "cannot_decrypt_data",
63 => "invalid_jwt_encryption_algorithm",
64 => "invalid_jwt_secret",
100 => "client_not_found",
101 => "invalid_client_id",
200 => "io_error",
Expand Down Expand Up @@ -561,6 +569,8 @@ impl Error {
Error::InvalidEncryptionKey => "invalid_encryption_key",
Error::CannotEncryptData => "cannot_encrypt_data",
Error::CannotDecryptData => "cannot_decrypt_data",
Error::InvalidJwtAlgorithm(_) => "invalid_jwt_algorithm",
Error::InvalidJwtSecret => "invalid_jwt_secret",
Error::CannotCreateBaseDirectory => "cannot_create_base_directory",
Error::CannotCreateStreamsDirectory => "cannot_create_streams_directory",
Error::CannotCreateStreamDirectory(_) => "cannot_create_stream_directory",
Expand Down
13 changes: 7 additions & 6 deletions iggy/src/users/update_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ impl BytesSerializable for UpdateUser {
bytes.extend(user_id_bytes);
if let Some(username) = &self.username {
bytes.put_u8(1);
bytes.put_u32_le(username.len() as u32);
#[allow(clippy::cast_possible_truncation)]
bytes.put_u8(username.len() as u8);
bytes.extend(username.as_bytes());
} else {
bytes.put_u8(0);
Expand Down Expand Up @@ -115,8 +116,8 @@ impl BytesSerializable for UpdateUser {

position += 1;
let username = if has_username == 1 {
let username_length = u32::from_le_bytes(bytes[position..position + 4].try_into()?);
position += 4;
let username_length = bytes[position];
position += 1;
let username =
from_utf8(&bytes[position..position + username_length as usize])?.to_string();
position += username_length as usize;
Expand Down Expand Up @@ -176,8 +177,8 @@ mod tests {
let mut position = user_id.get_size_bytes() as usize;
let has_username = bytes[position];
position += 1;
let username_length = u32::from_le_bytes(bytes[position..position + 4].try_into().unwrap());
position += 4;
let username_length = bytes[position];
position += 1;
let username = from_utf8(&bytes[position..position + username_length as usize]).unwrap();
position += username_length as usize;
let has_status = bytes[position];
Expand All @@ -200,7 +201,7 @@ mod tests {
let mut bytes = Vec::new();
bytes.extend(user_id.as_bytes());
bytes.put_u8(1);
bytes.put_u32_le(username.len() as u32);
bytes.put_u8(username.len() as u8);
bytes.extend(username.as_bytes());
bytes.put_u8(1);
bytes.put_u8(status.as_code());
Expand Down
2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "server"
version = "0.0.11"
version = "0.0.12"
edition = "2021"
build = "src/build.rs"

Expand Down
105 changes: 99 additions & 6 deletions server/server.http
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
@message_1_payload_base64 = aGVsbG8=
@message_2_payload_base64 = d29ybGQ=
@header_1_payload_base_64 = dmFsdWUgMQ==
@username = iggy
@password = iggy
@root_username = iggy
@root_password = iggy
@user1_username = user1
@user1_password = secret
@token = secret
@user_id = 1
@root_id = 1
@user1_id = 2

###
GET {{url}}
Expand All @@ -22,12 +25,102 @@ POST {{url}}/users/login
Content-Type: application/json

{
"username": "{{username}}",
"password": "{{password}}"
"username": "{{root_username}}",
"password": "{{root_password}}"
}

###
GET {{url}}/users/{{user_id}}
POST {{url}}/users/logout
Authorization: Bearer {{token}}
Content-Type: application/json

{
}

###
POST {{url}}/users
Authorization: Bearer {{token}}
Content-Type: application/json

{
"username": "{{user1_username}}",
"password": "{{user1_password}}",
"status": "active",
"permissions": null
}

###
GET {{url}}/users
Authorization: Bearer {{token}}

###
GET {{url}}/users/{{user1_id}}
Authorization: Bearer {{token}}

###
PUT {{url}}/users/{{user1_id}}
Authorization: Bearer {{token}}
Content-Type: application/json

{
"username": "{{user1_username}}",
"status": "active",
"permissions": null
}

###
PUT {{url}}/users/{{user1_id}}/password
Authorization: Bearer {{token}}
Content-Type: application/json

{
"current_password": "{{user1_password}}",
"new_password": "secret1"
}

###
PUT {{url}}/users/{{user1_id}}/permissions
Authorization: Bearer {{token}}
Content-Type: application/json

{
"permissions": {
"global": {
"manage_servers": false,
"read_servers": true,
"manage_users": true,
"read_users": true,
"manage_streams": false,
"read_streams": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true
},
"streams": {
"1": {
"manage_stream": false,
"read_stream": true,
"manage_topics": false,
"read_topics": true,
"poll_messages": true,
"send_messages": true,
"topics": {
"1": {
"manage_topic": false,
"read_topic": true,
"poll_messages": true,
"send_messages": true
}
}
}
}
}
}


###
DELETE {{url}}/users/{{user1_id}}
Authorization: Bearer {{token}}

###
Expand Down
4 changes: 2 additions & 2 deletions server/src/configs/defaults.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::configs::http::{HttpConfig, HttpCorsConfig, HttpTlsConfig, JwtConfig};
use crate::configs::http::{HttpConfig, HttpCorsConfig, HttpJwtConfig, HttpTlsConfig};
use crate::configs::quic::{QuicCertificateConfig, QuicConfig};
use crate::configs::server::{MessageCleanerConfig, MessageSaverConfig, ServerConfig};
use crate::configs::system::{
Expand Down Expand Up @@ -64,7 +64,7 @@ impl Default for HttpConfig {
enabled: true,
address: "127.0.0.1:3000".to_string(),
cors: HttpCorsConfig::default(),
jwt: JwtConfig::default(),
jwt: HttpJwtConfig::default(),
tls: HttpTlsConfig::default(),
}
}
Expand Down
48 changes: 45 additions & 3 deletions server/src/configs/http.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use iggy::error::Error;
use jsonwebtoken::Algorithm;
use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
pub struct HttpConfig {
pub enabled: bool,
pub address: String,
pub cors: HttpCorsConfig,
pub jwt: JwtConfig,
pub jwt: HttpJwtConfig,
pub tls: HttpTlsConfig,
}

Expand All @@ -21,9 +23,19 @@ pub struct HttpCorsConfig {
}

#[derive(Debug, Deserialize, Serialize, Default)]
pub struct JwtConfig {
pub secret: String,
pub struct HttpJwtConfig {
pub algorithm: String,
pub audience: String,
pub expiry: u64,
pub encoding_secret: String,
pub decoding_secret: String,
pub use_base64_secret: bool,
}

#[derive(Debug)]
pub enum JwtSecret {
Default(String),
Base64(String),
}

#[derive(Debug, Deserialize, Serialize, Default)]
Expand All @@ -32,3 +44,33 @@ pub struct HttpTlsConfig {
pub cert_file: String,
pub key_file: String,
}

impl HttpJwtConfig {
pub fn get_algorithm(&self) -> Result<Algorithm, Error> {
match self.algorithm.as_str() {
"HS256" => Ok(Algorithm::HS256),
"HS384" => Ok(Algorithm::HS384),
"HS512" => Ok(Algorithm::HS512),
"RS256" => Ok(Algorithm::RS256),
"RS384" => Ok(Algorithm::RS384),
"RS512" => Ok(Algorithm::RS512),
_ => Err(Error::InvalidJwtAlgorithm(self.algorithm.clone())),
}
}

pub fn get_decoding_secret(&self) -> JwtSecret {
self.get_secret(&self.decoding_secret)
}

pub fn get_encoding_secret(&self) -> JwtSecret {
self.get_secret(&self.encoding_secret)
}

fn get_secret(&self, secret: &str) -> JwtSecret {
if self.use_base64_secret {
JwtSecret::Base64(secret.to_string())
} else {
JwtSecret::Default(secret.to_string())
}
}
}
7 changes: 4 additions & 3 deletions server/src/http/http_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,13 @@ pub async fn start(config: HttpConfig, system: Arc<RwLock<System>>) {
}

fn build_app_state(config: &HttpConfig, system: Arc<RwLock<System>>) -> Arc<AppState> {
if config.jwt.secret.is_empty() {
panic!("JWT secret is empty");
let jwt_manager = JwtManager::from_config(&config.jwt);
if let Err(error) = jwt_manager {
panic!("Failed to initialize JWT manager: {}", error);
}

Arc::new(AppState {
jwt_manager: JwtManager::new(&config.jwt.secret, config.jwt.expiry),
jwt_manager: jwt_manager.unwrap(),
system,
})
}
Expand Down
Loading

0 comments on commit e84d240

Please sign in to comment.