Skip to content

Commit 2eb6454

Browse files
CakeLancelotdongresource
authored andcommitted
Usernames are now case-insensitive
This fixes a UX issue, where if you accidentally capitalized a letter in the username when logging in, it would instead create a new account. The behavior was confusing, since to the user it looks as if their characters were deleted or progress was not saved. In order for this to work, duplicate accounts (e.g. username and USERNAME) need to be deleted/renamed. The server will *detect* if any duplicates exist. If any are found, it will direct the server operator to a pruning script, or they can choose to resolve the duplicates manually.
1 parent bb4029a commit 2eb6454

File tree

4 files changed

+65
-2
lines changed

4 files changed

+65
-2
lines changed

sql/migration3.sql

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
It is recommended in the SQLite manual to turn off
3+
foreign keys when making schema changes that involve them
4+
*/
5+
PRAGMA foreign_keys=OFF;
6+
BEGIN TRANSACTION;
7+
-- Change username column (Login) to be case-insensitive
8+
CREATE TABLE Temp (
9+
AccountID INTEGER NOT NULL,
10+
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
11+
Password TEXT NOT NULL,
12+
Selected INTEGER DEFAULT 1 NOT NULL,
13+
AccountLevel INTEGER NOT NULL,
14+
Created INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
15+
LastLogin INTEGER DEFAULT (strftime('%s', 'now')) NOT NULL,
16+
BannedUntil INTEGER DEFAULT 0 NOT NULL,
17+
BannedSince INTEGER DEFAULT 0 NOT NULL,
18+
BanReason TEXT DEFAULT '' NOT NULL,
19+
PRIMARY KEY(AccountID AUTOINCREMENT)
20+
);
21+
INSERT INTO Temp SELECT * FROM Accounts;
22+
DROP TABLE Accounts;
23+
ALTER TABLE Temp RENAME TO Accounts;
24+
-- Update DB Version
25+
UPDATE Meta SET Value = 4 WHERE Key = 'DatabaseVersion';
26+
UPDATE Meta SET Value = strftime('%s', 'now') WHERE Key = 'LastMigration';
27+
COMMIT;
28+
PRAGMA foreign_keys=ON;

sql/tables.sql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CREATE TABLE IF NOT EXISTS Accounts (
22
AccountID INTEGER NOT NULL,
3-
Login TEXT NOT NULL UNIQUE,
3+
Login TEXT NOT NULL UNIQUE COLLATE NOCASE,
44
Password TEXT NOT NULL,
55
Selected INTEGER DEFAULT 1 NOT NULL,
66
AccountLevel INTEGER NOT NULL,

src/db/Database.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
#include <string>
66
#include <vector>
77

8-
#define DATABASE_VERSION 3
8+
#define DATABASE_VERSION 4
99

1010
namespace Database {
1111

src/db/init.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,37 @@
99
std::mutex dbCrit;
1010
sqlite3 *db;
1111

12+
/*
13+
* When migrating from DB version 3 to 4, we change the username column
14+
* to be case-insensitive. This function ensures there aren't any
15+
* duplicates, e.g. username and USERNAME, before doing the migration.
16+
* I handled this in the code itself rather than the migration file just so
17+
* we can have a more detailed error message than what SQLite provides.
18+
*/
19+
static void checkCaseSensitiveDupes() {
20+
const char* sql = "SELECT Login, COUNT(*) FROM Accounts GROUP BY LOWER(Login) HAVING COUNT(*) > 1;";
21+
22+
sqlite3_stmt* stmt;
23+
sqlite3_prepare_v2(db, sql, -1, &stmt, NULL);
24+
int stat = sqlite3_step(stmt);
25+
26+
if (stat == SQLITE_DONE) {
27+
// no rows returned, so we're good
28+
sqlite3_finalize(stmt);
29+
return;
30+
} else if (stat != SQLITE_ROW) {
31+
std::cout << "[FATAL] Failed to check for duplicate accounts: " << sqlite3_errmsg(db) << std::endl;
32+
sqlite3_finalize(stmt);
33+
exit(1);
34+
}
35+
36+
std::cout << "[FATAL] Case-sensitive duplicates detected in the Login column." << std::endl;
37+
std::cout << "Either manually delete/rename the offending accounts, or run the pruning script:" << std::endl;
38+
std::cout << "https://github.com/OpenFusionProject/scripts/tree/main/db_migration/caseinsens.py" << std::endl;
39+
sqlite3_finalize(stmt);
40+
exit(1);
41+
}
42+
1243
static void createMetaTable() {
1344
std::lock_guard<std::mutex> lock(dbCrit); // XXX
1445

@@ -143,6 +174,10 @@ static void checkMetaTable() {
143174
}
144175

145176
while (dbVersion != DATABASE_VERSION) {
177+
// need to run this before we do any migration logic
178+
if (dbVersion == 3)
179+
checkCaseSensitiveDupes();
180+
146181
// db migrations
147182
std::cout << "[INFO] Migrating Database to Version " << dbVersion + 1 << std::endl;
148183

0 commit comments

Comments
 (0)