Skip to content
Clone
a06673b
169 lines 3.7 KB Rust
use base64::Engine;
use rand::Rng;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
fn now_ts() -> i64 {
chrono_like_now()
}
fn chrono_like_now() -> i64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs() as i64)
.unwrap_or(0)
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AccessClaims {
pub sub: Uuid,
pub uname: String,
pub is_admin: bool,
pub is_restricted: bool,
pub iat: i64,
pub exp: i64,
pub iss: String,
pub aud: String,
pub jti: Uuid,
pub scope: String,
pub kid: String,
/// Snapshot of `users.passwd_version` at issuance. A password reset
/// (or any future bump) increments the DB counter; refresh- and
/// access-token validation MUST reject tokens whose pwd_version lags
/// behind the live value.
pub pwd_version: i64,
}
impl AccessClaims {
#[allow(clippy::too_many_arguments)]
pub fn new(
sub: Uuid,
uname: String,
is_admin: bool,
is_restricted: bool,
pwd_version: i64,
ttl: i64,
issuer: &str,
audience: &str,
kid: &str,
) -> Self {
let iat = now_ts();
Self {
sub,
uname,
is_admin,
is_restricted,
iat,
exp: iat + ttl,
iss: issuer.to_string(),
aud: audience.to_string(),
jti: Uuid::new_v4(),
scope: String::new(),
kid: kid.to_string(),
pwd_version,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshClaims {
pub sub: Uuid,
pub family_id: Uuid,
pub jti: Uuid,
pub iat: i64,
pub exp: i64,
pub iss: String,
pub aud: String,
pub kid: String,
pub parent_id: Option<Uuid>,
/// Snapshot of `users.passwd_version` at issuance. See AccessClaims.
pub pwd_version: i64,
}
impl RefreshClaims {
#[allow(clippy::too_many_arguments)]
pub fn new(
sub: Uuid,
family_id: Uuid,
parent_id: Option<Uuid>,
pwd_version: i64,
ttl: i64,
issuer: &str,
audience: &str,
kid: &str,
) -> Self {
let iat = now_ts();
Self {
sub,
family_id,
jti: Uuid::new_v4(),
iat,
exp: iat + ttl,
iss: issuer.to_string(),
aud: audience.to_string(),
kid: kid.to_string(),
parent_id,
pwd_version,
}
}
}
pub trait HasUserExt {
fn user_id(&self) -> Uuid;
fn jti(&self) -> Uuid;
}
pub trait HasJti {
fn jti(&self) -> Uuid;
}
impl HasUserExt for AccessClaims {
fn user_id(&self) -> Uuid {
self.sub
}
fn jti(&self) -> Uuid {
self.jti
}
}
impl HasUserExt for RefreshClaims {
fn user_id(&self) -> Uuid {
self.sub
}
fn jti(&self) -> Uuid {
self.jti
}
}
impl HasJti for AccessClaims {
fn jti(&self) -> Uuid {
self.jti
}
}
impl HasJti for RefreshClaims {
fn jti(&self) -> Uuid {
self.jti
}
}
pub fn random_bytes(n: usize) -> Vec<u8> {
let mut buf = vec![0u8; n];
rand::rng().fill_bytes(&mut buf);
buf
}
pub fn sha256_hex(data: &[u8]) -> String {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(data);
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(h.finalize())
}
pub fn b64_encode(data: &[u8]) -> String {
base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(data)
}
pub fn b64_decode(s: &str) -> Result<Vec<u8>, base64::DecodeError> {
base64::engine::general_purpose::URL_SAFE_NO_PAD.decode(s)
}