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:
SCOPEDD committed 2026-09-28 13:31:17 -04:00
commit a1f19e86c6
74 files changed
+2108 -95

No files matched your search

+66
View File
@@ -178,6 +178,43 @@ const MIGRATIONS: &[&str] = &[
CREATE INDEX server_events_server ON server_events(server_id, id);
CREATE INDEX server_events_uuid ON server_events(uuid, id);
"#,
// 3: idempotent server stat batches (retained until the server is deleted).
r#"
CREATE TABLE server_sync_receipts (
server_id INTEGER NOT NULL REFERENCES game_servers(id) ON DELETE CASCADE,
batch_id TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (server_id, batch_id)
);
"#,
// 4: permanent player identity, reserved historical names and audit data.
r#"
ALTER TABLE users ADD COLUMN auth_version INTEGER NOT NULL DEFAULT 0;
ALTER TABLE events ADD COLUMN uuid TEXT;
ALTER TABLE events ADD COLUMN detail TEXT;
ALTER TABLE events ADD COLUMN source TEXT NOT NULL DEFAULT 'launcher';
CREATE INDEX events_uuid ON events(uuid, id);
CREATE TABLE reserved_usernames (
name TEXT PRIMARY KEY COLLATE NOCASE,
uuid TEXT NOT NULL
);
INSERT INTO reserved_usernames SELECT username, uuid FROM users WHERE uuid <> '';
CREATE TRIGGER immutable_player_uuid BEFORE UPDATE OF uuid ON users
WHEN OLD.uuid <> '' AND NEW.uuid <> OLD.uuid
BEGIN SELECT RAISE(ABORT, 'player UUID is immutable'); END;
CREATE TRIGGER reserve_name_insert BEFORE INSERT ON users
WHEN EXISTS(SELECT 1 FROM reserved_usernames WHERE name = NEW.username AND uuid <> NEW.uuid)
BEGIN SELECT RAISE(ABORT, 'username reserved'); END;
CREATE TRIGGER reserve_name_update BEFORE UPDATE OF username ON users
WHEN EXISTS(SELECT 1 FROM reserved_usernames WHERE name = NEW.username AND uuid <> NEW.uuid)
BEGIN SELECT RAISE(ABORT, 'username reserved'); END;
CREATE TRIGGER remember_name_insert AFTER INSERT ON users
WHEN NEW.uuid <> ''
BEGIN INSERT OR IGNORE INTO reserved_usernames VALUES (NEW.username, NEW.uuid); END;
CREATE TRIGGER remember_name_update AFTER UPDATE OF username, uuid ON users
WHEN NEW.uuid <> ''
BEGIN INSERT OR IGNORE INTO reserved_usernames VALUES (NEW.username, NEW.uuid); END;
"#,
];
pub async fn connect(data_dir: &Path) -> Result<SqlitePool> {
@@ -233,3 +270,32 @@ async fn backfill_uuids(pool: &SqlitePool) -> Result<()> {
pub fn now() -> String {
chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn identity_migration_preserves_existing_player_data_keys() {
let pool = SqlitePoolOptions::new().max_connections(1).connect("sqlite::memory:").await.unwrap();
sqlx::raw_sql(MIGRATIONS[0]).execute(&pool).await.unwrap();
sqlx::raw_sql(MIGRATIONS[1]).execute(&pool).await.unwrap();
let existing = scopenet_shared::offline_uuid("LegacyPlayer");
sqlx::query("INSERT INTO users(username,password_hash,created_at,uuid) VALUES('LegacyPlayer','test',?,?)")
.bind(now())
.bind(&existing)
.execute(&pool)
.await
.unwrap();
sqlx::raw_sql("PRAGMA user_version=2").execute(&pool).await.unwrap();
migrate(&pool).await.unwrap();
let uuid: String = sqlx::query_scalar("SELECT uuid FROM users WHERE username='LegacyPlayer'").fetch_one(&pool).await.unwrap();
assert_eq!(uuid, existing);
sqlx::query("UPDATE users SET username='RenamedPlayer' WHERE uuid=?").bind(&existing).execute(&pool).await.unwrap();
let uuid: String = sqlx::query_scalar("SELECT uuid FROM users WHERE username='RenamedPlayer'").fetch_one(&pool).await.unwrap();
assert_eq!(uuid, existing);
let owner: String =
sqlx::query_scalar("SELECT uuid FROM reserved_usernames WHERE name='legacyplayer'").fetch_one(&pool).await.unwrap();
assert_eq!(owner, existing);
}
}