veloren_server/
login_provider.rs

1use crate::{
2    Client,
3    settings::{AdminRecord, Ban, Banlist, WhitelistRecord, banlist::NormalizedIpAddr},
4};
5use authc::{AuthClient, AuthClientError, AuthToken, Uuid};
6use chrono::Utc;
7use common::comp::AdminRole;
8use common_net::msg::RegisterError;
9use hashbrown::HashMap;
10use specs::Component;
11use std::{str::FromStr, sync::Arc};
12use tokio::{runtime::Runtime, sync::oneshot};
13use tracing::{error, info};
14
15/// Determines whether a user is banned, given a ban record connected to a user,
16/// the `AdminRecord` of that user (if it exists), and the current time.
17pub fn ban_applies(
18    ban: &Ban,
19    admin: Option<&AdminRecord>,
20    now: chrono::DateTime<chrono::Utc>,
21) -> bool {
22    // Make sure the ban is active, and that we can't override it.
23    //
24    // If we are an admin and our role is at least as high as the role of the
25    // person who banned us, we can override the ban; we negate this to find
26    // people who cannot override it.
27    let exceeds_ban_role = |admin: &AdminRecord| {
28        AdminRole::from(admin.role) >= AdminRole::from(ban.performed_by_role())
29    };
30    !ban.is_expired(now) && !admin.is_some_and(exceeds_ban_role)
31}
32
33fn derive_uuid(username: &str) -> Uuid {
34    let mut state = 144066263297769815596495629667062367629;
35
36    for byte in username.as_bytes() {
37        state ^= *byte as u128;
38        state = state.wrapping_mul(309485009821345068724781371);
39    }
40
41    Uuid::from_u128(state)
42}
43
44/// derive Uuid for "singleplayer" is a pub fn
45pub fn derive_singleplayer_uuid() -> Uuid { derive_uuid("singleplayer") }
46
47pub struct PendingLogin {
48    pending_r: oneshot::Receiver<Result<(String, Uuid), RegisterError>>,
49}
50
51impl PendingLogin {
52    pub(crate) fn new_success(username: String, uuid: Uuid) -> Self {
53        let (pending_s, pending_r) = oneshot::channel();
54        let _ = pending_s.send(Ok((username, uuid)));
55
56        Self { pending_r }
57    }
58}
59
60impl Component for PendingLogin {
61    type Storage = specs::DenseVecStorage<Self>;
62}
63
64pub struct LoginProvider {
65    runtime: Arc<Runtime>,
66    auth_server: Option<Arc<AuthClient>>,
67}
68
69impl LoginProvider {
70    pub fn new(auth_addr: Option<String>, runtime: Arc<Runtime>) -> Self {
71        tracing::trace!(?auth_addr, "Starting LoginProvider");
72
73        let auth_server = auth_addr.map(|addr| {
74            let (scheme, authority) = addr.split_once("://").expect("invalid auth url");
75
76            let scheme = scheme
77                .parse::<authc::Scheme>()
78                .expect("invalid auth url scheme");
79            let authority = authority
80                .parse::<authc::Authority>()
81                .expect("invalid auth url authority");
82
83            Arc::new(AuthClient::new(scheme, authority).expect("insecure auth scheme"))
84        });
85
86        Self {
87            runtime,
88            auth_server,
89        }
90    }
91
92    pub fn verify(&self, username_or_token: &str) -> PendingLogin {
93        let (pending_s, pending_r) = oneshot::channel();
94
95        match &self.auth_server {
96            // Token from auth server expected
97            Some(srv) => {
98                let srv = Arc::clone(srv);
99                let username_or_token = username_or_token.to_string();
100                self.runtime.spawn(async move {
101                    let _ = pending_s.send(Self::query(srv, &username_or_token).await);
102                });
103            },
104            // Username is expected
105            None => {
106                let username = username_or_token;
107                let uuid = derive_uuid(username);
108                let _ = pending_s.send(Ok((username.to_string(), uuid)));
109            },
110        }
111
112        PendingLogin { pending_r }
113    }
114
115    pub(crate) fn login<R>(
116        pending: &mut PendingLogin,
117        client: &Client,
118        admins: &HashMap<Uuid, AdminRecord>,
119        whitelist: &HashMap<Uuid, WhitelistRecord>,
120        banlist: &Banlist,
121        player_count_exceeded: impl FnOnce(String, Uuid) -> (bool, R),
122        make_ip_ban_upgrade: impl FnOnce(NormalizedIpAddr, Uuid, String),
123    ) -> Option<Result<R, RegisterError>> {
124        match pending.pending_r.try_recv() {
125            Ok(Err(e)) => Some(Err(e)),
126            Ok(Ok((username, uuid))) => {
127                let now = Utc::now();
128                // We ignore mpsc connections since those aren't to an external
129                // process.
130                let ip = client
131                    .connected_from_addr()
132                    .socket_addr()
133                    .map(|s| s.ip())
134                    .map(NormalizedIpAddr::from);
135                // Hardcoded admins can always log in.
136                let admin = admins.get(&uuid);
137                if let Some(ban) = banlist
138                    .uuid_bans()
139                    .get(&uuid)
140                    .and_then(|ban_entry| ban_entry.current.action.ban())
141                    .into_iter()
142                    .chain(ip.and_then(|ip| {
143                        banlist
144                            .ip_bans()
145                            .get(&ip)
146                            .and_then(|ban_entry| ban_entry.current.action.ban())
147                    }))
148                    .find(|ban| ban_applies(ban, admin, now))
149                {
150                    if let Some(ip) = ip
151                        && ban.upgrade_to_ip
152                    {
153                        make_ip_ban_upgrade(ip, uuid, username.clone());
154                    }
155
156                    // Get ban info and send a copy of it
157                    return Some(Err(RegisterError::Banned(ban.info())));
158                }
159
160                // non-admins can only join if the whitelist is empty (everyone can join)
161                // or their name is in the whitelist.
162                if admin.is_none() && !whitelist.is_empty() && !whitelist.contains_key(&uuid) {
163                    return Some(Err(RegisterError::NotOnWhitelist));
164                }
165
166                // non-admins can only join if the player count has not been exceeded.
167                let (player_count_exceeded, res) = player_count_exceeded(username, uuid);
168                if admin.is_none() && player_count_exceeded {
169                    return Some(Err(RegisterError::TooManyPlayers));
170                }
171
172                Some(Ok(res))
173            },
174            Err(oneshot::error::TryRecvError::Closed) => {
175                error!("channel got closed to early, this shouldn't happen");
176                Some(Err(RegisterError::AuthError(
177                    "Internal Error verifying".to_string(),
178                )))
179            },
180            Err(oneshot::error::TryRecvError::Empty) => None,
181        }
182    }
183
184    async fn query(
185        srv: Arc<AuthClient>,
186        username_or_token: &str,
187    ) -> Result<(String, Uuid), RegisterError> {
188        info!(?username_or_token, "Validating token");
189        // Parse token
190        let token = AuthToken::from_str(username_or_token)
191            .map_err(|e| RegisterError::AuthError(e.to_string()))?;
192        // Validate token
193        match async {
194            let uuid = srv.validate(token).await?;
195            let username = srv.uuid_to_username(uuid).await?;
196            let r: Result<_, AuthClientError> = Ok((username, uuid));
197            r
198        }
199        .await
200        {
201            Err(e) => Err(RegisterError::AuthError(e.to_string())),
202            Ok((username, uuid)) => Ok((username, uuid)),
203        }
204    }
205
206    pub fn username_to_uuid(&self, username: &str) -> Result<Uuid, AuthClientError> {
207        match &self.auth_server {
208            Some(srv) => {
209                //TODO: optimize
210                self.runtime.block_on(srv.username_to_uuid(&username))
211            },
212            None => Ok(derive_uuid(username)),
213        }
214    }
215
216    pub fn uuid_to_username(
217        &self,
218        uuid: Uuid,
219        fallback_alias: &str,
220    ) -> Result<String, AuthClientError> {
221        match &self.auth_server {
222            Some(srv) => {
223                //TODO: optimize
224                self.runtime.block_on(srv.uuid_to_username(uuid))
225            },
226            None => Ok(fallback_alias.into()),
227        }
228    }
229}