diff --git a/Sources/Fluent/ModelUser.swift b/Sources/Fluent/ModelUser.swift new file mode 100644 index 00000000..0479f4c2 --- /dev/null +++ b/Sources/Fluent/ModelUser.swift @@ -0,0 +1,48 @@ +import Vapor + +public protocol ModelUser: Model, Authenticatable { + static var usernameKey: KeyPath> { get } + static var passwordHashKey: KeyPath> { get } + func verify(password: String) throws -> Bool +} + +extension ModelUser { + public static func authenticator( + database: DatabaseID? = nil + ) -> ModelUserAuthenticator { + ModelUserAuthenticator(database: database) + } + + var _$username: Field { + self[keyPath: Self.usernameKey] + } + + var _$passwordHash: Field { + self[keyPath: Self.passwordHashKey] + } +} + +public struct ModelUserAuthenticator: BasicAuthenticator + where User: ModelUser +{ + public let database: DatabaseID? + + public func authenticate( + basic: BasicAuthorization, + for request: Request + ) -> EventLoopFuture { + User.query(on: request.db(self.database)) + .filter(\._$username == basic.username) + .first() + .flatMapThrowing + { + guard let user = $0 else { + return nil + } + guard try user.verify(password: basic.password) else { + return nil + } + return user + } + } +} diff --git a/Sources/Fluent/ModelUserToken.swift b/Sources/Fluent/ModelUserToken.swift new file mode 100644 index 00000000..a10748cd --- /dev/null +++ b/Sources/Fluent/ModelUserToken.swift @@ -0,0 +1,53 @@ +import Vapor + +public protocol ModelUserToken: Model { + associatedtype User: Model & Authenticatable + static var valueKey: KeyPath> { get } + static var userKey: KeyPath> { get } + var isValid: Bool { get } +} + +extension ModelUserToken { + public static func authenticator( + database: DatabaseID? = nil + ) -> ModelUserTokenAuthenticator { + ModelUserTokenAuthenticator(database: database) + } + + var _$value: Field { + self[keyPath: Self.valueKey] + } + + var _$user: Parent { + self[keyPath: Self.userKey] + } +} + +public struct ModelUserTokenAuthenticator: BearerAuthenticator + where Token: ModelUserToken +{ + public typealias User = Token.User + public let database: DatabaseID? + + public func authenticate( + bearer: BearerAuthorization, + for request: Request + ) -> EventLoopFuture { + let db = request.db(self.database) + return Token.query(on: db) + .filter(\._$value == bearer.token) + .first() + .flatMap + { token -> EventLoopFuture in + guard let token = token else { + return request.eventLoop.makeSucceededFuture(nil) + } + guard token.isValid else { + return token.delete(on: db).map { nil } + } + return token._$user.get(on: db) + .map { $0 } + } + } +} +