use std::fmt;
use std::future::Future;
use secrecy::SecretString;
use serde::{Deserialize, Serialize};
use crate::AuthError;
#[cfg(feature = "keyring")]
use crate::keyring::TokenHandle;
#[cfg(feature = "events")]
use lighty_event::EventBus;
pub type AuthResult<T> = Result<T, AuthError>;
#[derive(Clone)]
pub struct UserProfile {
pub id: Option<u64>,
pub username: String,
pub uuid: String,
pub access_token: Option<SecretString>,
#[cfg(feature = "keyring")]
pub token_handle: Option<TokenHandle>,
pub xuid: Option<String>,
pub email: Option<String>,
pub email_verified: bool,
pub money: Option<f64>,
pub role: Option<UserRole>,
pub banned: bool,
pub provider: AuthProvider,
}
impl UserProfile {
pub fn offline(username: impl Into<String>, uuid: impl Into<String>) -> Self {
Self {
id: None,
username: username.into(),
uuid: uuid.into(),
access_token: None,
#[cfg(feature = "keyring")]
token_handle: None,
xuid: None,
email: None,
email_verified: false,
money: None,
role: None,
banned: false,
provider: AuthProvider::Offline,
}
}
}
impl fmt::Debug for UserProfile {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UserProfile")
.field("id", &self.id)
.field("username", &self.username)
.field("uuid", &self.uuid)
.field("access_token", &self.access_token.as_ref().map(|_| "[REDACTED]"))
.field("xuid", &self.xuid)
.field("email", &self.email)
.field("email_verified", &self.email_verified)
.field("money", &self.money)
.field("role", &self.role)
.field("banned", &self.banned)
.field("provider", &self.provider)
.finish()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserRole {
pub name: String,
pub color: Option<String>,
}
#[derive(Debug, Clone)]
pub enum AuthProvider {
Offline,
Azuriom {
base_url: String,
},
Microsoft {
client_id: String,
refresh_token: Option<SecretString>,
},
Custom {
base_url: String,
},
}
pub(crate) struct TokenRouting {
pub access_token: Option<SecretString>,
#[cfg(feature = "keyring")]
pub token_handle: Option<TokenHandle>,
}
pub(crate) fn route_token(
token: String,
_keyring_service: Option<&str>,
_keyring_key: &str,
) -> Result<TokenRouting, AuthError> {
let secret = SecretString::from(token);
#[cfg(feature = "keyring")]
if let Some(service) = _keyring_service {
let handle = TokenHandle::new(service, _keyring_key);
handle.store(&secret)?;
return Ok(TokenRouting {
access_token: None,
token_handle: Some(handle),
});
}
Ok(TokenRouting {
access_token: Some(secret),
#[cfg(feature = "keyring")]
token_handle: None,
})
}
impl PartialEq for AuthProvider {
fn eq(&self, other: &Self) -> bool {
use secrecy::ExposeSecret;
match (self, other) {
(Self::Offline, Self::Offline) => true,
(Self::Azuriom { base_url: a }, Self::Azuriom { base_url: b }) => a == b,
(Self::Custom { base_url: a }, Self::Custom { base_url: b }) => a == b,
(
Self::Microsoft { client_id: ca, refresh_token: ta },
Self::Microsoft { client_id: cb, refresh_token: tb },
) => {
ca == cb
&& ta.as_ref().map(|s| s.expose_secret().to_string())
== tb.as_ref().map(|s| s.expose_secret().to_string())
}
_ => false,
}
}
}
impl Eq for AuthProvider {}
pub trait Authenticator {
fn authenticate(
&mut self,
#[cfg(feature = "events")] event_bus: Option<&EventBus>,
) -> impl Future<Output = AuthResult<UserProfile>> + Send;
fn verify(&self, token: &str) -> impl Future<Output = AuthResult<UserProfile>> + Send {
async move {
let _ = token;
Err(AuthError::Custom("Verification not supported for this provider".into()))
}
}
fn logout(&self, token: &str) -> impl Future<Output = AuthResult<()>> + Send {
async move {
let _ = token;
Ok(())
}
}
}
pub fn generate_offline_uuid(username: &str) -> String {
const NAMESPACE: &[u8] = b"OfflinePlayer:";
let mut data = Vec::with_capacity(NAMESPACE.len() + username.len());
data.extend_from_slice(NAMESPACE);
data.extend_from_slice(username.as_bytes());
let hash = lighty_core::calculate_sha1_bytes_raw(&data);
format!(
"{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-5{:01x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
hash[0], hash[1], hash[2], hash[3],
hash[4], hash[5],
hash[6] & 0x0f, hash[7],
(hash[8] & 0x3f) | 0x80, hash[9],
hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]
)
}