This repository has been archived by the owner on Nov 16, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from vapor/start
🚀
- Loading branch information
Showing
19 changed files
with
488 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.DS_Store | ||
/.build | ||
/Packages | ||
/*.xcodeproj | ||
Package.pins | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import PackageDescription | ||
|
||
let package = Package( | ||
name: "Auth", | ||
targets: [ | ||
Target(name: "Authentication"), | ||
Target(name: "Authorization", dependencies: ["Authentication"]), | ||
], | ||
dependencies: [ | ||
// Swift models, relationships, and querying for NoSQL and SQL databases. | ||
.Package(url: "https://github.com/vapor/fluent.git", Version(2,0,0, prereleaseIdentifiers: ["alpha"])) | ||
] | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
public enum AuthenticationError: Swift.Error { | ||
case invalidBasicAuthorization | ||
case invalidBearerAuthorization | ||
case noAuthorizationHeader | ||
case notAuthenticated | ||
case invalidCredentials | ||
case unsupportedCredentials | ||
case unspecified(Error) | ||
} | ||
|
||
extension AuthenticationError: CustomStringConvertible { | ||
public var description: String { | ||
let reason: String | ||
|
||
switch self { | ||
case .invalidBasicAuthorization: | ||
reason = "Invalid Authorization Basic header" | ||
case .invalidBearerAuthorization: | ||
reason = "Invalid Authorization Bearer header" | ||
case .noAuthorizationHeader: | ||
reason = "No authorization header" | ||
case .notAuthenticated: | ||
reason = "Not authenticated" | ||
case .invalidCredentials: | ||
reason = "Invalid credentials" | ||
case .unsupportedCredentials: | ||
reason = "Unsupported credentials" | ||
case .unspecified(let error): | ||
reason = "\(error)" | ||
} | ||
|
||
return "Authentication error: \(reason)" | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public protocol Authenticatable { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
public protocol Crendentials { } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
// MARK: Authenticatable | ||
|
||
public protocol CustomAuthenticatable: Authenticatable { | ||
/// Returns the user matching the custom credential. | ||
static func authenticate(custom: Crendentials) throws -> Self | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
// MARK: Data structure | ||
|
||
import Node | ||
|
||
public struct Identifier: Crendentials { | ||
let id: Node | ||
|
||
public init(id: Node) { | ||
self.id = id | ||
} | ||
} | ||
|
||
// MARK: Authenticatable | ||
|
||
public protocol IdentifierAuthenticatable { | ||
/// Return the user with the supplied id. | ||
static func authenticate(_: Identifier) throws -> Self | ||
} | ||
|
||
|
||
// MARK: Entity conformance | ||
|
||
import Fluent | ||
|
||
extension IdentifierAuthenticatable where Self: Entity { | ||
public static func authenticate(_ id: Identifier) throws -> Self { | ||
guard let match = try Self.find(id.id) else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
return match | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
// MARK: Data structure | ||
|
||
public struct Token: Crendentials { | ||
public let string: String | ||
|
||
public init(string: String) { | ||
self.string = string | ||
} | ||
} | ||
|
||
// MARK: Authenticatable | ||
|
||
public protocol TokenAuthenticatable: Authenticatable { | ||
/// The token entity that contains a foreign key | ||
/// pointer to the user table | ||
associatedtype TokenType: TokenProtocol | ||
|
||
/// Returns the user matching the supplied token. | ||
static func authenticate(_: Token) throws -> Self | ||
} | ||
|
||
public protocol TokenProtocol { | ||
static var tokenKey: String { get } | ||
static func findUser<U: Entity>(for: Token) throws -> U | ||
} | ||
|
||
extension TokenProtocol { | ||
public static var tokenKey: String { | ||
return "token" | ||
} | ||
} | ||
|
||
// MARK: Entity conformance | ||
|
||
import Fluent | ||
|
||
extension TokenAuthenticatable where Self: Entity { | ||
public static func authenticate(_ token: Token) throws -> Self { | ||
return try TokenType.findUser(for: token) | ||
} | ||
} | ||
|
||
extension TokenProtocol where Self: Entity { | ||
public static func findUser<U: Entity>(for token: Token) throws -> U { | ||
guard let user = try U.query() | ||
.join(self) | ||
.filter(self, tokenKey, token.string) | ||
.first() | ||
else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
return user | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
// MARK: Data structure | ||
|
||
public struct Password: Crendentials { | ||
public let username: String | ||
public let password: String | ||
public let verifier: PasswordVerifier? | ||
|
||
public init(username: String, password: String, verifier: PasswordVerifier? = nil) { | ||
self.username = username | ||
self.password = password | ||
self.verifier = verifier | ||
} | ||
} | ||
|
||
// MARK: Authenticatable | ||
|
||
public protocol PasswordAuthenticatable: Authenticatable { | ||
// MARK: Username / Password | ||
/// Return the user matching the supplied | ||
/// username and password | ||
static func authenticate(_: Password) throws -> Self | ||
|
||
/// The entity's hashed password used for | ||
/// validating against Password credentials | ||
/// with a PasswordVerifier | ||
var hashedPassword: String? { get } | ||
|
||
/// The key under which the user's username, | ||
/// email, or other identifing value is stored. | ||
static var usernameKey: String { get } | ||
|
||
/// The key under which the user's password | ||
/// is stored. | ||
static var passwordKey: String { get } | ||
} | ||
|
||
extension PasswordAuthenticatable { | ||
public static var usernameKey: String { | ||
return "email" | ||
} | ||
|
||
public static var passwordKey: String { | ||
return "password" | ||
} | ||
|
||
public var hashedPassword: String? { | ||
return nil | ||
} | ||
} | ||
|
||
public protocol PasswordVerifier { | ||
func verify(password: String, matchesHash: String) throws -> Bool | ||
} | ||
|
||
// MARK: Entity conformance | ||
|
||
import Fluent | ||
|
||
extension PasswordAuthenticatable where Self: Entity { | ||
public static func authenticate(_ creds: Password) throws -> Self { | ||
let user: Self | ||
|
||
if let verifier = creds.verifier { | ||
guard let match = try Self | ||
.query() | ||
.filter(usernameKey, creds.username) | ||
.first() | ||
else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
guard let hash = match.hashedPassword else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
guard try verifier.verify( | ||
password: creds.password, | ||
matchesHash: hash | ||
) else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
user = match | ||
} else { | ||
guard let match = try Self | ||
.query() | ||
.filter(usernameKey, creds.username) | ||
.filter(passwordKey, creds.password) | ||
.first() | ||
else { | ||
throw AuthenticationError.invalidCredentials | ||
} | ||
|
||
user = match | ||
} | ||
|
||
return user | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
public struct Authorization { | ||
public let header: String | ||
|
||
public init(header: String) { | ||
self.header = header | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import Core | ||
|
||
extension Authorization { | ||
public var basic: Password? { | ||
guard let range = header.range(of: "Basic ") else { | ||
return nil | ||
} | ||
|
||
let token = header.substring(from: range.upperBound) | ||
|
||
|
||
let decodedToken = token.makeBytes().base64Decoded.string | ||
guard let separatorRange = decodedToken.range(of: ":") else { | ||
return nil | ||
} | ||
|
||
let apiKeyID = decodedToken.substring(to: separatorRange.lowerBound) | ||
let apiKeySecret = decodedToken.substring(from: separatorRange.upperBound) | ||
|
||
return Password(username: apiKeyID, password: apiKeySecret) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
extension Authorization { | ||
public var bearer: Token? { | ||
guard let range = header.range(of: "Bearer ") else { | ||
return nil | ||
} | ||
|
||
let token = header.substring(from: range.upperBound) | ||
return Token(string: token) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import Fluent | ||
|
||
// public protocol User: Entity {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import Fluent | ||
|
||
public protocol Authorizable: Entity { | ||
|
||
} | ||
|
||
extension Authorizable { | ||
public func isAuthorized< | ||
PermissionType: Permission, | ||
PivotType: PivotProtocol & Entity | ||
>( | ||
to permission: PermissionType, | ||
withPivot: PivotType.Type = PivotType.self | ||
) throws -> Bool | ||
where | ||
PivotType.Left == Self, | ||
PivotType.Right == PermissionType | ||
{ | ||
guard let permission = try PermissionType.query().filter("key", permission.key).first() else { | ||
throw AuthorizationError.unknownPermission | ||
} | ||
|
||
return try PivotType.related(self, permission) | ||
} | ||
|
||
|
||
public func assertAuthorization< | ||
PermissionType: Permission, | ||
PivotType: PivotProtocol & Entity | ||
>( | ||
to permission: PermissionType, | ||
withPivot pivot: PivotType.Type = PivotType.self | ||
) throws | ||
where | ||
PivotType.Left == Self, | ||
PivotType.Right == PermissionType | ||
{ | ||
guard try isAuthorized(to: permission, withPivot: PivotType.self) else { | ||
throw AuthorizationError.notAuthorized | ||
} | ||
} | ||
} | ||
|
||
extension Authorizable { | ||
public func isAuthorized< | ||
PermissionType: Permission, | ||
MiddleType: Entity, | ||
PivotType: PivotProtocol & Entity | ||
>( | ||
to permission: PermissionType, | ||
_ item: MiddleType, | ||
withPivot: PivotType.Type = PivotType.self | ||
) throws -> Bool | ||
where | ||
PivotType.Left: PivotProtocol, | ||
PivotType.Left.Left == Self, | ||
PivotType.Left.Right == MiddleType, | ||
PivotType.Right == PermissionType | ||
{ | ||
guard let permission = try PermissionType.query().filter("key", permission.key).first() else { | ||
throw AuthorizationError.unknownPermission | ||
} | ||
|
||
return try PivotType.related(left: self, middle: item, right: permission) | ||
} | ||
|
||
public func assertAuthorization< | ||
PermissionType: Permission, | ||
MiddleType: Entity, | ||
PivotType: PivotProtocol & Entity | ||
>( | ||
to permission: PermissionType, | ||
_ item: MiddleType, | ||
withPivot pivot: PivotType.Type = PivotType.self | ||
) throws | ||
where | ||
PivotType.Left: PivotProtocol, | ||
PivotType.Left.Left == Self, | ||
PivotType.Left.Right == MiddleType, | ||
PivotType.Right == PermissionType | ||
{ | ||
guard try isAuthorized(to: permission, item, withPivot: PivotType.self) else { | ||
throw AuthorizationError.notAuthorized | ||
} | ||
} | ||
} |
Oops, something went wrong.