fix: harden Windows install and launch pipeline
This commit is contained in:
@@ -72,7 +72,9 @@ impl fmt::Display for AccountError {
|
||||
Self::Api(message) => formatter.write_str(message),
|
||||
Self::Io(error) => write!(formatter, "Не удалось сохранить сессию: {error}"),
|
||||
Self::InvalidSession => formatter.write_str("Сессия ShaCraft истекла — войдите снова"),
|
||||
Self::NoLinkedNickname => formatter.write_str("Сначала привяжите игровой ник к серверу Aeronautics"),
|
||||
Self::NoLinkedNickname => {
|
||||
formatter.write_str("Сначала привяжите игровой ник к серверу Aeronautics")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,9 +89,14 @@ fn client() -> Result<Client, AccountError> {
|
||||
|
||||
fn api_error(response: Response) -> AccountError {
|
||||
#[derive(Deserialize)]
|
||||
struct ErrorBody { detail: Option<String> }
|
||||
struct ErrorBody {
|
||||
detail: Option<String>,
|
||||
}
|
||||
let status = response.status();
|
||||
let detail = response.json::<ErrorBody>().ok().and_then(|body| body.detail);
|
||||
let detail = response
|
||||
.json::<ErrorBody>()
|
||||
.ok()
|
||||
.and_then(|body| body.detail);
|
||||
AccountError::Api(detail.unwrap_or_else(|| format!("ShaCraft API: HTTP {status}")))
|
||||
}
|
||||
|
||||
@@ -105,14 +112,19 @@ fn save_session(data_dir: &Path, token: &str) -> Result<(), AccountError> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::set_permissions(&temporary, fs::Permissions::from_mode(0o600)).map_err(AccountError::Io)?;
|
||||
fs::set_permissions(&temporary, fs::Permissions::from_mode(0o600))
|
||||
.map_err(AccountError::Io)?;
|
||||
}
|
||||
fs::rename(temporary, path).map_err(AccountError::Io)
|
||||
crate::download::replace_file(&temporary, &path).map_err(AccountError::Io)
|
||||
}
|
||||
|
||||
fn load_session(data_dir: &Path) -> Result<String, AccountError> {
|
||||
let token = fs::read_to_string(session_path(data_dir)).map_err(|error| {
|
||||
if error.kind() == io::ErrorKind::NotFound { AccountError::InvalidSession } else { AccountError::Io(error) }
|
||||
if error.kind() == io::ErrorKind::NotFound {
|
||||
AccountError::InvalidSession
|
||||
} else {
|
||||
AccountError::Io(error)
|
||||
}
|
||||
})?;
|
||||
let token = token.trim();
|
||||
if token.len() < 32 || token.bytes().any(|byte| byte.is_ascii_whitespace()) {
|
||||
@@ -121,32 +133,59 @@ fn load_session(data_dir: &Path) -> Result<String, AccountError> {
|
||||
Ok(token.to_owned())
|
||||
}
|
||||
|
||||
pub fn authenticate(data_dir: &Path, username: &str, password: &str, register: bool) -> Result<LoginResult, AccountError> {
|
||||
let endpoint = if register { "/api/launcher/auth/register" } else { "/api/launcher/auth/login" };
|
||||
let response = client()?.post(format!("{API_ORIGIN}{endpoint}"))
|
||||
.json(&Credentials { username, password }).send().map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() { return Err(api_error(response)); }
|
||||
let payload = response.json::<AuthResponse>().map_err(AccountError::Network)?;
|
||||
pub fn authenticate(
|
||||
data_dir: &Path,
|
||||
username: &str,
|
||||
password: &str,
|
||||
register: bool,
|
||||
) -> Result<LoginResult, AccountError> {
|
||||
let endpoint = if register {
|
||||
"/api/launcher/auth/register"
|
||||
} else {
|
||||
"/api/launcher/auth/login"
|
||||
};
|
||||
let response = client()?
|
||||
.post(format!("{API_ORIGIN}{endpoint}"))
|
||||
.json(&Credentials { username, password })
|
||||
.send()
|
||||
.map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(api_error(response));
|
||||
}
|
||||
let payload = response
|
||||
.json::<AuthResponse>()
|
||||
.map_err(AccountError::Network)?;
|
||||
save_session(data_dir, &payload.session_token)?;
|
||||
Ok(LoginResult { account: payload.account, recovery_codes: payload.recovery_codes })
|
||||
Ok(LoginResult {
|
||||
account: payload.account,
|
||||
recovery_codes: payload.recovery_codes,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_account(data_dir: &Path) -> Result<Account, AccountError> {
|
||||
let token = load_session(data_dir)?;
|
||||
let response = client()?.get(format!("{API_ORIGIN}/api/launcher/account"))
|
||||
.bearer_auth(token).send().map_err(AccountError::Network)?;
|
||||
let response = client()?
|
||||
.get(format!("{API_ORIGIN}/api/launcher/account"))
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.map_err(AccountError::Network)?;
|
||||
if response.status() == reqwest::StatusCode::UNAUTHORIZED {
|
||||
let _ = fs::remove_file(session_path(data_dir));
|
||||
return Err(AccountError::InvalidSession);
|
||||
}
|
||||
if !response.status().is_success() { return Err(api_error(response)); }
|
||||
if !response.status().is_success() {
|
||||
return Err(api_error(response));
|
||||
}
|
||||
response.json::<Account>().map_err(AccountError::Network)
|
||||
}
|
||||
|
||||
pub fn logout(data_dir: &Path) -> Result<(), AccountError> {
|
||||
if let Ok(token) = load_session(data_dir) {
|
||||
let _ = client()?.post(format!("{API_ORIGIN}/api/launcher/auth/logout"))
|
||||
.bearer_auth(token).json(&serde_json::json!({})).send();
|
||||
let _ = client()?
|
||||
.post(format!("{API_ORIGIN}/api/launcher/auth/logout"))
|
||||
.bearer_auth(token)
|
||||
.json(&serde_json::json!({}))
|
||||
.send();
|
||||
}
|
||||
match fs::remove_file(session_path(data_dir)) {
|
||||
Ok(()) => Ok(()),
|
||||
@@ -155,25 +194,43 @@ pub fn logout(data_dir: &Path) -> Result<(), AccountError> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_link(data_dir: &Path, server_id: &str, nickname: &str) -> Result<LinkStart, AccountError> {
|
||||
pub fn start_link(
|
||||
data_dir: &Path,
|
||||
server_id: &str,
|
||||
nickname: &str,
|
||||
) -> Result<LinkStart, AccountError> {
|
||||
let token = load_session(data_dir)?;
|
||||
let response = client()?.post(format!("{API_ORIGIN}/api/account/link/start"))
|
||||
.bearer_auth(token).json(&serde_json::json!({"server_id": server_id, "mc_username": nickname}))
|
||||
.send().map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() { return Err(api_error(response)); }
|
||||
let response = client()?
|
||||
.post(format!("{API_ORIGIN}/api/account/link/start"))
|
||||
.bearer_auth(token)
|
||||
.json(&serde_json::json!({"server_id": server_id, "mc_username": nickname}))
|
||||
.send()
|
||||
.map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(api_error(response));
|
||||
}
|
||||
response.json::<LinkStart>().map_err(AccountError::Network)
|
||||
}
|
||||
|
||||
pub fn link_status(data_dir: &Path, challenge_id: i64) -> Result<LinkStatus, AccountError> {
|
||||
let token = load_session(data_dir)?;
|
||||
let response = client()?.get(format!("{API_ORIGIN}/api/account/link/status/{challenge_id}"))
|
||||
.bearer_auth(token).send().map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() { return Err(api_error(response)); }
|
||||
let response = client()?
|
||||
.get(format!(
|
||||
"{API_ORIGIN}/api/account/link/status/{challenge_id}"
|
||||
))
|
||||
.bearer_auth(token)
|
||||
.send()
|
||||
.map_err(AccountError::Network)?;
|
||||
if !response.status().is_success() {
|
||||
return Err(api_error(response));
|
||||
}
|
||||
response.json::<LinkStatus>().map_err(AccountError::Network)
|
||||
}
|
||||
|
||||
pub fn aeronautics_nickname(data_dir: &Path) -> Result<String, AccountError> {
|
||||
get_account(data_dir)?.links.into_iter()
|
||||
get_account(data_dir)?
|
||||
.links
|
||||
.into_iter()
|
||||
.find(|link| link.server_id == "aoc")
|
||||
.map(|link| link.mc_username)
|
||||
.ok_or(AccountError::NoLinkedNickname)
|
||||
@@ -182,13 +239,19 @@ pub fn aeronautics_nickname(data_dir: &Path) -> Result<String, AccountError> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{load_session, save_session, session_path};
|
||||
use std::{fs, process, time::{SystemTime, UNIX_EPOCH}};
|
||||
use std::{
|
||||
fs, process,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
fn temporary_directory() -> std::path::PathBuf {
|
||||
std::env::temp_dir().join(format!(
|
||||
"shacraft-account-test-{}-{}",
|
||||
process::id(),
|
||||
SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos()
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_nanos()
|
||||
))
|
||||
}
|
||||
|
||||
@@ -208,7 +271,14 @@ mod tests {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let directory = temporary_directory();
|
||||
save_session(&directory, "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG").unwrap();
|
||||
assert_eq!(fs::metadata(session_path(&directory)).unwrap().permissions().mode() & 0o077, 0);
|
||||
assert_eq!(
|
||||
fs::metadata(session_path(&directory))
|
||||
.unwrap()
|
||||
.permissions()
|
||||
.mode()
|
||||
& 0o077,
|
||||
0
|
||||
);
|
||||
fs::remove_dir_all(directory).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user