lighty_auth/auth.rs
1use std::future::Future;
2use serde::{Deserialize, Serialize};
3use crate::AuthError;
4
5#[cfg(feature = "events")]
6use lighty_event::EventBus;
7
8pub type AuthResult<T> = Result<T, AuthError>;
9
10/// User profile returned after successful authentication
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct UserProfile {
13 /// User ID (optional for offline mode)
14 pub id: Option<u64>,
15
16 /// Username
17 pub username: String,
18
19 /// Minecraft UUID (with dashes)
20 pub uuid: String,
21
22 /// Access token for session validation
23 pub access_token: Option<String>,
24
25 /// User email (optional)
26 pub email: Option<String>,
27
28 /// Email verification status
29 pub email_verified: bool,
30
31 /// User money/credits (for custom launchers)
32 pub money: Option<f64>,
33
34 /// User role/rank
35 pub role: Option<UserRole>,
36
37 /// Whether the account is banned
38 pub banned: bool,
39}
40
41/// User role/rank information
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct UserRole {
44 /// Role name
45 pub name: String,
46
47 /// Role color (hex format: #RRGGBB)
48 pub color: Option<String>,
49}
50
51/// Authentication provider type
52#[derive(Debug, Clone, PartialEq, Eq)]
53pub enum AuthProvider {
54 /// Offline mode - no authentication
55 Offline,
56
57 /// Azuriom CMS authentication
58 Azuriom {
59 /// Base URL of the Azuriom instance (e.g., "https://example.com")
60 base_url: String,
61 },
62
63 /// Microsoft/Xbox Live authentication
64 Microsoft {
65 /// OAuth client ID
66 client_id: String,
67 },
68
69 /// Custom authentication endpoint
70 Custom {
71 /// Base URL of the custom auth API
72 base_url: String,
73 },
74}
75
76/// Core authentication trait
77///
78/// All authentication providers must implement this trait
79pub trait Authenticator {
80 /// Authenticate a user and return their profile
81 ///
82 /// # Arguments
83 /// - `event_bus`: Optional event bus for emitting auth events
84 ///
85 /// # Returns
86 /// - `Ok(UserProfile)` on success
87 /// - `Err(AuthError)` on failure
88 fn authenticate(
89 &mut self,
90 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
91 ) -> impl Future<Output = AuthResult<UserProfile>> + Send;
92
93 /// Verify if a token is still valid
94 ///
95 /// # Arguments
96 /// - `token`: The access token to verify
97 ///
98 /// # Returns
99 /// - `Ok(UserProfile)` if token is valid
100 /// - `Err(AuthError)` if token is invalid or expired
101 fn verify(&self, token: &str) -> impl Future<Output = AuthResult<UserProfile>> + Send {
102 async move {
103 let _ = token;
104 Err(AuthError::Custom("Verification not supported for this provider".into()))
105 }
106 }
107
108 /// Logout and invalidate the token
109 ///
110 /// # Arguments
111 /// - `token`: The access token to invalidate
112 fn logout(&self, token: &str) -> impl Future<Output = AuthResult<()>> + Send {
113 async move {
114 let _ = token;
115 Ok(())
116 }
117 }
118}
119
120/// Helper to generate UUID v5 from username (for offline mode)
121///
122/// Uses SHA1 hashing to generate a deterministic UUID from the username.
123/// This ensures the same username always produces the same UUID.
124///
125/// # Arguments
126/// - `username`: The username to generate a UUID for
127///
128/// # Returns
129/// A UUID v5 string in the format: xxxxxxxx-xxxx-5xxx-yxxx-xxxxxxxxxxxx
130pub fn generate_offline_uuid(username: &str) -> String {
131 // Namespace for offline UUIDs (OfflinePlayer)
132 const NAMESPACE: &[u8] = b"OfflinePlayer:";
133
134 // Concatenate namespace and username
135 let mut data = Vec::with_capacity(NAMESPACE.len() + username.len());
136 data.extend_from_slice(NAMESPACE);
137 data.extend_from_slice(username.as_bytes());
138
139 // Calculate SHA1 hash using lighty-core
140 let hash = lighty_core::calculate_sha1_bytes_raw(&data);
141
142 // Format as UUID v5 (SHA1-based)
143 // Version bits: 0101 (5) in the 13th position
144 // Variant bits: 10xx in the 17th position (RFC 4122)
145 format!(
146 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-5{:01x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
147 hash[0], hash[1], hash[2], hash[3],
148 hash[4], hash[5],
149 hash[6] & 0x0f, hash[7],
150 (hash[8] & 0x3f) | 0x80, hash[9],
151 hash[10], hash[11], hash[12], hash[13], hash[14], hash[15]
152 )
153}