1use crate::{Authenticator, AuthError, AuthProvider, AuthResult, UserProfile, generate_offline_uuid};
7
8#[cfg(feature = "events")]
9use lighty_event::{EventBus, Event, AuthEvent};
10
11pub struct OfflineAuth {
13 username: String,
14}
15
16impl OfflineAuth {
17 pub fn new(username: impl Into<String>) -> Self {
19 Self {
20 username: username.into(),
21 }
22 }
23
24 pub fn username(&self) -> &str {
26 &self.username
27 }
28}
29
30impl Authenticator for OfflineAuth {
31 async fn authenticate(
32 &mut self,
33 #[cfg(feature = "events")] event_bus: Option<&EventBus>,
34 ) -> AuthResult<UserProfile> {
35 #[cfg(feature = "events")]
36 if let Some(bus) = event_bus {
37 bus.emit(Event::Auth(AuthEvent::AuthenticationStarted {
38 provider: "Offline".to_string(),
39 }));
40 }
41
42 if self.username.is_empty() {
43 #[cfg(feature = "events")]
44 if let Some(bus) = event_bus {
45 bus.emit(Event::Auth(AuthEvent::AuthenticationFailed {
46 provider: "Offline".to_string(),
47 error: "Username cannot be empty".to_string(),
48 }));
49 }
50 return Err(AuthError::InvalidCredentials);
51 }
52
53 if self.username.len() < 3 || self.username.len() > 16 {
54 let error_msg = "Username must be between 3 and 16 characters".to_string();
55 #[cfg(feature = "events")]
56 if let Some(bus) = event_bus {
57 bus.emit(Event::Auth(AuthEvent::AuthenticationFailed {
58 provider: "Offline".to_string(),
59 error: error_msg.clone(),
60 }));
61 }
62 return Err(AuthError::Custom(error_msg));
63 }
64
65 if !self.username.chars().all(|c| c.is_alphanumeric() || c == '_') {
66 let error_msg = "Username can only contain letters, numbers, and underscores".to_string();
67 #[cfg(feature = "events")]
68 if let Some(bus) = event_bus {
69 bus.emit(Event::Auth(AuthEvent::AuthenticationFailed {
70 provider: "Offline".to_string(),
71 error: error_msg.clone(),
72 }));
73 }
74 return Err(AuthError::Custom(error_msg));
75 }
76
77 let uuid = generate_offline_uuid(&self.username);
78
79 #[cfg(feature = "events")]
80 if let Some(bus) = event_bus {
81 bus.emit(Event::Auth(AuthEvent::AuthenticationSuccess {
82 provider: "Offline".to_string(),
83 username: self.username.clone(),
84 uuid: uuid.clone(),
85 }));
86 }
87
88 Ok(UserProfile {
89 id: None,
90 username: self.username.clone(),
91 uuid,
92 access_token: None,
93 #[cfg(feature = "keyring")]
94 token_handle: None,
95 xuid: None,
96 email: None,
97 email_verified: false,
98 money: None,
99 role: None,
100 banned: false,
101 provider: AuthProvider::Offline,
102 })
103 }
104}