Add email sending functionality and configuration

This commit is contained in:
Christoph Hagen 2022-10-11 12:09:43 +02:00
parent 3a76c924ca
commit 067d7ffb7a
3 changed files with 71 additions and 2 deletions

View File

@ -3,6 +3,26 @@ import Foundation
struct Configuration { struct Configuration {
let serverPort: Int let serverPort: Int
let mail: EMail
struct EMail {
/// The url to the root of the server
let serverDomain: String
/// SMTP server address
let emailHostname: String
/// username to login
let email: String
/// password to login
let password: String
/// The number of minutes until a password reset token is no longer valid
let tokenExpiryDuration: Int
}
} }
extension Configuration { extension Configuration {
@ -27,6 +47,11 @@ extension Configuration {
} }
} }
} }
extension Configuration.EMail: Codable {
}
extension Configuration: Codable { extension Configuration: Codable {
} }

View File

@ -1,6 +1,7 @@
import Foundation import Foundation
import Fluent import Fluent
import Vapor import Vapor
import SwiftSMTP
typealias PasswordHash = String typealias PasswordHash = String
typealias SessionToken = String typealias SessionToken = String
@ -15,8 +16,20 @@ final class SQLiteDatabase {
private let tables: TableManagement private let tables: TableManagement
init(db: Database) throws { private let mailConfiguration: Configuration.EMail
private let smtp: SMTP
private let mailSender: Mail.User
init(db: Database, mail: Configuration.EMail) throws {
self.tables = try TableManagement(db: db) self.tables = try TableManagement(db: db)
self.smtp = SMTP(
hostname: mail.emailHostname,
email: mail.email,
password: mail.password)
self.mailSender = Mail.User(name: "Schafkopf Server", email: mail.email)
self.mailConfiguration = mail
} }
func registerPlayer(named name: PlayerName, hash: PasswordHash, in database: Database) -> EventLoopFuture<SessionToken> { func registerPlayer(named name: PlayerName, hash: PasswordHash, in database: Database) -> EventLoopFuture<SessionToken> {
@ -33,6 +46,37 @@ final class SQLiteDatabase {
} }
} }
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 mail = Mail(
from: mailSender,
to: [recipient],
subject: "Schafkopf Server Password Reset",
text:
"""
Hello \(name),
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:
<a href="\(url)">\(url)</a>
The link will expire in \(mailConfiguration.tokenExpiryDuration) minutes. If you didn't request the password reset, you don't have to do anything.
Regards,
The Schafkopf Server Team
"""
)
self.smtp.send(mail) { (error) in
if let error = error {
print("Failed to send recovery email to \(email): \(error)")
}
}
}
func passwordHashForExistingPlayer(named name: PlayerName, in database: Database) -> EventLoopFuture<PasswordHash> { func passwordHashForExistingPlayer(named name: PlayerName, in database: Database) -> EventLoopFuture<PasswordHash> {
User.query(on: database).filter(\.$name == name).first() User.query(on: database).filter(\.$name == name).first()
.unwrap(or: Abort(.forbidden)).map { $0.passwordHash } .unwrap(or: Abort(.forbidden)).map { $0.passwordHash }

View File

@ -36,7 +36,7 @@ public func configure(_ app: Application) throws {
app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))
let db = app.databases.database(.sqlite, logger: .init(label: "Init"), on: app.databases.eventLoopGroup.next())! let db = app.databases.database(.sqlite, logger: .init(label: "Init"), on: app.databases.eventLoopGroup.next())!
server = try SQLiteDatabase(db: db) server = try SQLiteDatabase(db: db, mail: configuration.mail)
// Gracefully shut down by closing potentially open socket // Gracefully shut down by closing potentially open socket
DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) { DispatchQueue.global(qos: .utility).asyncAfter(deadline: .now() + .seconds(5)) {