diff --git a/Package.swift b/Package.swift index 4b5f57c..d6bbb27 100755 --- a/Package.swift +++ b/Package.swift @@ -8,6 +8,8 @@ let package = Package( ], products: [ .library(name: "ImperialCore", targets: ["ImperialCore"]), + .library(name: "ImperialAuth0", targets: ["ImperialCore", "ImperialAuth0"]), + .library(name: "ImperialDiscord", targets: ["ImperialCore", "ImperialDiscord"]), .library(name: "ImperialDropbox", targets: ["ImperialCore", "ImperialDropbox"]), .library(name: "ImperialFacebook", targets: ["ImperialCore", "ImperialFacebook"]), .library(name: "ImperialGitHub", targets: ["ImperialCore", "ImperialGitHub"]), @@ -16,9 +18,10 @@ let package = Package( .library(name: "ImperialKeycloak", targets: ["ImperialCore", "ImperialKeycloak"]), .library(name: "ImperialMicrosoft", targets: ["ImperialCore", "ImperialMicrosoft"]), .library(name: "ImperialShopify", targets: ["ImperialCore", "ImperialShopify"]), - .library(name: "ImperialDiscord", targets: ["ImperialCore", "ImperialDiscord"]), .library(name: "Imperial", targets: [ "ImperialCore", + "ImperialAuth0", + "ImperialDiscord", "ImperialDropbox", "ImperialFacebook", "ImperialGitHub", @@ -26,8 +29,7 @@ let package = Package( "ImperialGoogle", "ImperialKeycloak", "ImperialMicrosoft", - "ImperialShopify", - "ImperialDiscord" + "ImperialShopify" ]), ], dependencies: [ @@ -42,6 +44,8 @@ let package = Package( .product(name: "JWTKit", package: "jwt-kit"), ] ), + .target(name: "ImperialAuth0", dependencies: ["ImperialCore"]), + .target(name: "ImperialDiscord", dependencies: ["ImperialCore"]), .target(name: "ImperialDropbox", dependencies: ["ImperialCore"]), .target(name: "ImperialFacebook", dependencies: ["ImperialCore"]), .target(name: "ImperialGitHub", dependencies: ["ImperialCore"]), @@ -50,7 +54,6 @@ let package = Package( .target(name: "ImperialKeycloak", dependencies: ["ImperialCore"]), .target(name: "ImperialMicrosoft", dependencies: ["ImperialCore"]), .target(name: "ImperialShopify", dependencies: ["ImperialCore"]), - .target(name: "ImperialDiscord", dependencies: ["ImperialCore"]), .testTarget(name: "ImperialTests", dependencies: ["ImperialCore", "ImperialShopify"]), ] ) diff --git a/Sources/Imperial/Services/Auth0/Auth0Router.swift b/Sources/Imperial/Services/Auth0/Auth0Router.swift deleted file mode 100644 index a10eac9..0000000 --- a/Sources/Imperial/Services/Auth0/Auth0Router.swift +++ /dev/null @@ -1,86 +0,0 @@ -import Vapor -import Foundation - -public class Auth0Router: FederatedServiceRouter { - public let baseURL: String - public let tokens: FederatedServiceTokens - public let callbackCompletion: (Request, String)throws -> (Future) - public var scope: [String] = [ ] - public var requiredScopes = [ "openid" ] - public let callbackURL: String - public let accessTokenURL: String - - private func providerUrl(path: String) -> String { - return self.baseURL.finished(with: "/") + path - } - - public required init(callback: String, completion: @escaping (Request, String)throws -> (Future)) throws { - let auth = try Auth0Auth() - self.tokens = auth - self.baseURL = "https://\(auth.domain)" - self.accessTokenURL = baseURL.finished(with: "/") + "oauth/token" - self.callbackURL = callback - self.callbackCompletion = completion - } - - public func authURL(_ request: Request) throws -> String { - let path="authorize" - - var params=[ - "response_type=code", - "client_id=\(self.tokens.clientID)", - "redirect_uri=\(self.callbackURL)", - ] - - let allScopes = self.scope + self.requiredScopes - let scopeString = allScopes.joined(separator: " ").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) - if let scopes = scopeString { - params += [ "scope=\(scopes)" ] - } - - let rtn = self.providerUrl(path: path + "?" + params.joined(separator: "&")) - return rtn - } - - public func fetchToken(from request: Request)throws -> Future { - let code: String - if let queryCode: String = try request.query.get(at: "code") { - code = queryCode - } else if let error: String = try request.query.get(at: "error") { - throw Abort(.badRequest, reason: error) - } else { - throw Abort(.badRequest, reason: "Missing 'code' key in URL query") - } - - let body = Auth0CallbackBody(clientId: self.tokens.clientID, - clientSecret: self.tokens.clientSecret, - code: code, - redirectURI: self.callbackURL) - - return try body.encode(using: request).flatMap(to: Response.self) { request in - guard let url = URL(string: self.accessTokenURL) else { - throw Abort(.internalServerError, reason: "Unable to convert String '\(self.accessTokenURL)' to URL") - } - request.http.method = .POST - request.http.url = url - request.http.contentType = .urlEncodedForm - - return try request.make(Client.self).send(request) - }.flatMap(to: String.self) { response in - return response.content.get(String.self, at: ["access_token"]) - } - } - - public func callback(_ request: Request)throws -> Future { - return try self.fetchToken(from: request).flatMap(to: ResponseEncodable.self) { accessToken in - let session = try request.session() - - session.setAccessToken(accessToken) - try session.set("access_token_service", to: OAuthService.auth0) - - return try self.callbackCompletion(request, accessToken) - }.flatMap(to: Response.self) { response in - return try response.encode(for: request) - } - } -} diff --git a/Sources/Imperial/Services/Auth0/Auth0.swift b/Sources/ImperialAuth0/Auth0.swift similarity index 64% rename from Sources/Imperial/Services/Auth0/Auth0.swift rename to Sources/ImperialAuth0/Auth0.swift index 9594211..b9c0794 100644 --- a/Sources/Imperial/Services/Auth0/Auth0.swift +++ b/Sources/ImperialAuth0/Auth0.swift @@ -1,3 +1,4 @@ +@_exported import ImperialCore import Vapor public class Auth0: FederatedService { @@ -6,18 +7,18 @@ public class Auth0: FederatedService { @discardableResult public required init( - router: Router, + routes: RoutesBuilder, authenticate: String, - authenticateCallback: ((Request)throws -> (Future))?, + authenticateCallback: ((Request) throws -> (EventLoopFuture))?, callback: String, scope: [String] = [], - completion: @escaping (Request, String)throws -> (Future) - )throws { + completion: @escaping (Request, String) throws -> (EventLoopFuture) + ) throws { self.router = try Auth0Router(callback: callback, completion: completion) self.tokens = self.router.tokens self.router.scope = scope - try self.router.configureRoutes(withAuthURL: authenticate, authenticateCallback: authenticateCallback, on: router) + try self.router.configureRoutes(withAuthURL: authenticate, authenticateCallback: authenticateCallback, on: routes) OAuthService.register(.auth0) } diff --git a/Sources/Imperial/Services/Auth0/Auth0Auth.swift b/Sources/ImperialAuth0/Auth0Auth.swift similarity index 100% rename from Sources/Imperial/Services/Auth0/Auth0Auth.swift rename to Sources/ImperialAuth0/Auth0Auth.swift diff --git a/Sources/Imperial/Services/Auth0/Auth0CallbackBody.swift b/Sources/ImperialAuth0/Auth0CallbackBody.swift similarity index 86% rename from Sources/Imperial/Services/Auth0/Auth0CallbackBody.swift rename to Sources/ImperialAuth0/Auth0CallbackBody.swift index 616b243..a098c16 100644 --- a/Sources/Imperial/Services/Auth0/Auth0CallbackBody.swift +++ b/Sources/ImperialAuth0/Auth0CallbackBody.swift @@ -7,7 +7,7 @@ struct Auth0CallbackBody: Content { let redirectURI: String let grantType: String = "authorization_code" - static var defaultContentType: MediaType = .urlEncodedForm + static var defaultContentType: HTTPMediaType = .urlEncodedForm enum CodingKeys: String, CodingKey { case clientId = "client_id" diff --git a/Sources/ImperialAuth0/Auth0Router.swift b/Sources/ImperialAuth0/Auth0Router.swift new file mode 100644 index 0000000..d806408 --- /dev/null +++ b/Sources/ImperialAuth0/Auth0Router.swift @@ -0,0 +1,54 @@ +import Vapor +import Foundation + +public class Auth0Router: FederatedServiceRouter { + + public let baseURL: String + public let tokens: FederatedServiceTokens + public let callbackCompletion: (Request, String) throws -> (EventLoopFuture) + public var scope: [String] = [ ] + public var requiredScopes = [ "openid" ] + public let callbackURL: String + public let accessTokenURL: String + public var service: OAuthService = .auth0 + public let callbackHeaders = HTTPHeaders([("Content-Type", "application/x-www-form-urlencoded")]) + + private func providerUrl(path: String) -> String { + return self.baseURL.finished(with: "/") + path + } + + public required init(callback: String, completion: @escaping (Request, String) throws -> (EventLoopFuture)) throws { + let auth = try Auth0Auth() + self.tokens = auth + self.baseURL = "https://\(auth.domain)" + self.accessTokenURL = baseURL.finished(with: "/") + "oauth/token" + self.callbackURL = callback + self.callbackCompletion = completion + } + + public func authURL(_ request: Request) throws -> String { + let path="authorize" + + var params=[ + "response_type=code", + "client_id=\(self.tokens.clientID)", + "redirect_uri=\(self.callbackURL)", + ] + + let allScopes = self.scope + self.requiredScopes + let scopeString = allScopes.joined(separator: " ").addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) + if let scopes = scopeString { + params += [ "scope=\(scopes)" ] + } + + let rtn = self.providerUrl(path: path + "?" + params.joined(separator: "&")) + return rtn + } + + public func callbackBody(with code: String) -> ResponseEncodable { + Auth0CallbackBody(clientId: self.tokens.clientID, + clientSecret: self.tokens.clientSecret, + code: code, + redirectURI: self.callbackURL) + } +} diff --git a/Sources/Imperial/Services/Auth0/Service+Auth0.swift b/Sources/ImperialAuth0/Service+Auth0.swift similarity index 61% rename from Sources/Imperial/Services/Auth0/Service+Auth0.swift rename to Sources/ImperialAuth0/Service+Auth0.swift index acabc24..9f3fa04 100644 --- a/Sources/Imperial/Services/Auth0/Service+Auth0.swift +++ b/Sources/ImperialAuth0/Service+Auth0.swift @@ -1,5 +1,5 @@ extension OAuthService { - public static let auth0 = OAuthService.init( + public static let auth0 = OAuthService( name: "auth0", endpoints: [:] ) diff --git a/docs/Auth0/README.md b/docs/Auth0/README.md index 0d166d1..05e5c7d 100644 --- a/docs/Auth0/README.md +++ b/docs/Auth0/README.md @@ -43,8 +43,9 @@ This provides you with an OAuth Client ID and secret you can provide to Imperial ## Imperial Integration -You can use Auth0 with the `ImperialAuth-` package. This expects two environment variables: +You can use Auth0 with the `ImperialAuth0` package. This expects three environment variables: +* `AUTH0_DOMAIN` * `AUTH0_CLIENT_ID` * `AUTH0_CLIENT_SECRET` diff --git a/docs/README.md b/docs/README.md index f0b4649..68d9fec 100644 --- a/docs/README.md +++ b/docs/README.md @@ -86,3 +86,4 @@ Below are links to the documentation to setup federated login with various OAuth - [Facebook](https://github.com/vapor-community/Imperial/tree/main/docs/Facebook/README.md) - [Keycloak](https://github.com/vapor-community/Imperial/tree/main/docs/Keycloak/README.md) - [Discord](https://github.com/vapor-community/Imperial/tree/main/docs/Discord/README.md) +- [Auth0](https://github.com/vapor-community/Imperial/tree/main/docs/Auth0/README.md)