This commit is contained in:
parent
d61f8c931f
commit
33e11088b2
@ -11,6 +11,19 @@ pub struct AuthenticationConfig {
|
|||||||
pub enabled: bool,
|
pub enabled: bool,
|
||||||
/// Optional custom authentication URL.
|
/// Optional custom authentication URL.
|
||||||
pub url: Option<String>,
|
pub url: Option<String>,
|
||||||
|
/// Fallback authentication server URLs to use if the primary/official server is down.
|
||||||
|
#[serde(alias = "fallback_urls")]
|
||||||
|
pub fallbacks: Vec<String>,
|
||||||
|
/// Optional custom profile lookup by username URL (template parameter `{username}`).
|
||||||
|
pub profile_by_name_url: Option<String>,
|
||||||
|
/// Optional fallback profile lookup by username URLs.
|
||||||
|
#[serde(alias = "profile_by_name_fallback_urls")]
|
||||||
|
pub profile_by_name_fallbacks: Vec<String>,
|
||||||
|
/// Optional custom profile lookup by UUID URL (template parameter `{uuid}`).
|
||||||
|
pub profile_by_uuid_url: Option<String>,
|
||||||
|
/// Optional fallback profile lookup by UUID URLs.
|
||||||
|
#[serde(alias = "profile_by_uuid_fallback_urls")]
|
||||||
|
pub profile_by_uuid_fallbacks: Vec<String>,
|
||||||
/// Connection timeout in milliseconds.
|
/// Connection timeout in milliseconds.
|
||||||
pub connect_timeout: u32,
|
pub connect_timeout: u32,
|
||||||
/// Read timeout in milliseconds.
|
/// Read timeout in milliseconds.
|
||||||
@ -35,6 +48,11 @@ impl Default for AuthenticationConfig {
|
|||||||
player_profile: PlayerProfileConfig::default(),
|
player_profile: PlayerProfileConfig::default(),
|
||||||
textures: TextureConfig::default(),
|
textures: TextureConfig::default(),
|
||||||
url: None,
|
url: None,
|
||||||
|
fallbacks: Vec::new(),
|
||||||
|
profile_by_name_url: None,
|
||||||
|
profile_by_name_fallbacks: Vec::new(),
|
||||||
|
profile_by_uuid_url: None,
|
||||||
|
profile_by_uuid_fallbacks: Vec::new(),
|
||||||
prevent_proxy_connection_auth_url: None,
|
prevent_proxy_connection_auth_url: None,
|
||||||
services_url: None,
|
services_url: None,
|
||||||
connect_timeout: 5000,
|
connect_timeout: 5000,
|
||||||
@ -115,3 +133,75 @@ impl Default for TextureTypes {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::AuthenticationConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_config_fallbacks_toml_deserialization() {
|
||||||
|
let toml_str = r#"
|
||||||
|
enabled = true
|
||||||
|
url = "https://primary.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
fallbacks = [
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}",
|
||||||
|
"https://fallback2.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
let config: AuthenticationConfig =
|
||||||
|
toml::from_str(toml_str).expect("config should deserialize");
|
||||||
|
assert_eq!(
|
||||||
|
config.url.as_deref(),
|
||||||
|
Some("https://primary.auth/hasJoined?username={username}&serverId={server_hash}")
|
||||||
|
);
|
||||||
|
assert_eq!(config.fallbacks.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
config.fallbacks[0],
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_config_fallback_urls_alias_toml_deserialization() {
|
||||||
|
let toml_str = r#"
|
||||||
|
enabled = true
|
||||||
|
fallback_urls = [
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
let config: AuthenticationConfig =
|
||||||
|
toml::from_str(toml_str).expect("config with alias should deserialize");
|
||||||
|
assert_eq!(config.fallbacks.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
config.fallbacks[0],
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_config_profile_urls_toml_deserialization() {
|
||||||
|
let toml_str = r#"
|
||||||
|
enabled = true
|
||||||
|
profile_by_name_url = "https://custom.auth/users/profiles/minecraft/{username}"
|
||||||
|
profile_by_name_fallback_urls = [
|
||||||
|
"https://fallback.auth/users/profiles/minecraft/{username}"
|
||||||
|
]
|
||||||
|
profile_by_uuid_url = "https://custom.auth/session/minecraft/profile/{uuid}?unsigned=false"
|
||||||
|
profile_by_uuid_fallback_urls = [
|
||||||
|
"https://fallback.auth/session/minecraft/profile/{uuid}?unsigned=false"
|
||||||
|
]
|
||||||
|
"#;
|
||||||
|
let config: AuthenticationConfig =
|
||||||
|
toml::from_str(toml_str).expect("profile config should deserialize");
|
||||||
|
assert_eq!(
|
||||||
|
config.profile_by_name_url.as_deref(),
|
||||||
|
Some("https://custom.auth/users/profiles/minecraft/{username}")
|
||||||
|
);
|
||||||
|
assert_eq!(config.profile_by_name_fallbacks.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
config.profile_by_uuid_url.as_deref(),
|
||||||
|
Some("https://custom.auth/session/minecraft/profile/{uuid}?unsigned=false")
|
||||||
|
);
|
||||||
|
assert_eq!(config.profile_by_uuid_fallbacks.len(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -51,18 +51,40 @@ const MOJANG_PREVENT_PROXY_AUTHENTICATION_URL: &str = "https://sessionserver.moj
|
|||||||
const MOJANG_SERVICES_URL: &str = "https://api.minecraftservices.com/";
|
const MOJANG_SERVICES_URL: &str = "https://api.minecraftservices.com/";
|
||||||
const MOJANG_PROFILE_BY_NAME_URL: &str =
|
const MOJANG_PROFILE_BY_NAME_URL: &str =
|
||||||
"https://api.mojang.com/users/profiles/minecraft/{username}";
|
"https://api.mojang.com/users/profiles/minecraft/{username}";
|
||||||
|
const MOJANG_PROFILE_BY_UUID_URL: &str =
|
||||||
|
"https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
|
||||||
|
|
||||||
/// Sends a GET request to Mojang's authentication servers to verify a client's Minecraft account.
|
fn create_agent(auth_config: &AuthenticationConfig) -> ureq::Agent {
|
||||||
|
let config = ureq::Agent::config_builder()
|
||||||
|
.timeout_connect(Some(std::time::Duration::from_millis(
|
||||||
|
auth_config.connect_timeout as u64,
|
||||||
|
)))
|
||||||
|
.timeout_recv_response(Some(std::time::Duration::from_millis(
|
||||||
|
auth_config.read_timeout as u64,
|
||||||
|
)))
|
||||||
|
.build();
|
||||||
|
config.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn format_auth_url(url_template: &str, username: &str, server_hash: &str, ip: &IpAddr) -> String {
|
||||||
|
url_template
|
||||||
|
.replace("{username}", username)
|
||||||
|
.replace("{server_hash}", server_hash)
|
||||||
|
.replace("{ip}", &ip.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sends a GET request to Mojang's (or custom/fallback) authentication servers to verify a client's Minecraft account.
|
||||||
///
|
///
|
||||||
/// **Purpose:**
|
/// **Purpose:**
|
||||||
///
|
///
|
||||||
/// This function is used to ensure that a client connecting to the server has a valid, premium Minecraft account. It's a crucial step in preventing unauthorized access and maintaining server security.
|
/// This function is used to ensure that a client connecting to the server has a valid, premium Minecraft account.
|
||||||
|
/// If the primary authentication server is down or unreachable, it falls back to trying configured fallback servers.
|
||||||
///
|
///
|
||||||
/// **How it Works:**
|
/// **How it Works:**
|
||||||
///
|
///
|
||||||
/// 1. A client with a premium account sends a login request to the Mojang session server.
|
/// 1. A client with a premium account sends a login request to the session server.
|
||||||
/// 2. Mojang's servers verify the client's credentials and add the player to the their Servers
|
/// 2. Session servers verify the client's credentials and add the player to their server session list.
|
||||||
/// 3. Now our server will send a Request to the Session servers and check if the Player has joined the Session Server .
|
/// 3. Pumpkin attempts to authenticate the player against the primary auth server, falling back to secondary auth servers if the primary is down.
|
||||||
///
|
///
|
||||||
/// See <https://pumpkinmc.org/developer/networking/authentication>
|
/// See <https://pumpkinmc.org/developer/networking/authentication>
|
||||||
pub fn authenticate(
|
pub fn authenticate(
|
||||||
@ -71,40 +93,71 @@ pub fn authenticate(
|
|||||||
ip: &IpAddr,
|
ip: &IpAddr,
|
||||||
auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<GameProfile, AuthError> {
|
) -> Result<GameProfile, AuthError> {
|
||||||
let address = if auth_config.prevent_proxy_connections {
|
let primary_url = if auth_config.prevent_proxy_connections {
|
||||||
let auth_url = auth_config
|
auth_config
|
||||||
.prevent_proxy_connection_auth_url
|
.prevent_proxy_connection_auth_url
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or(MOJANG_PREVENT_PROXY_AUTHENTICATION_URL);
|
.unwrap_or(MOJANG_PREVENT_PROXY_AUTHENTICATION_URL)
|
||||||
|
|
||||||
auth_url
|
|
||||||
.replace("{username}", username)
|
|
||||||
.replace("{server_hash}", server_hash)
|
|
||||||
.replace("{ip}", &ip.to_string())
|
|
||||||
} else {
|
} else {
|
||||||
let auth_url = auth_config
|
auth_config
|
||||||
.url
|
.url
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or(MOJANG_AUTHENTICATION_URL);
|
.unwrap_or(MOJANG_AUTHENTICATION_URL)
|
||||||
|
|
||||||
auth_url
|
|
||||||
.replace("{username}", username)
|
|
||||||
.replace("{server_hash}", server_hash)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut response = ureq::get(address)
|
let mut candidate_urls = Vec::with_capacity(1 + auth_config.fallbacks.len());
|
||||||
.call()
|
candidate_urls.push(primary_url);
|
||||||
.map_err(|_| AuthError::FailedResponse)?;
|
for fallback in &auth_config.fallbacks {
|
||||||
match response.status() {
|
candidate_urls.push(fallback.as_str());
|
||||||
StatusCode::OK => {}
|
}
|
||||||
StatusCode::NO_CONTENT => Err(AuthError::UnverifiedUsername)?,
|
|
||||||
other => Err(AuthError::UnknownStatusCode(other))?,
|
let agent = create_agent(auth_config);
|
||||||
|
|
||||||
|
let mut unverified_count = 0;
|
||||||
|
let mut last_unknown_status = None;
|
||||||
|
|
||||||
|
for url_template in candidate_urls {
|
||||||
|
let address = format_auth_url(url_template, username, server_hash, ip);
|
||||||
|
|
||||||
|
let mut response = match agent.get(&address).call() {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Authentication server at '{address}' is down or unreachable: {err}"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if status.is_server_error() {
|
||||||
|
tracing::warn!("Authentication server at '{address}' returned server error: {status}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match status {
|
||||||
|
StatusCode::OK => match response.body_mut().read_json::<GameProfile>() {
|
||||||
|
Ok(profile) => return Ok(profile),
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StatusCode::NO_CONTENT => {
|
||||||
|
unverified_count += 1;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
last_unknown_status = Some(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if unverified_count > 0 {
|
||||||
|
Err(AuthError::UnverifiedUsername)
|
||||||
|
} else if let Some(status) = last_unknown_status {
|
||||||
|
Err(AuthError::UnknownStatusCode(status))
|
||||||
|
} else {
|
||||||
|
Err(AuthError::FailedResponse)
|
||||||
}
|
}
|
||||||
let profile: GameProfile = response
|
|
||||||
.body_mut()
|
|
||||||
.read_json()
|
|
||||||
.map_err(|_| AuthError::FailedParse)?;
|
|
||||||
Ok(profile)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_textures(property: &Property, config: &TextureConfig) -> Result<(), TextureError> {
|
pub fn validate_textures(property: &Property, config: &TextureConfig) -> Result<(), TextureError> {
|
||||||
@ -158,7 +211,9 @@ pub fn fetch_mojang_public_keys(
|
|||||||
|
|
||||||
let url = format!("{services_url}/publickeys");
|
let url = format!("{services_url}/publickeys");
|
||||||
|
|
||||||
let mut response = ureq::get(url)
|
let agent = create_agent(auth_config);
|
||||||
|
let mut response = agent
|
||||||
|
.get(&url)
|
||||||
.call()
|
.call()
|
||||||
.map_err(|_| AuthError::FailedResponse)?;
|
.map_err(|_| AuthError::FailedResponse)?;
|
||||||
|
|
||||||
@ -195,54 +250,141 @@ struct MojangProfileByNameResponse {
|
|||||||
|
|
||||||
pub fn lookup_profile_by_name(
|
pub fn lookup_profile_by_name(
|
||||||
name: &str,
|
name: &str,
|
||||||
_auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<Option<(Uuid, String)>, AuthError> {
|
) -> Result<Option<(Uuid, String)>, AuthError> {
|
||||||
let url = MOJANG_PROFILE_BY_NAME_URL.replace("{username}", name);
|
let primary_url = auth_config
|
||||||
|
.profile_by_name_url
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or(MOJANG_PROFILE_BY_NAME_URL);
|
||||||
|
|
||||||
let mut response = ureq::get(url)
|
let mut candidate_urls = Vec::with_capacity(1 + auth_config.profile_by_name_fallbacks.len());
|
||||||
.call()
|
candidate_urls.push(primary_url);
|
||||||
.map_err(|_| AuthError::FailedResponse)?;
|
for fallback in &auth_config.profile_by_name_fallbacks {
|
||||||
|
candidate_urls.push(fallback.as_str());
|
||||||
match response.status() {
|
|
||||||
StatusCode::OK => {}
|
|
||||||
StatusCode::NO_CONTENT | StatusCode::NOT_FOUND => return Ok(None),
|
|
||||||
other => Err(AuthError::UnknownStatusCode(other))?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile: MojangProfileByNameResponse = response
|
let agent = create_agent(auth_config);
|
||||||
.body_mut()
|
|
||||||
.read_json()
|
|
||||||
.map_err(|_| AuthError::FailedParse)?;
|
|
||||||
|
|
||||||
let parsed_uuid = Uuid::parse_str(&profile.id).map_err(|_| AuthError::FailedParse)?;
|
let mut not_found_count = 0;
|
||||||
Ok(Some((parsed_uuid, profile.name)))
|
let mut last_unknown_status = None;
|
||||||
|
|
||||||
|
for url_template in candidate_urls {
|
||||||
|
let address = url_template.replace("{username}", name);
|
||||||
|
|
||||||
|
let mut response = match agent.get(&address).call() {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Profile lookup server at '{address}' is down or unreachable: {err}"
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if status.is_server_error() {
|
||||||
|
tracing::warn!("Profile lookup server at '{address}' returned server error: {status}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match status {
|
||||||
|
StatusCode::OK => match response
|
||||||
|
.body_mut()
|
||||||
|
.read_json::<MojangProfileByNameResponse>()
|
||||||
|
{
|
||||||
|
Ok(profile) => {
|
||||||
|
let parsed_uuid =
|
||||||
|
Uuid::parse_str(&profile.id).map_err(|_| AuthError::FailedParse)?;
|
||||||
|
return Ok(Some((parsed_uuid, profile.name)));
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!(
|
||||||
|
"Failed to parse profile by name response from '{address}': {err}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StatusCode::NO_CONTENT | StatusCode::NOT_FOUND => {
|
||||||
|
not_found_count += 1;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
last_unknown_status = Some(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if not_found_count > 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else if let Some(status) = last_unknown_status {
|
||||||
|
Err(AuthError::UnknownStatusCode(status))
|
||||||
|
} else {
|
||||||
|
Err(AuthError::FailedResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_profile_by_uuid(
|
pub fn fetch_profile_by_uuid(
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
_auth_config: &AuthenticationConfig,
|
auth_config: &AuthenticationConfig,
|
||||||
) -> Result<Option<GameProfile>, AuthError> {
|
) -> Result<Option<GameProfile>, AuthError> {
|
||||||
let url = format!(
|
let primary_url = auth_config
|
||||||
"https://sessionserver.mojang.com/session/minecraft/profile/{}?unsigned=false",
|
.profile_by_uuid_url
|
||||||
uuid.simple()
|
.as_deref()
|
||||||
);
|
.unwrap_or(MOJANG_PROFILE_BY_UUID_URL);
|
||||||
|
|
||||||
let mut response = ureq::get(&url)
|
let mut candidate_urls = Vec::with_capacity(1 + auth_config.profile_by_uuid_fallbacks.len());
|
||||||
.call()
|
candidate_urls.push(primary_url);
|
||||||
.map_err(|_| AuthError::FailedResponse)?;
|
for fallback in &auth_config.profile_by_uuid_fallbacks {
|
||||||
|
candidate_urls.push(fallback.as_str());
|
||||||
match response.status() {
|
|
||||||
StatusCode::OK => {}
|
|
||||||
StatusCode::NO_CONTENT | StatusCode::NOT_FOUND => return Ok(None),
|
|
||||||
other => Err(AuthError::UnknownStatusCode(other))?,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile: GameProfile = response
|
let agent = create_agent(auth_config);
|
||||||
.body_mut()
|
|
||||||
.read_json()
|
|
||||||
.map_err(|_| AuthError::FailedParse)?;
|
|
||||||
|
|
||||||
Ok(Some(profile))
|
let mut not_found_count = 0;
|
||||||
|
let mut last_unknown_status = None;
|
||||||
|
|
||||||
|
let uuid_simple = uuid.simple().to_string();
|
||||||
|
|
||||||
|
for url_template in candidate_urls {
|
||||||
|
let address = url_template
|
||||||
|
.replace("{uuid}", &uuid_simple)
|
||||||
|
.replace("{uuid_hyphenated}", &uuid.to_string());
|
||||||
|
|
||||||
|
let mut response = match agent.get(&address).call() {
|
||||||
|
Ok(resp) => resp,
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Profile fetch server at '{address}' is down or unreachable: {err}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let status = response.status();
|
||||||
|
if status.is_server_error() {
|
||||||
|
tracing::warn!("Profile fetch server at '{address}' returned server error: {status}");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match status {
|
||||||
|
StatusCode::OK => match response.body_mut().read_json::<GameProfile>() {
|
||||||
|
Ok(profile) => return Ok(Some(profile)),
|
||||||
|
Err(err) => {
|
||||||
|
tracing::warn!("Failed to parse GameProfile response from '{address}': {err}");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StatusCode::NO_CONTENT | StatusCode::NOT_FOUND => {
|
||||||
|
not_found_count += 1;
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
last_unknown_status = Some(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if not_found_count > 0 {
|
||||||
|
Ok(None)
|
||||||
|
} else if let Some(status) = last_unknown_status {
|
||||||
|
Err(AuthError::UnknownStatusCode(status))
|
||||||
|
} else {
|
||||||
|
Err(AuthError::FailedResponse)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
@ -309,4 +451,60 @@ mod tests {
|
|||||||
serde_json::from_slice(json.as_bytes()).expect("profile should parse");
|
serde_json::from_slice(json.as_bytes()).expect("profile should parse");
|
||||||
assert!(profile.signature_required);
|
assert!(profile.signature_required);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn format_auth_url() {
|
||||||
|
let template =
|
||||||
|
"https://auth.example.com/hasJoined?username={username}&serverId={server_hash}&ip={ip}";
|
||||||
|
let formatted = super::format_auth_url(
|
||||||
|
template,
|
||||||
|
"Player1",
|
||||||
|
"hash123",
|
||||||
|
&"127.0.0.1".parse().unwrap(),
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
formatted,
|
||||||
|
"https://auth.example.com/hasJoined?username=Player1&serverId=hash123&ip=127.0.0.1"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_config_fallbacks_deserialization() {
|
||||||
|
let json_str = r#"{
|
||||||
|
"enabled": true,
|
||||||
|
"url": "https://primary.auth/hasJoined?username={username}&serverId={server_hash}",
|
||||||
|
"fallbacks": [
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}",
|
||||||
|
"https://fallback2.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
let config: pumpkin_config::AuthenticationConfig =
|
||||||
|
serde_json::from_str(json_str).expect("config should deserialize");
|
||||||
|
assert_eq!(
|
||||||
|
config.url.as_deref(),
|
||||||
|
Some("https://primary.auth/hasJoined?username={username}&serverId={server_hash}")
|
||||||
|
);
|
||||||
|
assert_eq!(config.fallbacks.len(), 2);
|
||||||
|
assert_eq!(
|
||||||
|
config.fallbacks[0],
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn auth_config_fallback_urls_alias_deserialization() {
|
||||||
|
let json_str = r#"{
|
||||||
|
"enabled": true,
|
||||||
|
"fallback_urls": [
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
]
|
||||||
|
}"#;
|
||||||
|
let config: pumpkin_config::AuthenticationConfig =
|
||||||
|
serde_json::from_str(json_str).expect("config with alias should deserialize");
|
||||||
|
assert_eq!(config.fallbacks.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
config.fallbacks[0],
|
||||||
|
"https://fallback1.auth/hasJoined?username={username}&serverId={server_hash}"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user