#![warn(missing_docs)]
use data_encoding::{BASE32HEX_NOPAD, BASE64URL_NOPAD};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::{convert::TryInto, time::SystemTime};
pub use nkeys::KeyPair;
const JWT_HEADER: &str = r#"{"typ":"JWT","alg":"ed25519-nkey"}"#;
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
#[serde(rename = "iat")]
pub issued_at: i64,
#[serde(rename = "iss")]
pub issuer: String,
#[serde(rename = "jti")]
pub jwt_id: String,
pub sub: String,
pub name: String,
pub nats: NatsClaims,
#[serde(rename = "exp", skip_serializing_if = "Option::is_none")]
pub expires: Option<i64>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
pub enum NatsClaims {
User {
#[serde(flatten)]
permissions: NatsPermissionsMap,
issuer_account: String,
subs: i64,
data: i64,
payload: i64,
bearer_token: bool,
version: i64,
},
Account {
limits: NatsAccountLimits,
#[serde(skip_serializing_if = "Vec::is_empty")]
signing_keys: Vec<String>,
default_permissions: NatsPermissionsMap,
version: i64,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NatsPermissions {
#[serde(skip_serializing_if = "Vec::is_empty")]
pub allow: Vec<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub deny: Vec<String>,
}
impl NatsPermissions {
#[must_use]
pub fn is_empty(&self) -> bool {
self.allow.is_empty() && self.deny.is_empty()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct NatsPermissionsMap {
#[serde(rename = "pub", skip_serializing_if = "NatsPermissions::is_empty")]
pub publish: NatsPermissions,
#[serde(rename = "sub", skip_serializing_if = "NatsPermissions::is_empty")]
pub subscribe: NatsPermissions,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct NatsAccountLimits {
pub subs: i64,
pub data: i64,
pub payload: i64,
pub imports: i64,
pub exports: i64,
pub wildcards: bool,
pub conn: i64,
pub leaf: i64,
}
#[derive(Debug, Clone)]
pub struct CommonNatsClaims {
pub max_subscriptions: i64,
pub max_data: i64,
pub max_payload: i64,
pub permissions: NatsPermissionsMap,
}
pub trait IntoNatsClaims {
fn into_nats_claims(self, common: CommonNatsClaims) -> NatsClaims;
}
pub trait IntoPublicKey {
fn into_public_key(self) -> String;
}
impl IntoPublicKey for &KeyPair {
fn into_public_key(self) -> String {
self.public_key()
}
}
impl IntoPublicKey for String {
fn into_public_key(self) -> String {
self
}
}
impl IntoPublicKey for &str {
fn into_public_key(self) -> String {
self.to_string()
}
}
pub struct User {
bearer_token: bool,
issuer_account_id: String,
}
impl IntoNatsClaims for User {
fn into_nats_claims(self, common: CommonNatsClaims) -> NatsClaims {
NatsClaims::User {
permissions: common.permissions,
issuer_account: self.issuer_account_id,
subs: common.max_subscriptions,
data: common.max_data,
payload: common.max_payload,
bearer_token: self.bearer_token,
version: 2,
}
}
}
pub struct Account {
signing_keys: Vec<String>,
max_imports: i64,
max_exports: i64,
max_connections: i64,
max_leaf_nodes: i64,
allow_wildcards: bool,
}
impl IntoNatsClaims for Account {
fn into_nats_claims(self, common: CommonNatsClaims) -> NatsClaims {
NatsClaims::Account {
default_permissions: common.permissions,
limits: NatsAccountLimits {
subs: common.max_subscriptions,
data: common.max_data,
payload: common.max_payload,
imports: self.max_imports,
exports: self.max_exports,
wildcards: self.allow_wildcards,
conn: self.max_connections,
leaf: self.max_leaf_nodes,
},
signing_keys: self.signing_keys,
version: 2,
}
}
}
#[derive(Debug, Clone)]
pub struct Token<T: IntoNatsClaims> {
kind: T,
subject: String,
name: Option<String>,
nats: CommonNatsClaims,
expires: Option<i64>,
}
impl<T: IntoNatsClaims> Token<T> {
fn new(kind: T, subject: String) -> Self {
Self {
kind,
subject,
name: None,
nats: CommonNatsClaims {
max_subscriptions: -1,
max_payload: -1,
max_data: -1,
permissions: NatsPermissionsMap::default(),
},
expires: None,
}
}
#[must_use]
pub fn name(mut self, name: impl Into<String>) -> Self {
self.name = Some(name.into());
self
}
#[must_use]
pub fn max_subscriptions(mut self, max_subscriptions: i64) -> Self {
self.nats.max_subscriptions = max_subscriptions;
self
}
#[must_use]
pub fn max_payload(mut self, max_payload: i64) -> Self {
self.nats.max_payload = max_payload;
self
}
#[must_use]
pub fn max_data(mut self, max_data: i64) -> Self {
self.nats.max_data = max_data;
self
}
#[must_use]
pub fn allow_publish(mut self, subject: impl Into<String>) -> Self {
self.nats.permissions.publish.allow.push(subject.into());
self
}
#[must_use]
pub fn deny_publish(mut self, subject: impl Into<String>) -> Self {
self.nats.permissions.publish.deny.push(subject.into());
self
}
#[must_use]
pub fn allow_subscribe(mut self, subject: impl Into<String>) -> Self {
self.nats.permissions.subscribe.allow.push(subject.into());
self
}
#[must_use]
pub fn deny_subscribe(mut self, subject: impl Into<String>) -> Self {
self.nats.permissions.subscribe.deny.push(subject.into());
self
}
#[must_use]
pub fn expires(mut self, expires: i64) -> Self {
self.expires = Some(expires);
self
}
pub fn sign(self, signing_key: &KeyPair) -> String {
let issued_at = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("system time is after the unix epoch")
.as_secs()
.try_into()
.expect("seconds from UNIX epoch cannot be represented in a i64");
let subject = self.subject.clone();
let mut claims = Claims {
issued_at,
issuer: signing_key.public_key(),
jwt_id: String::new(),
name: self.name.unwrap_or_else(|| subject.clone()),
sub: subject,
nats: self.kind.into_nats_claims(self.nats),
expires: self.expires,
};
let claims_str = serde_json::to_string(&claims).expect("claims serialisation cannot fail");
let mut hasher = Sha256::new();
hasher.update(claims_str);
let claims_hash = hasher.finalize();
claims.jwt_id = BASE32HEX_NOPAD.encode(claims_hash.as_slice());
let claims_str = serde_json::to_string(&claims).expect("claims serialisation cannot fail");
let b64_header = BASE64URL_NOPAD.encode(JWT_HEADER.as_bytes());
let b64_body = BASE64URL_NOPAD.encode(claims_str.as_bytes());
let jwt_half = format!("{b64_header}.{b64_body}");
let sig = signing_key.sign(jwt_half.as_bytes()).unwrap();
let b64_sig = BASE64URL_NOPAD.encode(&sig);
format!("{jwt_half}.{b64_sig}")
}
}
impl Token<User> {
pub fn new_user(issuer_account_id: impl Into<String>, user_key_pub: impl Into<String>) -> Self {
Self::new(
User {
bearer_token: false,
issuer_account_id: issuer_account_id.into(),
},
user_key_pub.into(),
)
}
#[must_use]
pub fn bearer_token(mut self, bearer_token: bool) -> Self {
self.kind.bearer_token = bearer_token;
self
}
}
impl Token<Account> {
pub fn new_account(account_key_pub: impl Into<String>) -> Self {
Self::new(
Account {
signing_keys: vec![],
max_imports: -1,
max_exports: -1,
max_connections: -1,
max_leaf_nodes: -1,
allow_wildcards: true,
},
account_key_pub.into(),
)
}
#[must_use]
pub fn add_signing_key(mut self, signing_key: impl IntoPublicKey) -> Self {
self.kind.signing_keys.push(signing_key.into_public_key());
self
}
#[must_use]
pub fn max_imports(mut self, max_imports: i64) -> Self {
self.kind.max_imports = max_imports;
self
}
#[must_use]
pub fn max_exports(mut self, max_exports: i64) -> Self {
self.kind.max_exports = max_exports;
self
}
#[must_use]
pub fn max_connections(mut self, max_connections: i64) -> Self {
self.kind.max_connections = max_connections;
self
}
#[must_use]
pub fn max_leaf_nodes(mut self, max_leaf_nodes: i64) -> Self {
self.kind.max_leaf_nodes = max_leaf_nodes;
self
}
#[must_use]
pub fn allow_wildcards(mut self, allow_wildcards: bool) -> Self {
self.kind.allow_wildcards = allow_wildcards;
self
}
}