use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KeyClass {
Secret,
Publishable,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct Limits {
#[serde(skip_serializing_if = "Option::is_none")]
pub max_connections: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_subscriptions: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_snapshot_rows: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_messages_per_minute: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_bytes_per_minute: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionClaims {
pub iss: String,
pub sub: String,
pub aud: String,
pub iat: u64,
pub nbf: u64,
pub exp: u64,
pub jti: String,
pub scope: String,
pub metering_key: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub deployment_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub origin: Option<String>,
#[serde(skip_serializing_if = "Option::is_none", rename = "client_ip")]
pub client_ip: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub limits: Option<Limits>,
#[serde(skip_serializing_if = "Option::is_none")]
pub plan: Option<String>,
#[serde(rename = "key_class")]
pub key_class: KeyClass,
}
impl SessionClaims {
pub fn builder(
iss: impl Into<String>,
sub: impl Into<String>,
aud: impl Into<String>,
) -> SessionClaimsBuilder {
SessionClaimsBuilder::new(iss, sub, aud)
}
pub fn is_expired(&self, now: u64) -> bool {
self.exp <= now
}
pub fn is_valid(&self, now: u64) -> bool {
self.nbf <= now && self.iat <= now
}
}
pub struct SessionClaimsBuilder {
iss: String,
sub: String,
aud: String,
iat: u64,
nbf: u64,
exp: u64,
jti: String,
scope: String,
metering_key: String,
deployment_id: Option<String>,
origin: Option<String>,
client_ip: Option<String>,
limits: Option<Limits>,
plan: Option<String>,
key_class: KeyClass,
}
impl SessionClaimsBuilder {
fn new(iss: impl Into<String>, sub: impl Into<String>, aud: impl Into<String>) -> Self {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.expect("time should not be before epoch")
.as_secs();
Self {
iss: iss.into(),
sub: sub.into(),
aud: aud.into(),
iat: now,
nbf: now,
exp: now + crate::DEFAULT_SESSION_TTL_SECONDS,
jti: uuid::Uuid::new_v4().to_string(),
scope: "read".to_string(),
metering_key: String::new(),
deployment_id: None,
origin: None,
client_ip: None,
limits: None,
plan: None,
key_class: KeyClass::Publishable,
}
}
pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
self.exp = self.iat + ttl_seconds;
self
}
pub fn with_scope(mut self, scope: impl Into<String>) -> Self {
self.scope = scope.into();
self
}
pub fn with_metering_key(mut self, key: impl Into<String>) -> Self {
self.metering_key = key.into();
self
}
pub fn with_deployment_id(mut self, id: impl Into<String>) -> Self {
self.deployment_id = Some(id.into());
self
}
pub fn with_origin(mut self, origin: impl Into<String>) -> Self {
self.origin = Some(origin.into());
self
}
pub fn with_client_ip(mut self, client_ip: impl Into<String>) -> Self {
self.client_ip = Some(client_ip.into());
self
}
pub fn with_limits(mut self, limits: Limits) -> Self {
self.limits = Some(limits);
self
}
pub fn with_plan(mut self, plan: impl Into<String>) -> Self {
self.plan = Some(plan.into());
self
}
pub fn with_key_class(mut self, key_class: KeyClass) -> Self {
self.key_class = key_class;
self
}
pub fn with_jti(mut self, jti: impl Into<String>) -> Self {
self.jti = jti.into();
self
}
pub fn build(self) -> SessionClaims {
SessionClaims {
iss: self.iss,
sub: self.sub,
aud: self.aud,
iat: self.iat,
nbf: self.nbf,
exp: self.exp,
jti: self.jti,
scope: self.scope,
metering_key: self.metering_key,
deployment_id: self.deployment_id,
origin: self.origin,
client_ip: self.client_ip,
limits: self.limits,
plan: self.plan,
key_class: self.key_class,
}
}
}
#[derive(Debug, Clone)]
pub struct AuthContext {
pub subject: String,
pub issuer: String,
pub key_class: KeyClass,
pub metering_key: String,
pub deployment_id: Option<String>,
pub expires_at: u64,
pub scope: String,
pub limits: Limits,
pub plan: Option<String>,
pub origin: Option<String>,
pub client_ip: Option<String>,
pub jti: String,
}
impl AuthContext {
pub fn from_claims(claims: SessionClaims) -> Self {
Self {
subject: claims.sub,
issuer: claims.iss,
key_class: claims.key_class,
metering_key: claims.metering_key,
deployment_id: claims.deployment_id,
expires_at: claims.exp,
scope: claims.scope,
limits: claims.limits.unwrap_or_default(),
plan: claims.plan,
origin: claims.origin,
client_ip: claims.client_ip,
jti: claims.jti,
}
}
}