Skip to content

Commit

Permalink
Merge pull request #4 from skelpo/develop
Browse files Browse the repository at this point in the history
Changed JWTAuthenticatable.authBody(from:) Return Type to `Future<AuthBody>`
  • Loading branch information
calebkleveter authored Apr 26, 2018
2 parents be72694 + d080b42 commit 5df5571
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 39 deletions.
57 changes: 42 additions & 15 deletions Sources/JWTAuthenticatable/BasicJWTAuthenticatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ import Fluent
import Crypto
import Vapor

/// Used to decode a request body in
/// `BasicJWTAuthenticatable.authBody(from:)`.
///
/// This type is generic so we can access the property
/// name of the `usernameKey` as the `username` deocding string value.
struct UsernamePassword<Model: BasicJWTAuthenticatable>: Codable {

/// The `username` value for creating
/// a `BasicAuthorization` instance.
let username: String?

/// The `password` value for creating
/// a `BasicAuthorization` instance.
let password: String?

/// The keys used to decode a request
/// body to this struct type.
enum CodingKeys: CodingKey {

/// The decoding key for the `password` property.
case password

/// The decoding key for the `username` property.
case username

/// See `CodingKey.stringValue`.
var stringValue: String {
switch self {
case .password: return "password"
case .username: return (try? Model.reflectProperty(forKey: Model.usernameKey)?.path[0] ?? "email") ?? "email"
}
}
}
}

/// Represents a type that can be authenticated with a basic
/// username/email and password and be authorized with
/// a JWT payload.
Expand All @@ -31,23 +66,15 @@ public protocol BasicJWTAuthenticatable: JWTAuthenticatable where AuthBody == Ba
/// required by the `JWTAuthenticatable` protocol.
extension BasicJWTAuthenticatable {

public static func authBody(from request: Request)throws -> BasicAuthorization? {
public static func authBody(from request: Request)throws -> Future<BasicAuthorization?> {

// Get the `CodingKey` string value of the property referanced by the `usernameKey`.
// If one is not found, default to `"email"`.
let usernameKey = try Self.reflectProperty(forKey: Self.usernameKey)?.path[0] ?? "email"

// Get the values of `usernameKey` and `"password"` from the request body.
// We do this synchronously because:
// 1. The method signiture requires it
// 2. It's easier to work with.
// 3. We don't have to spin up another thread to
// do the decoding, so it will probably be faster.
guard let username: String = try request.content.syncGet(at: usernameKey), let password: String = try request.content.syncGet(at: "password") else {
return nil
// Get the request body as a `UsernamePassword` instance and convert it to a `BasicAuthorization` instance.
return try request.content.decode(UsernamePassword<Self>.self).map(to: AuthBody?.self) { authData in
guard let password = authData.password, let username = authData.username else {
return nil
}
return AuthBody(username: username, password: password)
}

return AuthBody(username: username, password: password)
}

public static func authenticate(from payload: Payload, on request: Request)throws -> Future<Self> {
Expand Down
4 changes: 2 additions & 2 deletions Sources/JWTAuthenticatable/JWTAuthenticatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ public protocol JWTAuthenticatable: Authenticatable, Content where Payload.ID ==
/// - Parameter request: The request for the route where the body
/// will be fetched to use in.
///
/// - Returns: The authentication data. `nil` if the data does not
/// - Returns: The authentication data wrapped in a future. `nil` if the data does not
/// exist in the request.
/// - Throws: Whatever throws in the implementation.
static func authBody(from request: Request)throws -> AuthBody?
static func authBody(from request: Request)throws -> Future<AuthBody?>

/// Verifies the payload passed in, then fetches the
/// correct user bassed on the payloads information.
Expand Down
49 changes: 27 additions & 22 deletions Sources/JWTMiddleware/JWTAuthenticatableMiddlware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,29 +23,34 @@ public final class JWTAuthenticatableMiddlware<A: JWTAuthenticatable>: Middlewar
if try request.isAuthenticated(A.self) {
return try next.respond(to: request)

// Check to see if an `AuthBody` instance can be created.
} else if let payload = try A.authBody(from: request) {

// We got an `AuthBody` instance. Authenticate the model, then fire the next responder.
return try A.authenticate(from: payload, on: request).flatMap(to: Response.self) { authenticated in
return try next.respond(to: request)
}

// Check to see if a `Authorization: Bearer ...` header exists.
} else if request.http.headers.bearerAuthorization != nil {

// Header found. Get the payload from the request.
let payload: A.Payload = try request.payload()

// Authenticate the model, then fire the next responder.
return try A.authenticate(from: payload, on: request).flatMap(to: Response.self) { model in
return try next.respond(to: request)
}

} else {

// No Authorized model or data to auth found. Throw a 401 (Unauthorized) error.
throw Abort(.unauthorized, reason: "No authorized user or data to authorize a user was found")
return try A.authBody(from: request).flatMap(to: Response.self, { body in

// Check to see if an `AuthBody` instance can be created.
if let payload = body {

// We got an `AuthBody` instance. Authenticate the model, then fire the next responder.
return try A.authenticate(from: payload, on: request).flatMap(to: Response.self) { authenticated in
return try next.respond(to: request)
}

// Check to see if a `Authorization: Bearer ...` header exists.
} else if request.http.headers.bearerAuthorization != nil {

// Header found. Get the payload from the request.
let payload: A.Payload = try request.payload()

// Authenticate the model, then fire the next responder.
return try A.authenticate(from: payload, on: request).flatMap(to: Response.self) { model in
return try next.respond(to: request)
}

} else {

// No Authorized model or data to auth found. Throw a 401 (Unauthorized) error.
throw Abort(.unauthorized, reason: "No authorized user or data to authorize a user was found")
}
})
}
}
}
Expand Down

0 comments on commit 5df5571

Please sign in to comment.