Skip to main content

Users and permissions

· 7 min read
Piotr Gankiewicz
Iggy.rs founder

In the most recent update, the Iggy server as well as the clients for all the available transport protocols have been extended with the support for users and permissions. From now on, you can additionally secure your data using the authentication and authorization by specifying the granular set of permissions for each user.

Breaking changes

In general, there are no breaking changes, except a new field added to the ClientInfo and ClientInfoDetails structs returned by get_client() and get_clients() methods.

pub struct ClientInfo {
pub client_id: u32,
pub user_id: Option<u32>, // New optional field
pub address: String,
pub transport: String,
pub consumer_groups_count: u32,
}

The user_id field is optional, and it's only returned when the user is authenticated. Otherwise, it's set to None (0 value for binary transport) or null (HTTP transport).

Keep in mind that the client represents the application connected to the server - since it's possible that multiple applications would share the same user credentials, thus you can think of a client as a single connection from the application to the server (1 user -> N clients).

This breaking change has been introduced with the commit #23b3309. The available iggy crate supports these changes since version 0.0.80.

Configuration

Currently, the authentication and authorization can be enabled in the server.toml configuration via [system.user] section:

[system.user]
authentication_enabled = true
authorization_enabled = true

This might change in the future releases (e.g. authentication and authorization will always be enabled), but in order to avoid the breaking changes to the existing clients SDKs, these options are configurable for now.

The additional settings for HTTP API which uses JWT (JSON Web Token), can be found in the [http.jwt] section:

[http.jwt]
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

Authentication and authorization

The overall implementation of the users authentication and authorization is rather typical. All the server methods except ping and login require the user to be authenticated. The login method is used to authenticate the user, and it returns the optional token which is then used to authenticate the user in the subsequent requests. For now, the token is only used by HTTP API based on JWT, for the binary transport there's no token - since, it's a stateful protocol, the server keeps track of the authenticated connections.

The authorization is based on the permissions, which are defined for each user. All the server methods have the specific authorization policies applied. For example, you can allow the user to read all the streams, or manage all the topics, while on the other hand, you could also specify only the set of particular streams or topics to which the user can send and/or poll the message to/from.

Take a look at the following JSON example to get the overall idea:

{
"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
}
}
}
}
}
}

Each user might have the global permissions which do apply to all the available streams and topics, as well as the granular permissions for each stream and its topics (this is a simple hashmap using the stream or topic ID as its key).

For example, the root user, which has the default username and password iggy and cannot be updated or deleted, can do everything, simply by having the following permissions:

{
"permissions": {
"global": {
"manage_servers": true,
"read_servers": true,
"manage_users": true,
"read_users": true,
"manage_streams": true,
"read_streams": true,
"manage_topics": true,
"read_topics": true,
"poll_messages": true,
"send_messages": true
},
"streams": null
}
}

SDK

The new UserClient trait has been added in order to support the users and permissions feature. It is implemented for all the available transport protocols.

pub trait UserClient {
async fn get_user(&self, command: &GetUser) -> Result<UserInfoDetails, Error>;
async fn get_users(&self, command: &GetUsers) -> Result<Vec<UserInfo>, Error>;
async fn create_user(&self, command: &CreateUser) -> Result<(), Error>;
async fn delete_user(&self, command: &DeleteUser) -> Result<(), Error>;
async fn update_user(&self, command: &UpdateUser) -> Result<(), Error>;
async fn update_permissions(&self, command: &UpdatePermissions) -> Result<(), Error>;
async fn change_password(&self, command: &ChangePassword) -> Result<(), Error>;
async fn login_user(&self, command: &LoginUser) -> Result<IdentityInfo, Error>;
async fn logout_user(&self, command: &LogoutUser) -> Result<(), Error>;
}

Commands and models

The following commands and response models have been added:

pub struct IdentityInfo {
pub user_id: u32,
pub token: Option<String>,
}
pub struct UserInfo {
pub id: u32,
pub created_at: u64,
pub status: UserStatus,
pub username: String,
}

pub struct UserInfoDetails {
pub id: u32,
pub created_at: u64,
pub status: UserStatus,
pub username: String,
pub permissions: Option<Permissions>,
}
pub struct Permissions {
pub global: GlobalPermissions,
pub streams: Option<HashMap<u32, StreamPermissions>>,
}

pub struct GlobalPermissions {
pub manage_servers: bool,
pub read_servers: bool,
pub manage_users: bool,
pub read_users: bool,
pub manage_streams: bool,
pub read_streams: bool,
pub manage_topics: bool,
pub read_topics: bool,
pub poll_messages: bool,
pub send_messages: bool,
}

pub struct StreamPermissions {
pub manage_stream: bool,
pub read_stream: bool,
pub manage_topics: bool,
pub read_topics: bool,
pub poll_messages: bool,
pub send_messages: bool,
pub topics: Option<HashMap<u32, TopicPermissions>>,
}

pub struct TopicPermissions {
pub manage_topic: bool,
pub read_topic: bool,
pub poll_messages: bool,
pub send_messages: bool,
}
pub struct GetUser {
pub user_id: Identifier,
}
pub struct GetUsers {}
pub struct CreateUser {
pub username: String,
pub password: String,
pub status: UserStatus,
pub permissions: Option<Permissions>,
}
pub struct DeleteUser {
pub user_id: Identifier,
}
pub struct UpdateUser {
pub user_id: Identifier,
pub username: Option<String>,
pub status: Option<UserStatus>,
}
pub struct UpdatePermissions {
pub user_id: Identifier,
pub permissions: Option<Permissions>,
}
pub struct ChangePassword {
pub user_id: Identifier,
pub current_password: String,
pub new_password: String,
}
pub struct LoginUser {
pub username: String,
pub password: String,
}
pub struct LogoutUser {}

Similar to the stream and topic ID, the user will always have the unique numeric ID and the username. The username must be within the range of 3–50 chars and will always be lowercased, using the same regex as the stream and topic name ^[\w\.\-\s]+$. The password must be within the range of 3–100 chars and will be hashed using the bcrypt algorithm.

HTTP API

The following endpoints have been added, and as always, you can find them in the server.http file in the main repository.

POST {{url}}/users/login
Content-Type: application/json

{
"username": "iggy",
"password": "iggy"
}

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

{
}

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

{
"username": "user1",
"password": "secret",
"status": "active",
"permissions": null
}

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

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

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

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

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

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

PUT {{url}}/users/user1permissions
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
Authorization: Bearer {{token}}