diff --git a/Sources/App/Management/SQLiteDatabase.swift b/Sources/App/Management/SQLiteDatabase.swift index 50cf9c1..0058d57 100644 --- a/Sources/App/Management/SQLiteDatabase.swift +++ b/Sources/App/Management/SQLiteDatabase.swift @@ -54,11 +54,35 @@ final class SQLiteDatabase { self.playerNameForToken[token] = name return token } + + /** + Send a password reset email. + + Possible errors: + - `404`: Player name or email not found. + */ + func sendPasswordResetEmailIfPossible(name: PlayerName, in database: Database) async throws { + guard let user = try await User.query(on: database).filter(\.$name == name).first() else { + throw Abort(.notFound) + } + guard let email = user.recoveryEmail else { + throw Abort(.notFound) + } + try await user.$resetRequest.load(on: database) + if let request = user.resetRequest { + request.renew() + try await request.save(on: database) + self.sendEmail(name: name, email: email, token: request.resetToken) + } else { + let reset = PasswordReset() + try await user.$resetRequest.create(reset, on: database) + self.sendEmail(name: name, email: email, token: reset.resetToken) + } } private func sendEmail(name: PlayerName, email: String, token: String) { let recipient = Mail.User(name: name, email: email) - let url = "\(mailConfiguration.serverDomain)/player/reset?token=\(token)" + let url = "\(mailConfiguration.serverDomain)/recovery.html?token=\(token)" let mail = Mail( from: mailSender, to: [recipient], @@ -70,7 +94,7 @@ final class SQLiteDatabase { a reset of your account password has been requested for the Schafkopf Server at \(mailConfiguration.serverDomain). To choose a new password, click the following link: - \(url) + \(url) The link will expire in \(mailConfiguration.tokenExpiryDuration) minutes. If you didn't request the password reset, you don't have to do anything. @@ -80,7 +104,7 @@ final class SQLiteDatabase { """ ) - self.smtp.send(mail) { (error) in + smtp.send(mail) { (error) in if let error = error { print("Failed to send recovery email to \(email): \(error)") } diff --git a/Sources/App/routes.swift b/Sources/App/routes.swift index 058ea84..c002ba0 100644 --- a/Sources/App/routes.swift +++ b/Sources/App/routes.swift @@ -64,6 +64,25 @@ func registerPlayer(_ app: Application) { return try await server.registerPlayer(named: name, hash: hash, email: mail, in: request.db) } } + +/** + Request an email to reset the password of a player. + + Headers: + - `name`: The player name + + Possible responses: + - `200`: Success, email will be sent + - `400`: Missing name header + - `404`: Player name not found or no email registered + */ +func requestPlayerPasswordReset(_ app: Application) { + app.post("player", "password", "reset") { request async throws -> HTTPResponseStatus in + let name = try request.header(.name) // Error: 400 + try await server.sendPasswordResetEmailIfPossible(name: name, in: request.db) + return .ok + } +} /** Delete a player.