Add email sending functionality and configuration
This commit is contained in:
parent
3a76c924ca
commit
067d7ffb7a
@ -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 {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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 }
|
||||||
|
@ -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)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user