use std::sync::{Arc, OnceLock};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
mod csprng;
pub mod generator;
pub mod jwt;
pub mod map;
pub mod validator;
pub(crate) use csprng::random_hex;
pub use generator::{TokenGenerator, generate_unique};
pub use jwt::{JwtAlgorithm, JwtClaims, JwtManager};
pub use validator::TokenValidator;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct TokenValue(Arc<str>);
impl TokenValue {
pub fn new(value: impl AsRef<str>) -> Self {
Self(Arc::from(value.as_ref()))
}
#[inline]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Serialize for TokenValue {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_str(&self.0)
}
}
impl<'de> Deserialize<'de> for TokenValue {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Self(Arc::from(s)))
}
}
impl From<String> for TokenValue {
fn from(s: String) -> Self {
Self(Arc::from(s))
}
}
impl From<&str> for TokenValue {
fn from(s: &str) -> Self {
Self(Arc::from(s))
}
}
impl From<TokenValue> for String {
fn from(v: TokenValue) -> Self {
v.0.to_string()
}
}
impl std::fmt::Display for TokenValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
pub fn intern_login_type(s: &str) -> Arc<str> {
if s.is_empty() || s == crate::keys::LOGIN_TYPE_DEFAULT || s == "login" {
static DEFAULT: OnceLock<Arc<str>> = OnceLock::new();
return DEFAULT
.get_or_init(|| Arc::from(crate::keys::LOGIN_TYPE_DEFAULT))
.clone();
}
Arc::from(s)
}
mod arc_str_serde {
use serde::{Deserialize, Deserializer, Serializer};
use std::sync::Arc;
pub(super) fn serialize<S: Serializer>(
value: &Arc<str>,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.serialize_str(value)
}
pub(super) fn deserialize<'de, D: Deserializer<'de>>(
deserializer: D,
) -> Result<Arc<str>, D::Error> {
let s = String::deserialize(deserializer)?;
Ok(Arc::from(s))
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenInfo {
pub token: TokenValue,
#[serde(with = "arc_str_serde")]
pub login_id: Arc<str>,
#[serde(with = "arc_str_serde")]
pub login_type: Arc<str>,
pub create_time: DateTime<Utc>,
pub last_active_time: DateTime<Utc>,
pub expire_time: Option<DateTime<Utc>>,
pub device: Option<String>,
pub extra_data: Option<serde_json::Value>,
pub nonce: Option<String>,
pub refresh_token: Option<String>,
pub refresh_token_expire_time: Option<DateTime<Utc>>,
#[serde(default)]
pub active_timeout_override: Option<i64>,
}
impl TokenInfo {
pub fn new(token: TokenValue, login_id: impl AsRef<str>) -> Self {
let now = Utc::now();
Self {
token,
login_id: Arc::from(login_id.as_ref()),
login_type: intern_login_type(crate::keys::LOGIN_TYPE_DEFAULT),
create_time: now,
last_active_time: now,
expire_time: None,
device: None,
extra_data: None,
nonce: None,
refresh_token: None,
refresh_token_expire_time: None,
active_timeout_override: None,
}
}
pub fn effective_active_timeout(&self, config: &crate::config::SaTokenConfig) -> i64 {
if config.dynamic_active_timeout {
self.active_timeout_override
.unwrap_or(config.active_timeout)
} else {
config.active_timeout
}
}
pub fn is_expired(&self) -> bool {
if let Some(expire_time) = self.expire_time {
Utc::now() > expire_time
} else {
false
}
}
pub fn update_active_time(&mut self) {
self.last_active_time = Utc::now();
}
pub fn is_freeze(&self, active_timeout: i64) -> bool {
if active_timeout <= 0 {
return false;
}
Utc::now()
.signed_duration_since(self.last_active_time)
.num_seconds()
> active_timeout
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_freeze_respects_active_timeout() {
let mut info = TokenInfo::new(TokenValue::new("t"), "u");
info.last_active_time = Utc::now() - chrono::Duration::seconds(120);
assert!(info.is_freeze(60));
assert!(!info.is_freeze(-1));
assert!(!info.is_freeze(0));
}
}
#[derive(Debug, Clone)]
pub struct TokenSign {
pub value: String,
pub device: Option<String>,
}