use std::borrow::Cow;
use std::sync::Arc;
use std::sync::OnceLock;
use crate::context::SaTokenContext;
use crate::event::{SaTokenEventBus, SaTokenListener};
use crate::keys::LOGIN_TYPE_DEFAULT;
use crate::session::SaSession;
use crate::token::{TokenInfo, TokenValue};
use crate::{SaTokenError, SaTokenManager, SaTokenResult};
static GLOBAL_MANAGER: OnceLock<Arc<SaTokenManager>> = OnceLock::new();
pub trait LoginId {
fn as_login_id(&self) -> Cow<'_, str>;
fn to_login_id(&self) -> String {
self.as_login_id().into_owned()
}
}
impl LoginId for str {
fn as_login_id(&self) -> Cow<'_, str> {
Cow::Borrowed(self)
}
}
impl LoginId for String {
fn as_login_id(&self) -> Cow<'_, str> {
Cow::Borrowed(self.as_str())
}
}
impl LoginId for &String {
fn as_login_id(&self) -> Cow<'_, str> {
Cow::Borrowed(self.as_str())
}
}
impl LoginId for &str {
fn as_login_id(&self) -> Cow<'_, str> {
Cow::Borrowed(*self)
}
}
macro_rules! impl_login_id_display {
($($t:ty),*) => {$(
impl LoginId for $t {
fn as_login_id(&self) -> Cow<'_, str> {
Cow::Owned(self.to_string())
}
}
)*};
}
impl_login_id_display!(i32, i64, u32, u64, i16, u16, isize, usize);
pub struct StpUtil;
impl StpUtil {
pub fn try_init_manager(manager: SaTokenManager) -> SaTokenResult<()> {
GLOBAL_MANAGER
.set(Arc::new(manager))
.map_err(|_| SaTokenError::AlreadyInitialized)
}
#[deprecated(note = "use try_init_manager() which returns Result instead of panicking")]
#[allow(clippy::panic)]
pub fn init_manager(manager: SaTokenManager) {
Self::try_init_manager(manager).unwrap_or_else(|e| {
panic!("{e}");
});
}
pub fn try_get_manager() -> SaTokenResult<&'static Arc<SaTokenManager>> {
GLOBAL_MANAGER.get().ok_or(SaTokenError::NotInitialized)
}
#[track_caller]
#[allow(dead_code, clippy::panic)]
pub(crate) fn get_manager() -> &'static Arc<SaTokenManager> {
Self::try_get_manager().unwrap_or_else(|e| {
panic!("{e}. Call StpUtil::try_init_manager() first.");
})
}
pub(crate) fn try_get_config() -> Option<&'static crate::config::SaTokenConfig> {
GLOBAL_MANAGER.get().map(|m| m.config.as_ref())
}
#[inline]
fn resolve_login_type() -> Cow<'static, str> {
match SaTokenContext::current_login_type() {
Some(login_type) => Cow::Owned(login_type),
None => Cow::Borrowed(LOGIN_TYPE_DEFAULT),
}
}
pub fn event_bus() -> Option<&'static SaTokenEventBus> {
GLOBAL_MANAGER.get().map(|m| &m.event_bus)
}
pub fn register_listener(listener: Arc<dyn SaTokenListener>) {
if let Some(bus) = Self::event_bus() {
bus.register(listener);
}
}
pub async fn login(login_id: impl LoginId) -> SaTokenResult<TokenValue> {
Self::try_get_manager()?.login(login_id.to_login_id()).await
}
pub async fn login_with_type(
login_id: impl LoginId,
login_type: impl Into<String>,
) -> SaTokenResult<TokenValue> {
Self::try_get_manager()?
.login_with_options(
login_id.to_login_id(),
Some(login_type.into()),
None,
None,
None,
None,
)
.await
}
pub async fn login_with_extra(
login_id: impl LoginId,
extra_data: serde_json::Value,
) -> SaTokenResult<TokenValue> {
Self::try_get_manager()?
.login_with_options(
login_id.to_login_id(),
None, None, Some(extra_data),
None, None, )
.await
}
pub async fn login_with_manager(
manager: &SaTokenManager,
login_id: impl Into<String>,
) -> SaTokenResult<TokenValue> {
manager.login(login_id).await
}
pub async fn logout(token: &TokenValue) -> SaTokenResult<()> {
tracing::debug!("开始执行 logout,token: {}", token);
let result = Self::try_get_manager()?.logout(token).await;
match &result {
Ok(_) => tracing::debug!("logout 执行成功,token: {}", token),
Err(e) => tracing::debug!("logout 执行失败,token: {}, 错误: {}", token, e),
}
result
}
pub async fn logout_with_manager(
manager: &SaTokenManager,
token: &TokenValue,
) -> SaTokenResult<()> {
manager.logout(token).await
}
pub fn write_token_cookie<R: sa_token_adapter::context::SaResponse>(
res: &mut R,
token: &TokenValue,
) -> SaTokenResult<()> {
let manager = Self::try_get_manager()?;
crate::token_io::write_token_cookie(res, token, &manager.config);
Ok(())
}
pub fn delete_token_cookie<R: sa_token_adapter::context::SaResponse>(
res: &mut R,
) -> SaTokenResult<()> {
let manager = Self::try_get_manager()?;
crate::token_io::delete_token_cookie(res, &manager.config);
Ok(())
}
pub async fn update_active_timeout(token: &TokenValue, seconds: i64) -> SaTokenResult<()> {
Self::try_get_manager()?
.update_active_timeout(token, seconds)
.await
}
pub async fn kick_out(login_id: impl LoginId) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::kick_out_with_type(login_type.as_ref(), login_id).await
}
pub async fn kick_out_with_type(login_type: &str, login_id: impl LoginId) -> SaTokenResult<()> {
Self::try_get_manager()?
.kick_out(login_type, &login_id.to_login_id())
.await
}
pub async fn kick_out_with_manager(
manager: &SaTokenManager,
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<()> {
manager.kick_out(login_type, &login_id.to_login_id()).await
}
pub async fn logout_by_login_id(login_id: impl LoginId) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::logout_by_login_id_with_type(login_type.as_ref(), login_id).await
}
pub async fn logout_by_login_id_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.logout_by_login_id(login_type, &login_id.to_login_id())
.await
}
pub async fn logout_by_token(token: &TokenValue) -> SaTokenResult<()> {
Self::logout(token).await
}
pub fn get_token_value() -> SaTokenResult<TokenValue> {
let ctx = SaTokenContext::try_current().ok_or(SaTokenError::NotLogin)?;
ctx.token().ok_or(SaTokenError::NotLogin)
}
pub async fn logout_current() -> SaTokenResult<()> {
let token = Self::get_token_value()?;
tracing::debug!("成功获取 token: {}", token);
let result = Self::logout(&token).await;
match &result {
Ok(_) => tracing::debug!("logout_current 执行成功,token: {}", token),
Err(e) => tracing::debug!("logout_current 执行失败,token: {}, 错误: {}", token, e),
}
result
}
pub fn is_login_current() -> bool {
Self::get_token_value().is_ok()
}
pub fn check_login_current() -> SaTokenResult<()> {
Self::get_token_value()?;
Ok(())
}
pub async fn check_login_current_async() -> SaTokenResult<()> {
let token = Self::get_token_value()?;
if !Self::try_get_manager()?.is_valid(&token).await {
return Err(SaTokenError::NotLogin);
}
Ok(())
}
pub async fn is_login_current_async() -> bool {
Self::check_login_current_async().await.is_ok()
}
pub async fn get_login_id_as_string() -> SaTokenResult<String> {
if let Some(ctx) = SaTokenContext::get_current() {
if let Some(switch_id) = ctx.switch_login_id() {
return Ok(switch_id);
}
}
let token = Self::get_token_value()?;
Self::get_login_id(&token).await
}
pub async fn get_login_id_as_long() -> SaTokenResult<i64> {
let login_id_str = Self::get_login_id_as_string().await?;
login_id_str
.parse::<i64>()
.map_err(|_| SaTokenError::LoginIdNotNumber)
}
pub fn get_token_info_current() -> SaTokenResult<Arc<TokenInfo>> {
let ctx = SaTokenContext::try_current().ok_or(SaTokenError::NotLogin)?;
ctx.token_info().ok_or(SaTokenError::NotLogin)
}
pub async fn is_login(token: &TokenValue) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager.is_valid(token).await
}
pub async fn is_login_by_login_id(login_id: impl LoginId) -> bool {
match Self::get_token_by_login_id(login_id).await {
Ok(token) => Self::is_login(&token).await,
Err(_) => false,
}
}
pub async fn is_login_with_manager(manager: &SaTokenManager, token: &TokenValue) -> bool {
manager.is_valid(token).await
}
pub async fn check_login(token: &TokenValue) -> SaTokenResult<()> {
if !Self::is_login(token).await {
return Err(SaTokenError::NotLogin);
}
Ok(())
}
pub async fn get_token_info(token: &TokenValue) -> SaTokenResult<TokenInfo> {
Self::try_get_manager()?.get_token_info(token).await
}
pub async fn get_login_id(token: &TokenValue) -> SaTokenResult<String> {
let token_info = Self::try_get_manager()?.get_token_info(token).await?;
Ok(token_info.login_id.to_string())
}
pub async fn get_login_id_or_default(token: &TokenValue, default: impl Into<String>) -> String {
Self::get_login_id(token)
.await
.unwrap_or_else(|_| default.into())
}
pub async fn get_token_by_login_id(login_id: impl LoginId) -> SaTokenResult<TokenValue> {
let login_type = Self::resolve_login_type();
Self::get_token_by_login_id_with_type(login_type.as_ref(), login_id).await
}
pub async fn get_token_by_login_id_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<TokenValue> {
Self::try_get_manager()?
.get_token_by_login_id(login_type, &login_id.to_login_id())
.await
}
pub async fn get_all_tokens_by_login_id(
login_id: impl LoginId,
) -> SaTokenResult<Vec<TokenValue>> {
let login_type = Self::resolve_login_type();
Self::get_all_tokens_by_login_id_with_type(login_type.as_ref(), login_id).await
}
pub async fn get_all_tokens_by_login_id_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<Vec<TokenValue>> {
Self::try_get_manager()?
.get_all_tokens_by_login_id(login_type, &login_id.to_login_id())
.await
}
pub async fn get_session_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<SaSession> {
Self::try_get_manager()?
.get_session_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn delete_session_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.delete_session_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn get_session(login_id: impl LoginId) -> SaTokenResult<SaSession> {
let login_type = Self::resolve_login_type();
Self::get_session_with_type(login_type.as_ref(), login_id).await
}
pub async fn save_session(session: &SaSession) -> SaTokenResult<()> {
Self::try_get_manager()?.save_session(session).await
}
pub async fn delete_session(login_id: impl LoginId) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::delete_session_with_type(login_type.as_ref(), login_id).await
}
pub async fn set_session_value<T: serde::Serialize>(
login_id: impl LoginId,
key: &str,
value: T,
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
let manager = Self::try_get_manager()?;
let login_id_str = login_id.to_login_id();
let mut session = manager
.get_session_with_type(login_type.as_ref(), &login_id_str)
.await?;
session.set(key, value)?;
manager.save_session(&session).await
}
pub async fn get_session_value<T: serde::de::DeserializeOwned>(
login_id: impl LoginId,
key: &str,
) -> SaTokenResult<Option<T>> {
let login_type = Self::resolve_login_type();
let session = Self::get_session_with_type(login_type.as_ref(), login_id).await?;
Ok(session.get::<T>(key))
}
pub fn create_token(token_value: impl Into<String>) -> TokenValue {
TokenValue::new(token_value.into())
}
pub fn is_valid_token_format(token: &str) -> bool {
!token.is_empty() && token.len() >= 16
}
}
impl std::fmt::Debug for StpUtil {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("StpUtil { .. }")
}
}
impl StpUtil {
pub async fn set_permissions_with_type(
login_type: &str,
login_id: impl LoginId,
permissions: Vec<String>,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.set_permissions_with_type(login_type, &login_id.to_login_id(), permissions)
.await
}
pub async fn set_permissions(
login_id: impl LoginId,
permissions: Vec<String>,
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::set_permissions_with_type(&login_type, login_id, permissions).await
}
pub async fn add_permission_with_type(
login_type: &str,
login_id: impl LoginId,
permission: impl Into<String>,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.add_permission_with_type(login_type, &login_id.to_login_id(), permission.into())
.await
}
pub async fn add_permission(
login_id: impl LoginId,
permission: impl Into<String>,
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::add_permission_with_type(&login_type, login_id, permission).await
}
pub async fn remove_permission_with_type(
login_type: &str,
login_id: impl LoginId,
permission: &str,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.remove_permission_with_type(login_type, &login_id.to_login_id(), permission)
.await
}
pub async fn remove_permission(login_id: impl LoginId, permission: &str) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::remove_permission_with_type(&login_type, login_id, permission).await
}
pub async fn clear_permissions_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.clear_permissions_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn clear_permissions(login_id: impl LoginId) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::clear_permissions_with_type(&login_type, login_id).await
}
pub async fn try_get_permissions_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<Vec<String>> {
Self::try_get_manager()?
.get_permissions_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn try_get_permissions(login_id: impl LoginId) -> SaTokenResult<Vec<String>> {
let login_type = Self::resolve_login_type();
Self::try_get_permissions_with_type(&login_type, login_id).await
}
pub async fn get_permissions(login_id: impl LoginId) -> Vec<String> {
let login_id = login_id.to_login_id();
match Self::try_get_permissions(&login_id).await {
Ok(list) => list,
Err(e) => {
tracing::warn!(
login_id = %login_id,
error = %e,
"failed to load permissions, treating as empty"
);
Vec::new()
}
}
}
pub async fn has_permission_with_type(
login_type: &str,
login_id: impl LoginId,
permission: &str,
) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_permission(login_type, &login_id.to_login_id(), permission)
.await
.unwrap_or(false)
}
pub async fn has_permission(login_id: impl LoginId, permission: &str) -> bool {
let login_type = Self::resolve_login_type();
Self::has_permission_with_type(&login_type, login_id, permission).await
}
pub async fn has_all_permissions_with_type(
login_type: &str,
login_id: impl LoginId,
permissions: &[&str],
) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_all_permissions(login_type, &login_id.to_login_id(), permissions)
.await
.unwrap_or(false)
}
pub async fn has_all_permissions(login_id: impl LoginId, permissions: &[&str]) -> bool {
let login_type = Self::resolve_login_type();
Self::has_all_permissions_with_type(&login_type, login_id, permissions).await
}
pub async fn has_permissions_and(login_id: impl LoginId, permissions: &[&str]) -> bool {
Self::has_all_permissions(login_id, permissions).await
}
pub async fn has_any_permission_with_type(
login_type: &str,
login_id: impl LoginId,
permissions: &[&str],
) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_any_permission(login_type, &login_id.to_login_id(), permissions)
.await
.unwrap_or(false)
}
pub async fn has_any_permission(login_id: impl LoginId, permissions: &[&str]) -> bool {
let login_type = Self::resolve_login_type();
Self::has_any_permission_with_type(&login_type, login_id, permissions).await
}
pub async fn has_permissions_or(login_id: impl LoginId, permissions: &[&str]) -> bool {
Self::has_any_permission(login_id, permissions).await
}
pub async fn check_permission_with_type(
login_type: &str,
login_id: impl LoginId,
permission: &str,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.authz_service()
.check_permission(login_type, &login_id.to_login_id(), permission)
.await
}
pub async fn check_permission(login_id: impl LoginId, permission: &str) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::check_permission_with_type(&login_type, login_id, permission).await
}
pub async fn check_all_permissions(
login_id: impl LoginId,
permissions: &[&str],
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.authz_service()
.check_all_permissions(&login_type, &login_id.to_login_id(), permissions)
.await
}
pub async fn check_any_permission(
login_id: impl LoginId,
permissions: &[&str],
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.authz_service()
.check_any_permission(&login_type, &login_id.to_login_id(), permissions)
.await
}
}
impl StpUtil {
pub async fn set_roles_with_type(
login_type: &str,
login_id: impl LoginId,
roles: Vec<String>,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.set_roles_with_type(login_type, &login_id.to_login_id(), roles)
.await
}
pub async fn set_roles(login_id: impl LoginId, roles: Vec<String>) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::set_roles_with_type(&login_type, login_id, roles).await
}
pub async fn add_role_with_type(
login_type: &str,
login_id: impl LoginId,
role: impl Into<String>,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.add_role_with_type(login_type, &login_id.to_login_id(), role.into())
.await
}
pub async fn add_role(login_id: impl LoginId, role: impl Into<String>) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::add_role_with_type(&login_type, login_id, role).await
}
pub async fn remove_role_with_type(
login_type: &str,
login_id: impl LoginId,
role: &str,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.remove_role_with_type(login_type, &login_id.to_login_id(), role)
.await
}
pub async fn remove_role(login_id: impl LoginId, role: &str) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::remove_role_with_type(&login_type, login_id, role).await
}
pub async fn clear_roles_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.clear_roles_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn clear_roles(login_id: impl LoginId) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::clear_roles_with_type(&login_type, login_id).await
}
pub async fn try_get_roles_with_type(
login_type: &str,
login_id: impl LoginId,
) -> SaTokenResult<Vec<String>> {
Self::try_get_manager()?
.get_roles_with_type(login_type, &login_id.to_login_id())
.await
}
pub async fn try_get_roles(login_id: impl LoginId) -> SaTokenResult<Vec<String>> {
let login_type = Self::resolve_login_type();
Self::try_get_roles_with_type(&login_type, login_id).await
}
pub async fn get_roles(login_id: impl LoginId) -> Vec<String> {
let login_id = login_id.to_login_id();
match Self::try_get_roles(&login_id).await {
Ok(list) => list,
Err(e) => {
tracing::warn!(
login_id = %login_id,
error = %e,
"failed to load roles, treating as empty"
);
Vec::new()
}
}
}
pub async fn has_role_with_type(login_type: &str, login_id: impl LoginId, role: &str) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_role(login_type, &login_id.to_login_id(), role)
.await
.unwrap_or(false)
}
pub async fn has_role(login_id: impl LoginId, role: &str) -> bool {
let login_type = Self::resolve_login_type();
Self::has_role_with_type(&login_type, login_id, role).await
}
pub async fn has_all_roles_with_type(
login_type: &str,
login_id: impl LoginId,
roles: &[&str],
) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_all_roles(login_type, &login_id.to_login_id(), roles)
.await
.unwrap_or(false)
}
pub async fn has_all_roles(login_id: impl LoginId, roles: &[&str]) -> bool {
let login_type = Self::resolve_login_type();
Self::has_all_roles_with_type(&login_type, login_id, roles).await
}
pub async fn has_roles_and(login_id: impl LoginId, roles: &[&str]) -> bool {
Self::has_all_roles(login_id, roles).await
}
pub async fn has_any_role_with_type(
login_type: &str,
login_id: impl LoginId,
roles: &[&str],
) -> bool {
let Ok(manager) = Self::try_get_manager() else {
return false;
};
manager
.authz_service()
.has_any_role(login_type, &login_id.to_login_id(), roles)
.await
.unwrap_or(false)
}
pub async fn has_any_role(login_id: impl LoginId, roles: &[&str]) -> bool {
let login_type = Self::resolve_login_type();
Self::has_any_role_with_type(&login_type, login_id, roles).await
}
pub async fn has_roles_or(login_id: impl LoginId, roles: &[&str]) -> bool {
Self::has_any_role(login_id, roles).await
}
pub async fn check_role_with_type(
login_type: &str,
login_id: impl LoginId,
role: &str,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.authz_service()
.check_role(login_type, &login_id.to_login_id(), role)
.await
}
pub async fn check_role(login_id: impl LoginId, role: &str) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::check_role_with_type(&login_type, login_id, role).await
}
pub async fn check_all_roles(login_id: impl LoginId, roles: &[&str]) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.authz_service()
.check_all_roles(&login_type, &login_id.to_login_id(), roles)
.await
}
pub async fn check_any_role(login_id: impl LoginId, roles: &[&str]) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.authz_service()
.check_any_role(&login_type, &login_id.to_login_id(), roles)
.await
}
}
impl StpUtil {
pub async fn disable(login_id: impl LoginId, time: i64) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.disable_with_type(login_type.as_ref(), &login_id.to_login_id(), time)
.await
}
pub async fn disable_with_type(
login_type: &str,
login_id: impl LoginId,
time: i64,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.disable_with_type(login_type, &login_id.to_login_id(), time)
.await
}
pub async fn disable_level(
login_id: impl LoginId,
service: &str,
level: i32,
time: i64,
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.disable_level_with_type(
login_type.as_ref(),
&login_id.to_login_id(),
service,
level,
time,
)
.await
}
pub async fn check_disable(login_id: impl LoginId) -> SaTokenResult<()> {
Self::check_disable_level(
login_id,
crate::disable::DEFAULT_DISABLE_SERVICE,
crate::disable::MIN_DISABLE_LEVEL,
)
.await
}
pub async fn check_disable_service(login_id: impl LoginId, service: &str) -> SaTokenResult<()> {
Self::check_disable_level(login_id, service, crate::disable::MIN_DISABLE_LEVEL).await
}
pub async fn check_disable_services(
login_id: impl LoginId,
services: &[&str],
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.check_disable_services_with_type(
login_type.as_ref(),
&login_id.to_login_id(),
services,
crate::disable::MIN_DISABLE_LEVEL,
)
.await
}
pub async fn check_disable_level(
login_id: impl LoginId,
service: &str,
level: i32,
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.check_disable_level_with_type(
login_type.as_ref(),
&login_id.to_login_id(),
service,
level,
)
.await
}
pub async fn get_disable_level(login_id: impl LoginId, service: &str) -> SaTokenResult<i32> {
let login_type = Self::resolve_login_type();
Self::get_disable_level_with_type(login_type.as_ref(), login_id, service).await
}
pub async fn get_disable_level_with_type(
login_type: &str,
login_id: impl LoginId,
service: &str,
) -> SaTokenResult<i32> {
Self::try_get_manager()?
.get_disable_level_with_type(login_type, &login_id.to_login_id(), service)
.await
}
pub async fn untie_disable(login_id: impl LoginId, service: &str) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.untie_disable_with_type(login_type.as_ref(), &login_id.to_login_id(), service)
.await
}
}
impl StpUtil {
pub async fn open_safe(service: &str, safe_time: i64) -> SaTokenResult<()> {
let token = Self::get_token_value()?;
Self::try_get_manager()?
.open_safe(&token, service, safe_time)
.await
}
pub async fn is_safe(service: &str) -> SaTokenResult<bool> {
let token = Self::get_token_value()?;
Self::try_get_manager()?.is_safe(&token, service).await
}
pub async fn check_safe(service: &str) -> SaTokenResult<()> {
Self::check_login_current()?;
let token = Self::get_token_value()?;
Self::try_get_manager()?.check_safe(&token, service).await
}
pub async fn close_safe(service: &str) -> SaTokenResult<()> {
let token = Self::get_token_value()?;
Self::try_get_manager()?.close_safe(&token, service).await
}
}
impl StpUtil {
pub fn switch_to(login_id: impl LoginId) {
let target = login_id.to_login_id();
SaTokenContext::with_current_mut(|inner| {
inner.switch_login_id = Some(target);
});
}
pub fn end_switch() {
SaTokenContext::with_current_mut(|inner| {
inner.switch_login_id = None;
});
}
pub fn is_switch() -> bool {
SaTokenContext::get_current()
.and_then(|c| c.switch_login_id())
.is_some()
}
pub fn get_switch_login_id() -> Option<String> {
SaTokenContext::get_current().and_then(|c| c.switch_login_id())
}
}
impl StpUtil {
pub async fn kick_out_batch<T: LoginId>(
login_ids: &[T],
) -> SaTokenResult<Vec<Result<(), SaTokenError>>> {
let manager = Self::try_get_manager()?;
let login_type = Self::resolve_login_type();
let mut results = Vec::new();
for login_id in login_ids {
results.push(
manager
.kick_out(login_type.as_ref(), &login_id.to_login_id())
.await,
);
}
Ok(results)
}
pub async fn get_token_timeout(token: &TokenValue) -> SaTokenResult<Option<i64>> {
let manager = Self::try_get_manager()?;
let token_info = manager.get_token_info(token).await?;
if let Some(expire_time) = token_info.expire_time {
let now = chrono::Utc::now();
let duration = expire_time.signed_duration_since(now);
Ok(Some(duration.num_seconds()))
} else {
Ok(None) }
}
pub async fn renew_timeout(token: &TokenValue, timeout_seconds: i64) -> SaTokenResult<()> {
Self::try_get_manager()?
.renew_timeout(token, timeout_seconds)
.await
}
pub async fn set_extra_data(
token: &TokenValue,
extra_data: serde_json::Value,
) -> SaTokenResult<()> {
Self::try_get_manager()?
.update_extra_data(token, extra_data)
.await
}
pub async fn get_extra_data(token: &TokenValue) -> SaTokenResult<Option<serde_json::Value>> {
let manager = Self::try_get_manager()?;
let token_info = manager.get_token_info(token).await?;
Ok(token_info.extra_data)
}
pub async fn get_terminal_list(
login_id: &str,
device_type: Option<&str>,
) -> SaTokenResult<Vec<crate::session::SaTerminalInfo>> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.get_terminal_list(login_type.as_ref(), login_id, device_type)
.await
}
pub async fn get_token_value_list_by_login_id(
login_id: &str,
device_type: Option<&str>,
) -> SaTokenResult<Vec<String>> {
let login_type = Self::resolve_login_type();
Self::try_get_manager()?
.get_token_value_list_by_login_id(login_type.as_ref(), login_id, device_type)
.await
}
pub async fn get_terminal_info_by_token(
token: &TokenValue,
) -> SaTokenResult<Option<crate::session::SaTerminalInfo>> {
Self::try_get_manager()?
.get_terminal_info_by_token(token)
.await
}
pub async fn check_current_terminal(expected: &str) -> SaTokenResult<()> {
Self::check_login_current_async().await?;
let token = Self::get_token_value()?;
let term = Self::get_terminal_info_by_token(&token).await?;
let actual = term.map(|t| t.device_type).unwrap_or_default();
if actual != expected {
return Err(SaTokenError::TerminalDenied {
expected: expected.to_string(),
actual,
});
}
Ok(())
}
pub fn stp_logic(login_type: &str) -> SaTokenResult<crate::stp_logic::SaLogic> {
Ok(crate::stp_logic::SaLogic::new(
login_type,
Self::try_get_manager()?.as_ref().clone(),
))
}
#[deprecated(note = "SaLogic is a cloneable facade; use SaLogic::new / StpUtil::stp_logic")]
pub fn put_stp_logic(_logic: crate::stp_logic::SaLogic) {}
#[deprecated(note = "SaLogic is a cloneable facade; nothing to remove")]
pub fn remove_stp_logic(_login_type: &str) {}
pub async fn get_token_session(token: &TokenValue) -> SaTokenResult<SaSession> {
Self::try_get_manager()?.get_token_session(token).await
}
pub async fn get_token_session_current() -> SaTokenResult<SaSession> {
let token = Self::get_token_value()?;
Self::get_token_session(&token).await
}
pub async fn save_token_session(token: &TokenValue, session: &SaSession) -> SaTokenResult<()> {
Self::try_get_manager()?
.save_token_session(token, session)
.await
}
pub async fn delete_token_session(token: &TokenValue) -> SaTokenResult<()> {
Self::try_get_manager()?.delete_token_session(token).await
}
pub async fn kick_out_by_token(token: &TokenValue) -> SaTokenResult<()> {
Self::try_get_manager()?.kick_out_by_token(token).await
}
pub async fn with_grant_scope<F, T>(future: F) -> T
where
F: Future<Output = T>,
{
crate::context::GrantScope::run(crate::context::GrantScope::new(), future).await
}
pub async fn check_permission_or_role(
login_id: impl LoginId,
permissions: &[&str],
roles: &[&str],
) -> SaTokenResult<()> {
let login_type = Self::resolve_login_type();
let login_id = login_id.to_login_id();
let authz = Self::try_get_manager()?.authz_service();
if !permissions.is_empty()
&& authz
.has_any_permission(&login_type, &login_id, permissions)
.await?
{
return Ok(());
}
if !roles.is_empty() && authz.has_any_role(&login_type, &login_id, roles).await? {
return Ok(());
}
Err(SaTokenError::PermissionDeniedDetail(format!(
"none of permissions [{}] or roles [{}] matched",
permissions.join(", "),
roles.join(", ")
)))
}
pub fn builder(login_id: impl LoginId) -> TokenBuilder {
TokenBuilder::new(login_id.to_login_id())
}
pub fn request_sign() -> SaTokenResult<crate::sign::RequestSign> {
let manager = Self::try_get_manager()?;
let secret = manager
.config
.sign_secret_key
.clone()
.filter(|s| !s.is_empty())
.ok_or_else(|| SaTokenError::ConfigError("sign_secret_key is not configured".into()))?;
Ok(
crate::sign::RequestSign::new(secret, manager.config.sign_window_secs)
.with_dao(manager.dao().clone()),
)
}
pub async fn sign_params(
params: std::collections::BTreeMap<String, String>,
) -> SaTokenResult<std::collections::BTreeMap<String, String>> {
Self::request_sign()?.create_signed(params)
}
pub async fn check_sign(
params: &std::collections::BTreeMap<String, String>,
) -> SaTokenResult<()> {
let sign = params
.get("sign")
.cloned()
.ok_or(SaTokenError::SignInvalid)?;
Self::request_sign()?.verify_params(params, &sign).await
}
pub async fn get_same_token() -> SaTokenResult<String> {
crate::same_token::get_token().await
}
pub async fn refresh_same_token() -> SaTokenResult<String> {
crate::same_token::refresh_token().await
}
pub async fn check_same_token(token: &str) -> SaTokenResult<()> {
crate::same_token::check_token(token).await
}
pub async fn create_temp_token(
value: impl Into<String>,
timeout_secs: i64,
) -> SaTokenResult<String> {
crate::temp_token::create_default(value, timeout_secs).await
}
pub async fn parse_temp_token(
token: &str,
) -> SaTokenResult<crate::temp_token::TempTokenRecord> {
crate::temp_token::parse_default(token).await
}
pub async fn delete_temp_token(token: &str) -> SaTokenResult<()> {
crate::temp_token::delete_default(token).await
}
}
pub struct TokenBuilder {
login_id: String,
extra_data: Option<serde_json::Value>,
device: Option<String>,
login_type: Option<String>,
nonce: Option<String>,
expire_time: Option<chrono::DateTime<chrono::Utc>>,
}
impl std::fmt::Debug for TokenBuilder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("TokenBuilder { .. }")
}
}
impl TokenBuilder {
pub fn new(login_id: String) -> Self {
Self {
login_id,
extra_data: None,
device: None,
login_type: None,
nonce: None,
expire_time: None,
}
}
pub fn extra_data(mut self, data: serde_json::Value) -> Self {
self.extra_data = Some(data);
self
}
pub fn device(mut self, device: impl Into<String>) -> Self {
self.device = Some(device.into());
self
}
pub fn login_type(mut self, login_type: impl Into<String>) -> Self {
self.login_type = Some(login_type.into());
self
}
pub fn nonce(mut self, nonce: impl Into<String>) -> Self {
self.nonce = Some(nonce.into());
self
}
pub fn expire_at(mut self, expire_time: chrono::DateTime<chrono::Utc>) -> Self {
self.expire_time = Some(expire_time);
self
}
pub fn expire_at_unix(mut self, unix_seconds: i64) -> Self {
self.expire_time = chrono::DateTime::from_timestamp(unix_seconds, 0);
self
}
#[deprecated(note = "use expire_at()")]
pub fn expire_time(self, expire_time: chrono::DateTime<chrono::Utc>) -> Self {
self.expire_at(expire_time)
}
pub async fn login<T: LoginId>(self, login_id: Option<T>) -> SaTokenResult<TokenValue> {
let manager = StpUtil::try_get_manager()?;
let final_login_id = match login_id {
Some(id) => id.to_login_id(),
None => self.login_id,
};
manager
.login_with_options(
final_login_id,
self.login_type,
self.device,
self.extra_data,
self.nonce,
self.expire_time,
)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_format_validation() {
assert!(StpUtil::is_valid_token_format("1234567890abcdef"));
assert!(!StpUtil::is_valid_token_format(""));
assert!(!StpUtil::is_valid_token_format("short"));
}
#[test]
fn test_create_token() {
let token = StpUtil::create_token("test-token-123");
assert_eq!(token.as_str(), "test-token-123");
}
}