use crate::error::Result;
use std::collections::BTreeMap;
use std::time::Duration;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TokenClaims {
pub subject: String,
pub roles: Vec<String>,
pub permissions: Vec<String>,
pub extra: BTreeMap<String, String>,
pub expires_in: Option<Duration>,
}
impl TokenClaims {
pub fn new(subject: impl Into<String>) -> Self {
Self {
subject: subject.into(),
..Self::default()
}
}
pub fn with_roles<I, S>(mut self, roles: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.roles = roles.into_iter().map(Into::into).collect();
self
}
pub fn with_permissions<I, S>(mut self, permissions: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.permissions = permissions.into_iter().map(Into::into).collect();
self
}
pub fn with_claim(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.extra.insert(key.into(), value.into());
self
}
pub fn expires_in(mut self, lifetime: Duration) -> Self {
self.expires_in = Some(lifetime);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IssuedToken {
pub token: String,
pub jti: String,
pub expires_at_unix_secs: u64,
}
#[async_trait::async_trait]
pub trait ITokenIssuer: Send + Sync {
async fn issue(&self, claims: TokenClaims) -> Result<IssuedToken>;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn token_claims_builder_sets_every_dimension() {
let claims = TokenClaims::new("user-1")
.with_roles(["admin", "user"])
.with_permissions(["order.create"])
.with_claim("tenant_id", "acme")
.expires_in(Duration::from_secs(60));
assert_eq!(claims.subject, "user-1");
assert_eq!(claims.roles, vec!["admin", "user"]);
assert_eq!(claims.permissions, vec!["order.create"]);
assert_eq!(
claims.extra.get("tenant_id").map(String::as_str),
Some("acme")
);
assert_eq!(claims.expires_in, Some(Duration::from_secs(60)));
}
#[test]
fn token_claims_default_lifetime_is_the_issuers_default() {
assert_eq!(TokenClaims::new("user-1").expires_in, None);
}
}