Giter VIP home page Giter VIP logo

auth's Introduction

Vapor

Documentation Team Chat MIT License Continuous Integration Code Coverage Swift 5.7+ Mastodon


Vapor is an HTTP web framework for Swift. It provides a beautifully expressive and easy-to-use foundation for your next website, API, or cloud project.

Take a look at some of the awesome stuff created with Vapor.

💧 Community

Join the welcoming community of fellow Vapor developers on Discord.

🚀 Contributing

To contribute a feature or idea to Vapor, create an issue explaining your idea or bring it up on Discord.

If you find a bug, please create an issue.

If you find a security vulnerability, please contact [email protected] as soon as possible.

💛 Sponsors

Support Vapor's development by becoming a sponsor.

Broken Hands Emerge Tools Jari Donut Dane MacStadium

💚 Backers

Support Vapor's development by becoming a backer.

Moritz LangMaarten EngelsThomas KrajacicJesse TiptonSteve HumeMikkel UlstrupGeoffrey FosterPaul SchmiedmayerScott RobbinsSven A. SchmidtSpencer CurtisZach RausnitzTim „Timinator“ KretzschmarKlaasAndrew Edwards+Li, Inc.Stijn WillemsKyle NewsomeVia Aurelia SolutionsJakub KiermaszBrian DrellingMattes MohrJamieGalen RhodesLitmapsDavid RomanBrian StrobachKishikawa KatsumiAlex SherbakovSidetrackGreg KarpatiFrantišek MikšJeremy GreenwoodRay FixMićo MiloložaAlanJonas SannewaldTapEnvy.us, LLCJawadPARAIPAN SORINKalyn DavisYR ChenAarón Martínez Cuevas

auth's People

Contributors

0xtim avatar anthonycastelli avatar cardoso avatar ericwvgg avatar ezfe avatar gperdomor avatar jseibert avatar ksmandersen avatar mattpolzin avatar mpklu avatar rausnitz avatar siemensikkema avatar sorix avatar tanner0101 avatar vzsg avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

auth's Issues

Conforming Fluent models to BasicAuthenticable when username/password types are optional (i.e. String?)

Consider the scenario where not all users have username/passwords and thus those properties are optional.

Currently it's not possible to conform the below model to the BasicAuthenticable protocol because you need to supply WritableKeyPath<User, String> keys, not WritableKeyPath<User, String?>.

final class User : MySQLModel {
    var id: Int?
    var username: String?
    var passwordHash: String?
}

What do you think about allowing username /password fields of type WritableKeyPath<User, String?> to also conform to BasicAuthenticable?

I think this can be pulled off like so:

public protocol BasicAuthenticatableKeyType { }
extension String : BasicAuthenticatableKeyType {}
extension Optional : BasicAuthenticatableKeyType where Wrapped == String {}

/// Authenticatable by `Basic username:password` auth.
public protocol BasicAuthenticatable: Authenticatable {
    associatedtype UsernameKeyType: BasicAuthenticatableKeyType
    associatedtype PasswordKeyType: BasicAuthenticatableKeyType
    
    /// Key path to the username
    typealias UsernameKey = WritableKeyPath<Self, UsernameKeyType>
    
    /// The key under which the user's username,
    /// email, or other identifing value is stored.
    static var usernameKey: UsernameKey { get }
    
    /// Key path to the password
    typealias PasswordKey = WritableKeyPath<Self, PasswordKeyType>
    
    /// The key under which the user's password
    /// is stored.
    static var passwordKey: PasswordKey { get }
    
    /// Authenticates using the supplied credentials, connection, and verifier.
    static func authenticate(using basic: BasicAuthorization, verifier: PasswordVerifier, on connection: DatabaseConnectable) -> Future<Self?>
    
    var basicPassword: String? { get }
}

extension BasicAuthenticatable  where PasswordKeyType == String {
    public var basicPassword: String? { return self[keyPath: Self.passwordKey] }
}
extension BasicAuthenticatable  where PasswordKeyType == String? {
    public var basicPassword: String? { return self[keyPath: Self.passwordKey] }
}

extension BasicAuthenticatable where Self: Model, Self.Database: QuerySupporting {
    public static func authenticate(using basic: BasicAuthorization, verifier: PasswordVerifier, on conn: DatabaseConnectable) -> Future<Self?> {
        do {
            let filter = ModelFilter<Self>(filter: try QueryFilter(field: usernameKey.makeQueryField(), type: QueryFilterType<Self.Database>.equals, value: .data(basic.username)))
            return Self.query(on: conn).filter(filter).first().map(to: Self?.self) { user in
                guard let user = user, let basicPassword = user.basicPassword, try verifier.verify(basic.password, created: basicPassword) else {
                    return nil
                }
                return user
            }
        } catch {
            return conn.eventLoop.newFailedFuture(error: error)
        }
    }
}


Would it be possible to avoid depending on the Vapor package?

I noticed that my own code is actually only depending on several auxiliary modules of the Vapor family, but not the main Vapor module itself. In the spirit of having minimal dependencies, I'm now trying to avoid depending on the Vapor module altogether, but I am using the Auth module.

As far as I can tell, the only import Vapor statements are in Exports.swift and a test. Would it therefore be possible to avoid having to include the whole Vapor repo in this project's dependencies?

consider including JWT by default

Providing a JWT auth middleware by default could be a nice addition to this package. Vapor's JWT package would be a lightweight dep since Auth already relies on Crypto.

final class JWTAuthenticationMiddleware<U>: Middleware where U: Authenticatable & JWTPayload {
    let signer: JWTSigner

    init(_ type: U.Type, signer: JWTSigner) {
        self.signer = signer
    }
    
    /// See `Middleware`.
    func respond(to req: Request, chainingTo next: Responder) throws -> EventLoopFuture<Response> {
        // fetches the token from `Authorization: Bearer <token>` header
        guard let bearer = req.http.headers.bearerAuthorization else {
            // no authorization header, pass along un-authenticated request
            return try next.respond(to: req)
        }
        
        // parse JWT from token string, using configured signer
        let jwt = try JWT<U>(from: bearer.token, verifiedUsing: signer)
        try req.authenticate(jwt.payload)
        
        // pass along authenticated request
        return try next.respond(to: req)
    }
}

`AuthenticationSessionsMiddleware` should use a connection pool

At the moment, AuthenticationSessionsMiddleware calls A.authenticate(sessionID: aID, on: req), which reserves a database connection for the lifetime of the request. For long-running requests that only use pooled database connections elsewhere, this can cause DB connection count bottlenecks with e.g. Postgres. This problem is exacerbated by the fact that AuthenticationSessionsMiddleware might run on every request. (Postgres has very strict connection limits by default.)

In fact, the following has happened to me in production:

My database connection pool size is 3. I send three concurrent requests to the server. The middleware flow is like this:

  1. SessionsMiddleware is invoked and in turn calls DatabaseKeyedCache.get, which uses a connection pool.
  2. AuthenticationSessionsMiddleware authenticates the user and reserves a database connection for the lifetime of the request.
  3. After the request has finished, SessionsMiddleware calls DatabaseKeyedCache.set in addCookies, again using a connection pool.

Now, if all three database connections are taken by AuthenticationSessionsMiddleware after step 2, requesting another connection from the pool will block (because the pool has been exhausted by the cached connections), resulting in a deadlock on all three requests. Especially if DB calls have non-negligible latency (e.g. 50-100ms) due to the DB and the Vapor server not running on the same machine, this can be problematic, even with slightly higher connection pool sizes.

To solve this, I would suggest using a pooled connection for authenticating the user. This might make changes to SessionAuthenticatable necessary, though.

Using token and session auth should not run both

When using TokenAuthenticatable and SessionAuthenticatable both seem to be run on each request which just does more queries and so is not ideal.

If middleware is set as User.tokenAuthMiddleware(), User.authSessionsMiddleware(), User.guardAuthMiddleware() and on a request, I only provide a bearer token and no cookies, I would expect only the tokenAuthMiddleware to run. Or if authSessionsMiddleware runs, that it exits early without doing any queries.

Using the Vapor 3 auth-template, then enabled sessions (and setup cache etc.), once user is logged in and a request is run, these are the logs:

SELECT * FROM "UserToken" WHERE ("UserToken"."expiresAt" IS NULL OR "UserToken"."expiresAt" > (?)) AND "UserToken"."string" = (?) LIMIT 1 OFFSET 0 [1559561285.008395, "bzmCb/FWPE2l/4ItxasXbw=="]
SELECT * FROM "User" WHERE "User"."id" = (?) LIMIT 1 OFFSET 0 [1]
SELECT * FROM "fluentcache" WHERE "fluentcache"."key" = (?) LIMIT 1 OFFSET 0 ["19OwkNmfnogOE6gwiUUqSA=="]
INSERT INTO "fluentcache" ("key", "data") VALUES (?, ?) ["19OwkNmfnogOE6gwiUUqSA==", 0x7b2264617461223a7b225f5573657253657373696f6e223a2231227d7d]

I wouldn't expect the 2 fluentcache queries since only a bearer token was given in the request. Also should be same for the opposite, if there is a vapor-session cookie, the token middleware should exit early and not perform any queries.

Would really appreciate a fix for Vapor 3 since Vapor 4 seems far off still (for production). Or some pointers on how I could contribute on this.

Route using SQLite model and SessionMiddleware timeouts when ran on ubuntu.

Description
When deploying project on ubuntu on a VPS, I ran into timeouts on a route that uses a Fluent SQLite Model.
The project is running fine when ran on Mac.

Environment

  • Ubuntu 16.04
  • Swift up to date
  • Vapor up to date
  • Dependencies up to date

Conditions (based on my observations)

  • Use SessionMiddleware
  • Use SQLiteCache for KeyedCache
  • Use SQLite Fluent Model
  • Use sessions from within a Future returned by fluent

Here is a zip containing a sample project (reduced from my project), in which the login post route is affected by the issue :
Workshop copie.zip

UserIDType typealias required

#39 introduced a bug where the typealias for UserIDType needs to be specified, despite the fact the UserType is specified. This should be able to be inferred

Unable to compile

When trying to compile, using the vapor build command I got this error.

Sources/AuthProvider/RedirectMiddleware.swift:10:30: error: use of undeclared type 'RedirectType'
public let redirectType: RedirectType

Sessions not persisted

Weird bug that has been reported and is reproducible in Vapor Cloud easily. When using the sessions middleware after authenticating a User the session is not persisted and the user does not appear to be logged in. This has been reported on the RW forums and on Slack. I haven't been able to reproduce locally at all, but have managed to reproduce in Vapor Cloud. If I had to guess it would be that the Session Cache isn't being shared across threads

Protected routes can still be hit after calling `unauthenticate(_:)`

Protected routes can still be hit after calling unauthenticate(_:)

Steps to reproduce

Configurate:

// ...
try services.register(AuthenticationProvider())
// ...
var migrations = MigrationConfig()
migrations.add(model: User.self, database: .sqlite)
migrations.add(model: Token.self, database: .sqlite)
// ...

Models:

final class User: SQLiteUUIDModel, Migration, Content, Parameter, BasicAuthenticatable, TokenAuthenticatable {
    var id: UUID?
    var username: String
    var password: String

    init(username: String, password: String) {
        self.username = username
        self.password = password
    }

    static let usernameKey: UsernameKey = \.username
    static let passwordKey: PasswordKey = \.password
}
final class Token: SQLiteUUIDModel, Migration, Content, Authentication.Token, BearerAuthenticatable {
    var id: UUID?
    var token: String
    var userID: User.ID

    init(token: String, userID: User.ID) {
        self.token = token
        self.userID = userID
    }

    typealias UserType = User
    static let userIDKey: UserIDKey = \.userID

    static let tokenKey: TokenKey = \.token

    static func prepare(on connection: SQLiteConnection) -> Future<Void> {
        return Database.create(self, on: connection) { builder in
            try addProperties(to: builder)
            try builder.addReference(from: \.userID, to: \User.id)
        }
    }

    static func generate(for user: User) throws -> Token {
        let random = try CryptoRandom().generateData(count: 16)
        return try Token(token: random.base64EncodedString(), userID: user.requireID())
    }
}

Controller:

func boot(router: Router) throws {
    let usersRoutes = router.grouped("users")

    usersRoutes.post(User.self, use: createHandler)

    let basicAuthMiddleware = User.basicAuthMiddleware(using: BCryptDigest())
    let basicAuthGroup = usersRoutes.grouped(basicAuthMiddleware)

    basicAuthGroup.post("login", use: loginHandler)

    let tokenAuthMiddleware = User.tokenAuthMiddleware()
    let tokenAuthGroup = usersRoutes.grouped(tokenAuthMiddleware)

    tokenAuthGroup.delete(use: logoutHandler)
}

func createHandler(_ req: Request, user: User) throws -> Future<User> {
    let hasher = try req.make(BCryptDigest.self)
    user.password = try hasher.hash(user.password)
    
    return user.save(on: req)
}

func loginHandler(_ request: Request) throws -> Future<Token> {
    let user = try request.requireAuthenticated(User.self)
    let token = try Token.generate(for: user)
    
    return token.save(on: request)
}

func logoutHandler(_ request: Request) throws -> HTTPStatus {
    guard try request.isAuthenticated(User.self) else { throw Abort(.unauthorized) }
    
    try request.unauthenticate(User.self)
    return .ok
}

Steps:

  1. Call POST /users
  2. Call POST /users/login
  3. Call DELETE /users
  4. Call DELETE /users

Expected behavior

401 Unauthorized

Actual behavior

200 OK

Environment

  • Vapor Framework version: 3.0.3
  • FluentSQLite Framework version: 3.0.0-rc.2.2
  • Authentication Framework version: 2.0.0-rc.4.1
  • Vapor Toolbox version: 3.1.7
  • OS version: macOS 10.3.5

Should AuthenticationCache be public?

Hi!

I'm trying to use TokenAuthenticatable for a user model, however, when running the app I'm getting this log:

[ ERROR ] ServiceError.make: No services are available for 'AuthenticationCache'. (Container.swift:112)
[ DEBUG ] Suggested fixes for ServiceError.make: Register a service for 'AuthenticationCache'. `services.register(AuthenticationCache.self) { ... }`. (Logger+LogError.swift:20)

After trying to register the AuthenticationCache service in my configuration I'm getting a Use of unresolver identifier error, because AuthenticationCache is not public:

/// Stores authenticated objects. This should be created
/// using the request container as a singleton. Authenticated
/// objects can then be stored here by middleware and fetched
/// later in route closures.
final class AuthenticationCache: Service { ... }

Should this class be public? or maybe I'm doing this wrong, I'm pretty new with vapor and I can't find many guides or references about this.

Thank you.

Auth Beta - no way to 'logout' with sessions

With the current beta, it is just missing an unauthenticate function to remove the authenticated user from the session to log the user out. Adding here as a reminder so I don't forget!

BasicAuthenticatable for non-final classes

How would we conform the class User to BasicAuthenticatable in the following case:

import Vapor
import FluentPostgreSQL
import Authentication

class User: Codable {
    var id: Int?
    
    var username: String
    var email: String
    var passwordHash: String
    
    init(username: String, email: String, passwordHash: String) {
        self.username = username
        self.email = email
        self.passwordHash = passwordHash
    }
}

final class Student: User {
    
    struct Public {
        let id: Int
        let email: String
        let username: String
        let nationality: String?
    }
    
    let nationality: String?
    
    enum CodingKeys: CodingKey {
        case nationality
    }
    
    init(username: String, email: String, passwordHash: String, nationality: String?) {
        self.nationality = nationality
        super.init(username: username, email: email, passwordHash: passwordHash)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: Student.CodingKeys.self)
        self.nationality = try container.decode(String.self, forKey: .nationality)
        try super.init(from: decoder)
    }
    
    override func encode(to encoder: Encoder) throws {
        try super.encode(to: encoder)
        var container = encoder.container(keyedBy: Student.CodingKeys.self)
        try container.encode(nationality, forKey: .nationality)
    }
}

extension Student.Public: Content {}
extension Student: PostgreSQLModel {}
extension Student: Content {}
extension Student: Parameter {}
extension Student: Migration {}

If I do the following:

extension User: BasicAuthenticatable {
    static let usernameKey: UsernameKey = \User.username
    static let passwordKey: PasswordKey = \User.passwordHash
}

I get the error, that BasicAuthenticatable can only be implemented by final-classes. How should I work around this? Thx in advance :)

Unable to remove sessions with AuthenticationSessionsMiddleware enabled

The problem

When using AuthenticationSessionsMiddleware it is not possible to delete sessions because of the way #52 has been fixed. I think I have found the problem, but I am not entirely sure. I don't think I am the first since this could be the same: vapor/vapor#1667 and vapor/vapor#1661.

I am also using Redis as sessions storage.

What happens?

When calling req.destroySession() and req.unauthenticate(User.self) the session and authenticated user in the cache are set to nil. This works as expected, however, when the AuthenticationSessionsMiddleware responds, it sees that the user is no longer authenticated, and calls req.unauthenticateSession(A.self) as implemented by #52. This in turns calls session() that sees the cache is set to nil and creates a new session.

For this reason, the SessionMiddleware(which responds after the auth session middleware) thinks we have created a new session, and stores that in the session storage. Not only does this mean that the previous session is not deleted, a new one is created, without any user context.

Potential fix

For the time being, I have been using a custom middleware that works like AuthenticationSessionsMiddleware but does not do any responding. Thus, the middleware never calls session() and I am able to correctly delete the session without a new one being created.

I have not created a PR, because I don't know how this should be handled appropriately. Because the middleware does not respond, you will have to call try req.authenticateSession(user) manually, and not req.authenticate(user) in your signin method.

Our temporary middleware looks something like:

final class UserSessionsMiddleware: Middleware {

    init() {}

    public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {
        if let id = try req.authenticatedSession(User.self) {
            // try to find user with id from session
            return User.authenticate(sessionID: id, on: req).flatMap { user in
                // if the user was found, auth it
                guard let user = user else {
                    throw SomeError
                }

                try req.authenticate(user)

                // return done
                return try next.respond(to: req)
            }
        } else {
            // no need to authenticate
            return try next.respond(to: req)
        }
    }
}

Request has no member 'hasSession'

did "swift package update"
swift 5, vapor toolbox 3.1.10

getting:

/private/tmp/.../.build/checkouts/auth/Sources/Authentication/Persist/SessionAuthenticatable.swift:44:19: error: value of type 'Request' has no member 'hasSession'; did you mean 'session'? guard try self.hasSession() else { ^~~~ ~~~~~~~~~~ session Vapor.Request:2:17: note: 'session' declared here public func session() throws -> Vapor.Session

Add multitype capability to TokenAuthenticationMiddleware

Currently, if we want to secure routes with a TokenAuthenticationMiddleware we can only specify a single TokenAuthenticatable type, which we require to be authenticated in order to execute the route.

In multi-role systems (ie. blog), we usually have multiple different user types (which do not necessarily share a common superclass). Therefore, we often want to secure routes with a middleware that would allow us to specify multiple different types where one of them has to be authenticated for the route to be executed.

After some discussion with @tanner0101, a possible solution could be a new "non-throwing" TokenAuthenticationMiddleware or BearerAuthenticationMiddleware, which would authenticate a specified user type if possible, otherwise just continue, instead of throwing. Having this "non-throwing" middleware, it would allow us to compose aka chain middlewares in order to achieve the "multitype authentication" capability. At the end of the chain, we would of course still need a throwing middleware that would check if one of the types has been authenticated otherwise finally throw with unauthorized.


I've been playing around with this a bit and here is the rough version of how I managed to achieve this.

NonThrowingBearerAuthenticationMiddleware

public final class NonThrowingBearerAuthenticationMiddleware<A>: Middleware where A: BearerAuthenticatable {

    public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {

        if try req.isAuthenticated(A.self) {
            return try next.respond(to: req)
        }

        guard let token = req.http.headers.bearerAuthorization else {
            // TODO: Throw AuthenticationError when initializer is exposed publicly
            // @tanner0101 is AuthenticationError intentionally hidden?
            return try next.respond(to: req)
        }

        // auth token on connection
        return A.authenticate(using: token, on: req).flatMap(to: Response.self) { a in
            guard let a = a else {
                return try next.respond(to: req)
            }
            // set authed on request
            try req.authenticate(a)
            return try next.respond(to: req)
        }
    }
}

extension BearerAuthenticatable {

    public static func nonThrowingBearerAuthenticationMiddleware(
        database: DatabaseIdentifier<Database>? = nil
        ) -> NonThrowingBearerAuthenticationMiddleware<Self> {
        return .init()
    }
}

ThrowIfNoneAuthenticatedMiddleware

public final class ThrowIfNoneAuthenticatedMiddleware: Middleware {

    public enum UserType {
        case admin
        case user
        case moderator
    }

    private let allowedUserTypes: [UserType]

    /// Create a new `ThrowIfNoneAuthenticatedMiddleware`
    public init(allowedUserTypes: [UserType]) {
        self.allowedUserTypes = allowedUserTypes
    }

    /// See Middleware.respond
    public func respond(to req: Request, chainingTo next: Responder) throws -> Future<Response> {

        for userType in allowedUserTypes {
            switch userType {
            case .admin:
                if try req.isAuthenticated(Admin.TokenType.self) {
                    return try next.respond(to: req)
                }
            case .user:
                if try req.isAuthenticated(User.TokenType.self) {
                    return try next.respond(to: req)
                }
            case .moderator:
                if try req.isAuthenticated(Moderator.TokenType.self) {
                    return try next.respond(to: req)
                }
            }
        }
        throw Abort(.unauthorized, reason: "Invalid credentials")
    }
}

Route

let routes = router.grouped("api", "posts")
            .grouped(Admin.TokenType.nonThrowingBearerAuthenticationMiddleware())
            .grouped(User.TokenType.nonThrowingBearerAuthenticationMiddleware())
            .grouped(ThrowIfNoneAuthenticatedMiddleware(allowedUserTypes: [.admin, .user]))

Above routes will be protected with bearer authentication, allowing only Admin and User types to access them and throwing unauthorized for everyone else.

Remove reliance on DatabaseKit/Fluent

It would be great to be able to use the auth package when implementing something like the repository pattern. In SteamPress for Vapor 3, there is no knowledge of Fluent (at least the inner package). All the models are held behind repositories accessible from services. This would be fine, except for the fact things like BasicAuthenticatable that all the web sessions stuff relies on use DatabaseConnection to get the user for verifying. Because it's done in the protocol there's no way to implement the extensions yourself but still conform, meaning you have to reimplement most of the auth package 😞

Would be great to be fixed with Vapor 4!

Auth 2 - Cookies not working in Safari Tech Preview

STP Release 50 - session cookies are not working correctly, after authentication if I try and access a protected route it redirects me back using the RedirectMiddleware. Works fine in Firefox, Chrome and normal Safari. Adding here to remind me to investigate further, either Vapor (unlikely since it works fine in others) or a STP bug that needs filing in a radar

[Beta] Crash in middleware: DB connection

Summary

I'm getting a crash when I use the middleware in the beta branch of Auth. Specifically when requesting a route which has been protected with basicAuthMiddleware or tokenAuthMiddleware.

Detail

Given a User model which is BasicAuthenticatable:

extension User: BasicAuthenticatable {
	static let usernameKey = \User.email
	static let passwordKey = \User.password
}

…And a route which uses the middleware:

let authMiddleware = try User.basicAuthMiddleware(using: PlaintextVerifier(), database: .psql)
let group = router.grouped(authMiddleware)
group.get("test"){ req -> String in
	return "OK"
}

…Requesting /test results in a crash when DatabaseKit tries to use a nil connection.

Results

DatabaseKit throws an exception in line 21 of /Connection/DatabaseConnection.swift:

extension DatabaseConnection {
    /// See `DatabaseConnectable.connect(to:)`
    public func connect<D>(to database: DatabaseIdentifier<D>?) -> Future<D.Connection> {
        assert(database == nil, "Unexpected \(#function): nil database identifier") // 🚨🚨🚨
        assert(self is D.Connection, "Unexpected \(#function): \(self) not \(D.Connection.self)")
        return Future(self as! D.Connection)
    }
}

🚨: codes.vapor.application (3): Assertion failed: Unexpected connect(to:): nil database identifier

Steps to reproduce

  • Download example project
  • Configure pqsl details in /App/Setup/Configure.swift
  • Run the project, POST to /api/v1/register with correct details (or use the Paw file, api-template.paw)
  • GET /api/v1/me with basic auth details (Get me with basic Auth in Paw)
    • The crash should happen during the request.

Missing expiration date from Bearer

I'm using the authentication middleware to check if a bearer token is valid and everything is working as expected, but I can't find a way to check for its "expires_in" parameter (that is not mandatory but recommended from the standard). Is it possible to add a customizable variable that will check against expiration_date and return a 401 if the token is not valid anymore? Or should I do this check somewhere else?

Can not generate Xcode project

Here is the Package.swift file

let package = Package(
    name: "Foo",
    dependencies: [
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.3"),
        .package(url: "https://github.com/vapor/fluent.git",from: "3.0.0-rc.2.4.1"),
        .package(url: "https://github.com/vapor/fluent-postgresql.git", from: "1.0.0-rc.2.3"),
        .package(url: "https://github.com/vapor/auth.git", from: "2.0.0-rc.4.1"),
        .package(url: "https://github.com/vapor/leaf.git", from: "3.0.0-rc.2.2"),
    ],
    targets: [
        .target(name: "App", dependencies: [
            "Vapor",
            "Fluent",
            "FluentPostgreSQL",
            "Auth",
            "Leaf",
            ]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

and the eror log

Generating Xcode Project [Failed]
'Auth' /Users/h172462/Desktop/Foo: error: product dependency 'Auth' not found
warning: dependency 'Auth' is not used by any target

Error: Could not generate Xcode project: 'Foo' /Users/h172462/Desktop/Foo: error: product dependency 'Auth' not found
warning: dependency 'Auth' is not used by any target

Add System for Authorization Checks

I've been wanting to add some permissions around my routes and I thought I'd write my thoughts down as as suggestions or ideas for future development. I'm new to Vapor and backend development, so please take this with a grain of salt.

Roles/Permissions

First, I thought it would be great to be able to add some ability to define permissions levels or roles in conjunction with the User model. It seems like the most flexible system would allow the author to define these levels on their own (These could be defined as either one-to-one or one-to-many). Alternatively, Vapor could provide preset permissions levels, but that seems like possible overkill. Presumably this could be built as a Protocol any model could conform to, but concrete implementations could be provided as well.

Authorizable Could be designed like Timestampable:

extension User: Authorizable {
    // Adds mapping to Authorizable key path properties
    static var permissionsKey: WritableKeyPath<User, [PermissionType]> { return \.roleLevel }
}

Rules/Authorization

Now, to add Authorization the routes you could add an extra function to the route chain like .authorize(using: .FooPolicy). This could be done with a closure or function that accepts arguments that could be used to define a true/false or pass/fail test. Laravel does something similar with their Authorization system.

If you start with a collection of routes like this:

let athleteRoutes = router.grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.post(use: create)
athleteRoutes.delete(Athlete.parameter, use: delete)

With Authorization the route group could end up looking like the following:

let athleteRoutes = router.authorize(using: athletePolicy).grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.post(use: create)
athleteRoutes.delete(Athlete.parameter, use: delete)

Or:

let athleteRoutes = router.grouped("api", "athletes")
athleteRoutes.get(use: index)
athleteRoutes.authorize(using: athleteEditPolicy).post(use: create)
athleteRoutes.authorize(using: athleteEditPolicy).delete(Athlete.parameter, use: delete)

Ideally, you could add this authorization component at both the individual route or route group level.

The authorization function could look something like this:

func athletePolicy(_ req: Request) throws -> Future<Bool> {
    return user.permissions.contains(.athleteEditor) // pseudocode for getting user permissions levels 
}

Or:

func athleteEditPolicy(_ req: Request) throws -> Future<Bool> {
    return try req.content.decode(Athlete.self).map(to: Bool.self) { athlete in 
        return athlete.ownerID == user.ID //pseudocode for getting user.ID
    }
}

In the event that the Authorization process fails, I'd be great if Vapor sent the appropriate HTTP error code (I think this would be a 403, but I could be wrong).

Auth doesn't finish build with vapor 4

I have the following testing Vapor App:

import PackageDescription

let package = Package(
    name: "VaporKirchenmusikServer",
    platforms: [
        .macOS(.v10_14)
    ],
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-beta"),
        .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-beta"),
        .package(url: "https://github.com/vapor/auth.git", .branch("master")),
        //.package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0-beta")
        .package(url: "https://github.com/vapor/fluent-sqlite-driver.git", from: "4.0.0-beta")
    ],
    targets: [
        .target(name: "App", dependencies: ["Fluent", "FluentSQLiteDriver", "Vapor", "OpenCrypto", "Auth"]),
        .target(name: "Run", dependencies: ["App"]),
        .testTarget(name: "AppTests", dependencies: ["App"])
    ]
)

vapor-beta build is working eternal on this.

Stable release?

Is stable release coming any time soon?
It is limiting us to Vapor2 for now.

Protected routes can still be hit after calling `unauthenticateSession(_:)`

Protected routes can still be hit after calling unauthenticateSession(_:).

It do set cache to nil, but only for current request, not globally.

Steps to reproduce

Configurate:

// ...

var middlewares = MiddlewareConfig()
middlewares.use(SessionsMiddleware.self)
services.register(middlewares)

// ...

var migrations = MigrationConfig()
migrations.add(model: User.self, database: .mysql)
services.register(migrations)

// ...

Model:

final class User: MySQLModel, Migration, PasswordAuthenticatable, SessionAuthenticatable {
    var email: String
    var password: String

    // ...

    static var usernameKey: UsernameKey = \User.email
    static var passwordKey: PasswordKey = \User.password
}

Controller:

let authSessionRoutes = router.grouped(User.authSessionsMiddleware())
authSessionRoutes.get("login", use: makeLoginView)
authSessionRoutes.post("login", use: login)

let protectedRoutes = authSessionRoutes.grouped(RedirectMiddleware<User>(path: "/login"))
protectedRoutes.get("logout", use: logout)
protectedRoutes.get("/", use: makeHomeView)

// ...

Route:

func login(_ req: Request) throws -> Future<Response> {

    // ...

    try req.authenticateSession(authed)
}

 func logout(_ req: Request) throws -> Response {
    try req.unauthenticateSession(User.self)
    return req.redirect(to: "/login")
}
  1. Call / route, and redirected to /login.
  2. Submit login form with correct user credentials.
  3. Call /logout route.
  4. Call / route.

Expected behavior

Redirected to /login.

Actual behavior

Hit protected / and not redirecting.

Environment

  • Vapor Framework version: 3.0.0-rc.2.2.4
  • Auth version: 2.0.0-rc.3.0.1
  • Leaf version: 3.0.0-rc.2.1.2
  • Vapor Toolbox version: 3.1.4
  • OS version: macOS 10.3.3

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.