Persist registrations on disk

This commit is contained in:
Christoph Hagen 2021-12-01 12:45:42 +01:00
parent e7b3ac3e9f
commit 7265fd0f0d
4 changed files with 80 additions and 5 deletions

1
Resources/.gitkeep Normal file
View File

@ -0,0 +1 @@

View File

@ -7,10 +7,10 @@ final class Database {
private let tables: TableManagement private let tables: TableManagement
init() { init(storageFolder: URL) throws {
self.players = PlayerManagement() self.players = try PlayerManagement(storageFolder: storageFolder)
self.tables = TableManagement() self.tables = TableManagement()
// TODO: Load server data from disk // TODO: Load table data from disk
// TODO: Save data to disk // TODO: Save data to disk
} }

View File

@ -15,9 +15,75 @@ final class PlayerManagement {
/// A reverse mapping between generated access tokens and player name /// A reverse mapping between generated access tokens and player name
private var playerNameForToken = [SessionToken: PlayerName]() private var playerNameForToken = [SessionToken: PlayerName]()
private let passwordFile: FileHandle
private let passwordFileUrl: URL
init() { init(storageFolder: URL) throws {
let url = storageFolder.appendingPathComponent("passwords.txt")
if !FileManager.default.fileExists(atPath: url.path) {
try Data().write(to: url)
}
passwordFile = try FileHandle(forUpdating: url)
passwordFileUrl = url
if #available(macOS 10.15.4, *) {
guard let data = try passwordFile.readToEnd() else {
try passwordFile.seekToEnd()
return
}
try loadPasswords(data: data)
} else {
let data = passwordFile.readDataToEndOfFile()
try loadPasswords(data: data)
}
print("Loaded \(playerPasswordHashes.count) players")
}
private func loadPasswords(data: Data) throws {
String(data: data, encoding: .utf8)!
.components(separatedBy: "\n")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { $0 != "" }
.forEach { line in
let parts = line.components(separatedBy: ":")
// Token may contain the separator
guard parts.count >= 2 else {
print("Invalid line in password file")
return
}
let name = parts[0]
let token = parts.dropFirst().joined(separator: ":")
if token == "" {
playerPasswordHashes[name] = nil
} else {
playerPasswordHashes[name] = token
}
}
}
private func save(password: PasswordHash, forPlayer player: PlayerName) -> Bool {
let entry = player + ":" + password + "\n"
let data = entry.data(using: .utf8)!
do {
if #available(macOS 10.15.4, *) {
try passwordFile.write(contentsOf: data)
} else {
passwordFile.write(data)
}
try passwordFile.synchronize()
return true
} catch {
print("Failed to save password to disk: \(error)")
return false
}
}
private func deletePassword(forPlayer player: PlayerName) -> Bool {
save(password: "", forPlayer: player)
} }
/** /**
@ -48,6 +114,9 @@ final class PlayerManagement {
guard !hasRegisteredPlayer(named: name) else { guard !hasRegisteredPlayer(named: name) else {
return nil return nil
} }
guard save(password: hash, forPlayer: name) else {
return nil
}
self.playerPasswordHashes[name] = hash self.playerPasswordHashes[name] = hash
return startNewSessionForRegisteredPlayer(named: name) return startNewSessionForRegisteredPlayer(named: name)
} }
@ -58,6 +127,9 @@ final class PlayerManagement {
- Returns: The session token of the current player, if one exists - Returns: The session token of the current player, if one exists
*/ */
func deletePlayer(named name: PlayerName) -> SessionToken? { func deletePlayer(named name: PlayerName) -> SessionToken? {
guard deletePassword(forPlayer: name) else {
return nil
}
playerPasswordHashes.removeValue(forKey: name) playerPasswordHashes.removeValue(forKey: name)
guard let sessionToken = sessionTokenForPlayer.removeValue(forKey: name) else { guard let sessionToken = sessionTokenForPlayer.removeValue(forKey: name) else {
return nil return nil

View File

@ -7,7 +7,9 @@ public func configure(_ app: Application) throws {
// serve files from /Public folder // serve files from /Public folder
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
database = Database() let storageFolder = URL(fileURLWithPath: app.directory.resourcesDirectory)
database = try Database(storageFolder: storageFolder)
// register routes // register routes
try routes(app) try routes(app)
} }