Skip to content
This repository has been archived by the owner on Sep 7, 2021. It is now read-only.

Feature/modifier rework #35

Merged
merged 8 commits into from
May 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 0 additions & 95 deletions Sources/Corvus/Endpoints/CRUD.swift

This file was deleted.

5 changes: 4 additions & 1 deletion Sources/Corvus/Endpoints/Custom.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import Fluent
/// A class that contains custom functionality passed in by the implementor for
/// a generic type `T conforming to `ResponseEncodable` grouped under a given
/// path.
public final class Custom<R: ResponseEncodable>: RestEndpoint {
public final class Custom<R: CorvusModel>: RestEndpoint {

/// The return value of the `.handler()`.
public typealias Element = R

/// The type the Component operates on.
public typealias QuerySubject = R

/// The path to the component, can be used for route parameters.
public let pathComponents: [PathComponent]
Expand Down
7 changes: 4 additions & 3 deletions Sources/Corvus/Endpoints/Delete/Delete.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ public final class Delete<T: CorvusModel>: AuthEndpoint {

/// Initializes the component with a given path parameter.
///
/// - Parameter id: A `PathComponent` which represents the ID of the item.
/// - Parameter softDelete: Whether deletion should include soft deleted
/// items or not.
/// - Parameters:
/// - id: A `PathComponent` which represents the ID of the item.
/// - softDelete: Whether deletion should include soft deleted
/// items or not.
public init(_ id: PathComponent, softDelete: Bool = false) {
self.id = id
self.useSoftDelete = softDelete
Expand Down
50 changes: 16 additions & 34 deletions Sources/Corvus/Endpoints/Modifiers/Auth/AuthModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,20 @@ import Fluent

/// A class that wraps a component which utilizes an `.auth()` modifier. That
/// allows Corvus to chain modifiers, as it gets treated as any other struct
/// conforming to `AuthEndpoint`. Requires an object `T` that represents the
/// conforming to `AuthEndpoint`. Requires an object `U` that represents the
/// user to authorize.
public final class AuthModifier<
public class AuthModifier<
A: AuthEndpoint,
T: CorvusModelAuthenticatable>:
AuthEndpoint, RestEndpointModifier {
U: CorvusModelAuthenticatable>:
RestModifier<A>, AuthEndpoint {

/// The return type for the `.handler()` modifier.
public typealias Element = A.Element

/// The return value of the `.query()`, so the type being operated on in
/// the current component.
public typealias QuerySubject = A.QuerySubject

/// The `KeyPath` to the user property of the `QuerySubject` which is to be
/// authenticated.
public typealias UserKeyPath = KeyPath<
A.QuerySubject,
A.QuerySubject.Parent<T>
QuerySubject,
QuerySubject.Parent<U>
>

/// The `ReadEndpoint` the `.auth()` modifier is attached to.
public let modifiedEndpoint: A

/// The path to the property to authenticate for.
public let userKeyPath: UserKeyPath

Expand All @@ -40,29 +30,21 @@ AuthEndpoint, RestEndpointModifier {
/// - user: A `KeyPath` which leads to the property to authenticate for.
/// - operationType: The HTTP method of the wrapped component.
public init(_ authEndpoint: A, user: UserKeyPath) {
self.modifiedEndpoint = authEndpoint
self.userKeyPath = user
super.init(authEndpoint)
}

/// Returns the `queryEndpoint`'s query.
///
/// - Parameter req: An incoming `Request`.
/// - Returns: A `QueryBuilder`, which represents a `Fluent` query defined
/// by the `queryEndpoint`.
/// - Throws: An `Abort` error if the item is not found.
public func query(_ req: Request) throws -> QueryBuilder<QuerySubject> {
try modifiedEndpoint.query(req)
}

/// A method which checks if the user `T` supplied in the `Request` is
/// A method which checks if the user `U` supplied in the `Request` is
/// equal to the user belonging to the particular `QuerySubject`.
///
/// - Parameter req: An incoming `Request`.
/// - Returns: An `EventLoopFuture` containing an eagerloaded value as
/// defined by `Element`. If authentication fails or a user is not found,
/// HTTP `.unauthorized` and `.notFound` are thrown respectively.
/// - Throws: An `Abort` error if an item is not found.
public func handler(_ req: Request) throws -> EventLoopFuture<Element> {
override public func handler(_ req: Request)
throws -> EventLoopFuture<Element>
{
let users = try query(req)
.with(userKeyPath)
.all()
Expand All @@ -76,7 +58,7 @@ AuthEndpoint, RestEndpointModifier {
throw Abort(.notFound)
}

guard let authorized = req.auth.get(T.self) else {
guard let authorized = req.auth.get(U.self) else {
throw Abort(.unauthorized)
}

Expand All @@ -102,14 +84,14 @@ AuthEndpoint, RestEndpointModifier {
extension AuthEndpoint {

/// A modifier used to make sure components only authorize requests where
/// the supplied user `T` is actually related to the `QuerySubject`.
/// the supplied user `U` is actually related to the `QuerySubject`.
///
/// - Parameter user: A `KeyPath` to the related user property.
/// - Returns: An instance of a `AuthModifier` with the supplied `KeyPath`
/// to the user.
public func auth<T: CorvusModelAuthenticatable>(
_ user: AuthModifier<Self, T>.UserKeyPath
) -> AuthModifier<Self, T> {
public func auth<U: CorvusModelAuthenticatable>(
_ user: AuthModifier<Self, U>.UserKeyPath
) -> AuthModifier<Self, U> {
AuthModifier(self, user: user)
}
}
68 changes: 13 additions & 55 deletions Sources/Corvus/Endpoints/Modifiers/Auth/CreateAuthModifier.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,70 +3,28 @@ import Fluent

/// A class that wraps a `Create` component which utilizes an `.auth()`
/// modifier. That allows Corvus to chain modifiers, as it gets treated as any
/// other struct conforming to `CrateAuthEndpoint`. Requires an object `T` that
/// other struct conforming to `CreateAuthModifier`. Requires an object `U` that
/// represents the user to authorize.
public final class CreateAuthModifier<
A: CreateEndpoint,
T: CorvusModelAuthenticatable>:
CreateEndpoint, RestEndpointModifier {
U: CorvusModelAuthenticatable>:
AuthModifier<A, U>, CreateEndpoint {

/// The return type for the `.handler()` modifier.
public typealias Element = A.Element

/// The return value of the `.query()`, so the type being operated on in
/// the current component.
public typealias QuerySubject = A.QuerySubject

/// The `KeyPath` to the user property of the `QuerySubject` which is to be
/// authenticated.
public typealias UserKeyPath = KeyPath<
A.QuerySubject,
A.QuerySubject.Parent<T>
>

/// The `ReadEndpoint` the `.auth()` modifier is attached to.
public let modifiedEndpoint: A

/// The path to the property to authenticate for.
public let userKeyPath: UserKeyPath

/// Initializes the modifier with its underlying `QueryEndpoint` and its
/// `auth` path, which is the keypath to the property to run authentication
/// for.
///
/// - Parameters:
/// - queryEndpoint: The `QueryEndpoint` which the modifer is attached
/// to.
/// - user: A `KeyPath` which leads to the property to authenticate for.
/// - operationType: The HTTP method of the wrapped component.
public init(_ authEndpoint: A, user: UserKeyPath) {
self.modifiedEndpoint = authEndpoint
self.userKeyPath = user
}

/// Returns the `queryEndpoint`'s query.
///
/// - Parameter req: An incoming `Request`.
/// - Returns: A `QueryBuilder`, which represents a `Fluent` query defined
/// by the `queryEndpoint`.
/// - Throws: An `Abort` error if the item is not found.
public func query(_ req: Request) throws -> QueryBuilder<QuerySubject> {
try modifiedEndpoint.query(req)
}

/// A method which checks if the user `T` supplied in the `Request` is
/// A method which checks if the user `U` supplied in the `Request` is
/// equal to the user belonging to the particular `QuerySubject`.
///
/// - Parameter req: An incoming `Request`.
/// - Returns: An `EventLoopFuture` containing an eagerloaded value as
/// defined by `Element`. If authentication fails or a user is not found,
/// HTTP `.unauthorized` and `.notFound` are thrown respectively.
/// - Throws: An `Abort` error if an item is not found.
public func handler(_ req: Request) throws -> EventLoopFuture<Element> {
override public func handler(_ req: Request)
throws -> EventLoopFuture<Element>
{
let requestContent = try req.content.decode(A.QuerySubject.self)
let requestUser = requestContent[keyPath: self.userKeyPath]

guard let authorized = req.auth.get(T.self) else {
guard let authorized = req.auth.get(U.self) else {
throw Abort(.unauthorized)
}

Expand All @@ -79,18 +37,18 @@ CreateEndpoint, RestEndpointModifier {
}

/// An extension that adds the `.auth()` modifier to components conforming to
/// `CreateAuthEndpoint`.
/// `CreateEndpoint`.
extension CreateEndpoint {

/// A modifier used to make sure components only authorize requests where
/// the supplied user `T` is actually related to the `QuerySubject`.
/// the supplied user `U` is actually related to the `QuerySubject`.
///
/// - Parameter user: A `KeyPath` to the related user property.
/// - Returns: An instance of a `CreateAuthModifier` with the supplied
/// `KeyPath` to the user.
public func auth<T: CorvusModelAuthenticatable>(
_ user: CreateAuthModifier<Self, T>.UserKeyPath
) -> CreateAuthModifier<Self, T> {
public func auth<U: CorvusModelAuthenticatable>(
_ user: CreateAuthModifier<Self, U>.UserKeyPath
) -> CreateAuthModifier<Self, U> {
CreateAuthModifier(self, user: user)
}
}
Loading