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
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user