129 lines
4.8 KiB
Swift
129 lines
4.8 KiB
Swift
import Vapor
|
|
import FluentPostgresDriver
|
|
import SwiftSMTP
|
|
|
|
public struct AuthenticationController<User: ManagedUser>: Sendable where User.SessionID == ExpiringUserId {
|
|
public init() {}
|
|
|
|
public func routes (_ router: any RoutesBuilder, api apiRouter: any RoutesBuilder) {
|
|
let auth = router.grouped ("auth")
|
|
let api = apiRouter
|
|
.grouped ("auth")
|
|
|
|
auth.get ("login", use: loginForm)
|
|
api.post ("login", use: login)
|
|
auth.get ("password", ":token", use: passwordForm)
|
|
auth.grouped (LogoutMiddleware<User>()).get ("logout", use: logout)
|
|
api.get ("token", ":token", use: token)
|
|
api.post ("forgot", use: forgotPassword)
|
|
api.post ("password", ":token", use: savePassword)
|
|
}
|
|
|
|
func loginForm (request: Request) async throws -> View {
|
|
return try await request.view.render ("login", ["section": "login"])
|
|
}
|
|
|
|
private struct LoginInput: Decodable {
|
|
let email: String
|
|
let password: String
|
|
}
|
|
|
|
func login (request: Request) async throws -> some Response {
|
|
let loginInput = try request.content.decode (LoginInput.self)
|
|
|
|
return try await request.db.withSQLConnection { connection in
|
|
if let user = try await User.find (email: loginInput.email, password: loginInput.password, on: connection) {
|
|
request.auth.login (user)
|
|
request.logger.info ("Authenticated user \(user)")
|
|
|
|
return Response (status: .ok)
|
|
} else {
|
|
return Response (status: .unauthorized)
|
|
}
|
|
}
|
|
}
|
|
|
|
func passwordForm (request: Request) async throws -> View {
|
|
let token = try request.parameters.require ("token")
|
|
|
|
return try await request.view.render ("password", ["token": token, "section": "login"])
|
|
}
|
|
|
|
func logout (request: Request) async throws -> some Response {
|
|
return request.redirect (to: try Environment.baseURL.absoluteString)
|
|
}
|
|
|
|
func token (request: Request) async throws -> UserToken.Token {
|
|
let token = try request.parameters.require ("token")
|
|
|
|
return try await request.db.withSQLConnection { connection in
|
|
guard let token = try await UserToken.fetch (token: token, connection: connection) else {
|
|
throw Abort (.notFound)
|
|
}
|
|
|
|
return token
|
|
}
|
|
}
|
|
|
|
private struct NewPassword: Decodable {
|
|
let password: String
|
|
}
|
|
|
|
func savePassword (request: Request) async throws -> Response {
|
|
let token = try request.parameters.require ("token")
|
|
let newPassword = try request.content.decode (NewPassword.self)
|
|
|
|
return try await request.db.withConnection { connection in
|
|
try await connection.transaction { connection in
|
|
guard let userToken = try await UserToken.fetch (token: token, connection: connection) else {
|
|
throw Abort (.notFound)
|
|
}
|
|
|
|
try await User.update (userId: userToken.userId, password: newPassword.password, on: connection)
|
|
try await UserToken.delete (token: token, connection: connection)
|
|
|
|
return Response(status: .ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct Input: Decodable {
|
|
let email: String
|
|
}
|
|
|
|
func forgotPassword (request: Request) async throws -> Response {
|
|
|
|
let input = try request.content.decode (Input.self)
|
|
|
|
return try await request.db.withConnection { connection in
|
|
try await connection.transaction { connection in
|
|
if let user = try await User.find (email: input.email, on: connection) {
|
|
let token = try await UserToken.create (connection: connection).token
|
|
try await User.store (token: token, userId: user.id, on: connection)
|
|
let host = try Environment.baseURL.host() ?? ""
|
|
let body = try await request.view.render ("email/reset", ["token": token, "host": host, "section": "login"])
|
|
.data
|
|
let message = Email (sender: Email.Contact (emailAddress: try Environment.emailSender),
|
|
recipients: [Email.Contact(emailAddress: input.email)],
|
|
subject: "Aktivera ditt konto på \(host)",
|
|
body: .plain (String (buffer: body)))
|
|
try await request.swiftSMTP.mailer.send (message)
|
|
}
|
|
|
|
return Response(status: .ok)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
struct LogoutMiddleware<User: ManagedUser>: AsyncMiddleware {
|
|
func respond (to request: Vapor.Request, chainingTo next: any AsyncResponder) async throws -> Vapor.Response {
|
|
let response = try await next.respond (to: request)
|
|
|
|
request.auth.logout (User.self)
|
|
request.session.destroy()
|
|
|
|
return response
|
|
}
|
|
}
|