Complete private authentication, server integrations and activity reporting
Add Fabric/Forge version builds and Paper integration, preserve permanent player identities across renames, harden session authorization, and surface privacy-conscious launcher/server activity in the panel.
This commit is contained in:
commit
a1f19e86c6
74 files changed
+2108
-95
No files matched your search
@@ -67,7 +67,7 @@ async fn find_user(state: &AppState, username: &str) -> AppResult<Option<UserRow
|
||||
}
|
||||
|
||||
/// Panel token + a fresh game session for authlib-injector.
|
||||
async fn signed_in(state: &AppState, user: &UserRow) -> AppResult<AuthResponse> {
|
||||
pub(crate) async fn signed_in(state: &AppState, user: &UserRow) -> AppResult<AuthResponse> {
|
||||
let (access_token, client_token) = yggdrasil::issue_token(state, user.id, None).await?;
|
||||
Ok(AuthResponse {
|
||||
token: state.keys.issue(user)?,
|
||||
@@ -98,6 +98,7 @@ pub async fn login(State(state): State<AppState>, Json(req): Json<LoginRequest>)
|
||||
return Err(AppError::forbidden("account sign-in is currently disabled"));
|
||||
}
|
||||
sqlx::query("UPDATE users SET last_login = ? WHERE id = ?").bind(crate::db::now()).bind(user.id).execute(&state.db).await?;
|
||||
super::activity::record(&state, &user, "auth", "login", None).await?;
|
||||
Ok(Json(signed_in(&state, &user).await?))
|
||||
}
|
||||
|
||||
@@ -134,16 +135,41 @@ pub async fn me(State(state): State<AppState>, AuthUser(user): AuthUser) -> AppR
|
||||
|
||||
pub async fn event(
|
||||
State(state): State<AppState>,
|
||||
MaybeUser(user): MaybeUser,
|
||||
AuthUser(user): AuthUser,
|
||||
ClientIp(ip): ClientIp,
|
||||
Json(ev): Json<LaunchEvent>,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let kind = if ev.kind == "launch" { "launch" } else { "other" };
|
||||
let kind = ev.kind.as_str();
|
||||
if !matches!(
|
||||
kind,
|
||||
"launch"
|
||||
| "launcher_open"
|
||||
| "launcher_close"
|
||||
| "settings_changed"
|
||||
| "account_selected"
|
||||
| "logout"
|
||||
| "install_start"
|
||||
| "install_complete"
|
||||
| "repair_start"
|
||||
| "repair_complete"
|
||||
| "launch_failed"
|
||||
| "launch_cancelled"
|
||||
| "game_exit"
|
||||
| "game_crash"
|
||||
| "game_killed"
|
||||
| "instance_deleted"
|
||||
| "cache_cleared"
|
||||
| "update_installed"
|
||||
| "folder_opened"
|
||||
| "link_opened"
|
||||
) {
|
||||
return Err(AppError::bad_request("unknown launcher event"));
|
||||
}
|
||||
// Remember where signed-in players launch from, so game servers can
|
||||
// require "joined through the launcher" (see game server settings).
|
||||
if let (Some(u), Some(ip), "launch") = (&user, &ip, kind) {
|
||||
if let (Some(ip), "launch") = (&ip, kind) {
|
||||
sqlx::query("INSERT INTO launcher_sessions (user_id, ip, created_at) VALUES (?, ?, ?)")
|
||||
.bind(u.id)
|
||||
.bind(user.id)
|
||||
.bind(ip)
|
||||
.bind(crate::db::now())
|
||||
.execute(&state.db)
|
||||
@@ -151,10 +177,11 @@ pub async fn event(
|
||||
let cutoff = (chrono::Utc::now() - chrono::Duration::days(2)).to_rfc3339_opts(chrono::SecondsFormat::Secs, true);
|
||||
sqlx::query("DELETE FROM launcher_sessions WHERE created_at < ?").bind(cutoff).execute(&state.db).await?;
|
||||
}
|
||||
let name = user.map(|u| u.username).or(ev.username).map(|n| n.chars().take(32).collect::<String>());
|
||||
sqlx::query("INSERT INTO events (instance_id, username, kind, created_at) VALUES (?, ?, ?, ?)")
|
||||
// Identity comes exclusively from the verified bearer token.
|
||||
sqlx::query("INSERT INTO events (instance_id, username, uuid, kind, created_at) VALUES (?, ?, ?, ?, ?)")
|
||||
.bind(ev.instance_id.chars().take(64).collect::<String>())
|
||||
.bind(name)
|
||||
.bind(&user.username)
|
||||
.bind(&user.uuid)
|
||||
.bind(kind)
|
||||
.bind(crate::db::now())
|
||||
.execute(&state.db)
|
||||
|
||||
Reference in new issue
Block a user