From 024bb271b574205324ec6d2642b9227a2d9cdab4 Mon Sep 17 00:00:00 2001 From: Adam Fowler Date: Mon, 7 Dec 2020 12:38:47 +0000 Subject: [PATCH] Soto v5 (#18) * Use aws-sdk-swift-master branch of aws-cognito-authentication-kit * Add AWS prefix in exports.swift * Use soto-v5 branch * Remove unnecessary import * Update exports * Rename to SotoCognitoAuthentication * Replace AWS prefix with Soto * Remove Soto Prefix * Update README for change API * Use 2.0.0 of soto-cognito-authentication-kit * Rename to +Cognito --- Package.swift | 10 +++--- README.md | 33 +++++++++---------- .../AWSCognitoAuthentication/Exports.swift | 3 -- .../Application+Cognito.swift} | 12 +++---- .../Authenticator+Cognito.swift} | 23 +++++++------ .../SotoCognitoAuthentication/Exports.swift | 3 ++ .../Request+Cognito.swift} | 22 ++++++------- 7 files changed, 52 insertions(+), 54 deletions(-) delete mode 100644 Sources/AWSCognitoAuthentication/Exports.swift rename Sources/{AWSCognitoAuthentication/Application+AWSCognito.swift => SotoCognitoAuthentication/Application+Cognito.swift} (79%) rename Sources/{AWSCognitoAuthentication/Authenticator+AWSCognito.swift => SotoCognitoAuthentication/Authenticator+Cognito.swift} (65%) create mode 100644 Sources/SotoCognitoAuthentication/Exports.swift rename Sources/{AWSCognitoAuthentication/Request+AWSCognito.swift => SotoCognitoAuthentication/Request+Cognito.swift} (81%) diff --git a/Package.swift b/Package.swift index 7a35b5a..f37832f 100644 --- a/Package.swift +++ b/Package.swift @@ -4,18 +4,18 @@ import PackageDescription let package = Package( - name: "aws-cognito-authentication", + name: "soto-cognito-authentication", platforms: [.macOS(.v10_15)], products: [ - .library(name: "AWSCognitoAuthentication", targets: ["AWSCognitoAuthentication"]), + .library(name: "SotoCognitoAuthentication", targets: ["SotoCognitoAuthentication"]), ], dependencies: [ - .package(url: "https://github.com/adam-fowler/aws-cognito-authentication-kit.git", .upToNextMajor(from: "1.0.0")), + .package(url: "https://github.com/adam-fowler/soto-cognito-authentication-kit.git", from: "2.0.0"), .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "4.0.0")), ], targets: [ - .target(name: "AWSCognitoAuthentication", dependencies: [ - .product(name: "AWSCognitoAuthenticationKit", package: "aws-cognito-authentication-kit"), + .target(name: "SotoCognitoAuthentication", dependencies: [ + .product(name: "SotoCognitoAuthenticationKit", package: "soto-cognito-authentication-kit"), .product(name: "Vapor", package: "vapor") ]) ] diff --git a/README.md b/README.md index c9c39be..59c04f7 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,36 @@ -# AWS Cognito Authentication +# Soto Cognito Authentication [Swift 5.1](https://swift.org) -[](https://github.com/adam-fowler/aws-cognito-authentication/actions?query=workflow%3ASwift) +[](https://github.com/adam-fowler/soto-cognito-authentication/actions?query=workflow%3ASwift) -This is the Vapor wrapper for [AWS Cognito Authentication Kit](https://github.com/adam-fowler/aws-cognito-authentication-kit). It provides application storage for configurations and authentication calls on request. Documentation on AWS Cognito Authentication Kit can be found [here](https://github.com/adam-fowler/aws-cognito-authentication-kit/blob/main/README.md) +This is the Vapor wrapper for [Soto Cognito Authentication Kit](https://github.com/adam-fowler/soto-cognito-authentication-kit). It provides application storage for configurations and authentication calls on request. Documentation on Soto Cognito Authentication Kit can be found [here](https://github.com/adam-fowler/soto-cognito-authentication-kit/blob/main/README.md) # Using with Vapor ## Configuration -Store your AWSCognitoConfiguration on the Application object. In configure.swift add the following with your configuration details +Store your `CognitoConfiguration` on the Application object. In configure.swift add the following with your configuration details ```swift -let awsCognitoConfiguration = AWSCognitoConfiguration( +let awsCognitoConfiguration = CognitoConfiguration( userPoolId: String = "eu-west-1_userpoolid", clientId: String = "23432clientId234234", clientSecret: String = "1q9ln4m892j2cnsdapa0dalh9a3aakmpeugiaag8k3cacijlbkrp", - cognitoIDP: CognitoIdentityProvider = CognitoIdentityProvider(region: .euwest1), - region: Region = .euwest1 + cognitoIDP: CognitoIdentityProvider = CognitoIdentityProvider(region: .euwest1) ) -app.awsCognito.authenticatable = AWSCognitoAuthenticatable(configuration: awsCognitoConfiguration) +app.cognito.authenticatable = CognitoAuthenticatable(configuration: awsCognitoConfiguration) ``` The CognitoIdentity configuration can be setup in a similar way. ```swift -let awsCognitoIdentityConfiguration = AWSCognitoIdentityConfiguration( +let awsCognitoIdentityConfiguration = CognitoIdentityConfiguration( identityPoolId: String = "eu-west-1_identitypoolid" identityProvider: String = "provider" cognitoIdentity: CognitoIdentity = CognitoIdentity(region: .euwest1) ) -let app.awsCognito.identifiable = AWSCognitoIdentifiable(configuration: awsCognitoIdentityConfiguration) +let app.cognito.identifiable = CognitoIdentifiable(configuration: awsCognitoIdentityConfiguration) ``` ## Accessing functionality -Functions like `createUser`, `signUp`, `authenticate` with username and password and `responseToChallenge` are all accessed through `request.application.awsCognito.authenticatable`. Extend `AWSCognitoAuthenticateResponse` to conform to `Content` and the following login route will return the full response from `AWSCognitoAuthenticable.authenticate`. +Functions like `createUser`, `signUp`, `authenticate` with username and password and `responseToChallenge` are all accessed through `request.application.cognito.authenticatable`. The following login route will return the full response from `CognitoAuthenticable.authenticate`. ```swift - func login(_ req: Request) throws -> EventLoopFuture { + func login(_ req: Request) throws -> EventLoopFuture { let user = try req.content.decode(User.self) - return req.application.awsCognito.authenticatable.authenticate( + return req.application.cognito.authenticatable.authenticate( username: user.username, password: user.password, context: req, @@ -41,7 +40,7 @@ Functions like `createUser`, `signUp`, `authenticate` with username and password If id, access or refresh tokens are provided in the 'Authorization' header as Bearer tokens the following functions in Request can be used to verify them `authenticate(idToken:)`, `authenticate(accessToken:)`, `refresh`. as in the following ```swift func authenticateAccess(_ req: Request) throws -> Future<> { - req.awsCognito.authenticateAccess().flatMap { _ in + req.cognito.authenticateAccess().flatMap { _ in ... } } @@ -49,7 +48,7 @@ func authenticateAccess(_ req: Request) throws -> Future<> { ## Authenticators -Three authenticators are available. See the [Vapor docs](https://docs.vapor.codes/4.0/authentication) for more details on authentication in Vapor.`AWSCognitoBasicAuthenticator` will do username, password authentication and returns a `AWSCognitoAuthenticateResponse`. `AWSCognitoAccessAuthenticator` will do access token authentication and returns an `AWSCognitoAccessToken` which holds all the information that could be extracted from the access token. `AWSCognitoIdAuthenticator` does id token authentication and extracts information from the id token into your own `Payload` type. The standard list of claims that can be found in an id token are detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). Your `Payload` type needs to decode using these tags, the username tag "cognito:username" and any custom tags you may have setup for the user pool. Below is an example of using the id token authenticator. +Three authenticators are available. See the [Vapor docs](https://docs.vapor.codes/4.0/authentication) for more details on authentication in Vapor.`CognitoBasicAuthenticator` will do username, password authentication and returns a `CognitoAuthenticateResponse`. `CognitoAccessAuthenticator` will do access token authentication and returns an `CognitoAccessToken` which holds all the information that could be extracted from the access token. `CognitoIdAuthenticator` does id token authentication and extracts information from the id token into your own `Payload` type. The standard list of claims that can be found in an id token are detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims). Your `Payload` type needs to decode using these tags, the username tag "cognito:username" and any custom tags you may have setup for the user pool. Below is an example of using the id token authenticator. First create a User type to store your id token payload in. ```swift @@ -63,9 +62,9 @@ struct User: Content & Authenticatable { } } ``` -Add a route using the authenticator. The `AWSCognitoIdAuthenticator` authenticates the request, the `guardMiddleware` ensures the user if authenticated. The actual function accesses the `User` type via `req.auth.require`. +Add a route using the authenticator. The `CognitoIdAuthenticator` authenticates the request, the `guardMiddleware` ensures the user if authenticated. The actual function accesses the `User` type via `req.auth.require`. ```swift -app.grouped(AWSCognitoIdAuthenticator()) +app.grouped(CognitoIdAuthenticator()) .grouped(User.guardMiddleware()) .get("user") { (req) throws -> EventLoopFuture in let user = try req.auth.require(User.self) diff --git a/Sources/AWSCognitoAuthentication/Exports.swift b/Sources/AWSCognitoAuthentication/Exports.swift deleted file mode 100644 index a7ae4a7..0000000 --- a/Sources/AWSCognitoAuthentication/Exports.swift +++ /dev/null @@ -1,3 +0,0 @@ -@_exported import AWSCognitoAuthenticationKit -@_exported import CognitoIdentity -@_exported import CognitoIdentityProvider diff --git a/Sources/AWSCognitoAuthentication/Application+AWSCognito.swift b/Sources/SotoCognitoAuthentication/Application+Cognito.swift similarity index 79% rename from Sources/AWSCognitoAuthentication/Application+AWSCognito.swift rename to Sources/SotoCognitoAuthentication/Application+Cognito.swift index 739d635..28fac3f 100644 --- a/Sources/AWSCognitoAuthentication/Application+AWSCognito.swift +++ b/Sources/SotoCognitoAuthentication/Application+Cognito.swift @@ -2,16 +2,16 @@ import Vapor public extension Application { - var awsCognito: AWSCognito { + var cognito: SotoCognito { .init(application: self) } - struct AWSCognito { + struct SotoCognito { struct AuthenticatableKey: StorageKey { - typealias Value = AWSCognitoAuthenticatable + typealias Value = CognitoAuthenticatable } - public var authenticatable: AWSCognitoAuthenticatable { + public var authenticatable: CognitoAuthenticatable { get { guard let authenticatable = self.application.storage[AuthenticatableKey.self] else { fatalError("AWSCognito authenticatable not setup. Use application.awsCognito.authenticatable = ...") @@ -24,10 +24,10 @@ public extension Application { } struct IdentifiableKey: StorageKey { - typealias Value = AWSCognitoIdentifiable + typealias Value = CognitoIdentifiable } - public var identifiable: AWSCognitoIdentifiable { + public var identifiable: CognitoIdentifiable { get { guard let identifiable = self.application.storage[IdentifiableKey.self] else { fatalError("AWSCognito identifiable not setup. Use application.awsCognito.identifiable = ...") diff --git a/Sources/AWSCognitoAuthentication/Authenticator+AWSCognito.swift b/Sources/SotoCognitoAuthentication/Authenticator+Cognito.swift similarity index 65% rename from Sources/AWSCognitoAuthentication/Authenticator+AWSCognito.swift rename to Sources/SotoCognitoAuthentication/Authenticator+Cognito.swift index 0bb34fa..83bab3b 100644 --- a/Sources/AWSCognitoAuthentication/Authenticator+AWSCognito.swift +++ b/Sources/SotoCognitoAuthentication/Authenticator+Cognito.swift @@ -1,21 +1,20 @@ -import AWSCognitoAuthenticationKit -import AWSSDKSwiftCore +import SotoCognitoAuthenticationKit import NIO import Vapor -extension AWSCognitoAuthenticateResponse: Authenticatable {} -extension AWSCognitoAccessToken: Authenticatable {} +extension CognitoAuthenticateResponse: Authenticatable {} +extension CognitoAccessToken: Authenticatable {} -public typealias AWSCognitoBasicAuthenticatable = AWSCognitoAuthenticateResponse -public typealias AWSCognitoAccessAuthenticatable = AWSCognitoAccessToken +public typealias CognitoBasicAuthenticatable = CognitoAuthenticateResponse +public typealias CognitoAccessAuthenticatable = CognitoAccessToken /// Authenticator for Cognito username and password -public struct AWSCognitoBasicAuthenticator: BasicAuthenticator { +public struct CognitoBasicAuthenticator: BasicAuthenticator { public init() {} public func authenticate(basic: BasicAuthorization, for request: Request) -> EventLoopFuture { - return request.application.awsCognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request, on:request.eventLoop).map { token in + return request.application.cognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request, on:request.eventLoop).map { token in request.auth.login(token) }.flatMapErrorThrowing { error in switch error { @@ -30,12 +29,12 @@ public struct AWSCognitoBasicAuthenticator: BasicAuthenticator { } /// Authenticator for Cognito access tokens -public struct AWSCognitoAccessAuthenticator: BearerAuthenticator { +public struct CognitoAccessAuthenticator: BearerAuthenticator { public init() {} public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture { - return request.application.awsCognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop).map { token in + return request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop).map { token in request.auth.login(token) }.flatMapErrorThrowing { error in switch error { @@ -52,12 +51,12 @@ public struct AWSCognitoAccessAuthenticator: BearerAuthenticator { /// Authenticator for Cognito id tokens. Can use this to extract information from Id Token into Payload struct. The list of standard list of claims found in an id token are /// detailed in the [OpenID spec] (https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims) . Your `Payload` type needs /// to decode using these tags, plus the AWS specific "cognito:username" tag and any custom tags you have setup for the user pool. -public struct AWSCognitoIdAuthenticator: BearerAuthenticator { +public struct CognitoIdAuthenticator: BearerAuthenticator { public init() {} public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture { - return request.application.awsCognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop).map { (payload: Payload)->() in + return request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop).map { (payload: Payload)->() in request.auth.login(payload) }.flatMapErrorThrowing { error in switch error { diff --git a/Sources/SotoCognitoAuthentication/Exports.swift b/Sources/SotoCognitoAuthentication/Exports.swift new file mode 100644 index 0000000..192f173 --- /dev/null +++ b/Sources/SotoCognitoAuthentication/Exports.swift @@ -0,0 +1,3 @@ +@_exported import SotoCognitoAuthenticationKit +@_exported import SotoCognitoIdentity +@_exported import SotoCognitoIdentityProvider diff --git a/Sources/AWSCognitoAuthentication/Request+AWSCognito.swift b/Sources/SotoCognitoAuthentication/Request+Cognito.swift similarity index 81% rename from Sources/AWSCognitoAuthentication/Request+AWSCognito.swift rename to Sources/SotoCognitoAuthentication/Request+Cognito.swift index 3d078c2..f3963d7 100644 --- a/Sources/AWSCognitoAuthentication/Request+AWSCognito.swift +++ b/Sources/SotoCognitoAuthentication/Request+Cognito.swift @@ -1,25 +1,25 @@ -import AWSCognitoAuthenticationKit +import SotoCognitoAuthenticationKit import Vapor // extend AWSCognitoAuthenticateResponse so it can be returned from a Vapor route -extension AWSCognitoAuthenticateResponse: Content {} +extension CognitoAuthenticateResponse: Content {} public extension Request { - var awsCognito: AWSCognito { + var cognito: SotoCognito { .init(request: self) } - struct AWSCognito { + struct SotoCognito { /// helper function that returns if request with bearer token is cognito access authenticated /// - returns: /// An access token object that contains the user name and id - public func authenticateAccess() -> EventLoopFuture { + public func authenticateAccess() -> EventLoopFuture { guard let bearer = request.headers.bearerAuthorization else { return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) } - return request.application.awsCognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop) + return request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop) } /// helper function that returns if request with bearer token is cognito id authenticated and returns contents in the payload type @@ -29,17 +29,17 @@ public extension Request { guard let bearer = request.headers.bearerAuthorization else { return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) } - return request.application.awsCognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop) + return request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop) } /// helper function that returns refreshed access and id tokens given a request containing the refresh token as a bearer token /// - returns: /// The payload contained in the token. See `authenticate(idToken:on:)` for more details - public func refresh(username: String) -> EventLoopFuture { + public func refresh(username: String) -> EventLoopFuture { guard let bearer = request.headers.bearerAuthorization else { return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) } - return request.application.awsCognito.authenticatable.refresh(username: username, refreshToken: bearer.token, context: request, on: request.eventLoop) + return request.application.cognito.authenticatable.refresh(username: username, refreshToken: bearer.token, context: request, on: request.eventLoop) } /// helper function that returns AWS credentials for a provided identity. The idToken is provided as a bearer token. @@ -50,7 +50,7 @@ public extension Request { guard let bearer = request.headers.bearerAuthorization else { return request.eventLoop.makeFailedFuture(Abort(.unauthorized)) } - let identifiable = request.application.awsCognito.identifiable + let identifiable = request.application.cognito.identifiable return identifiable.getIdentityId(idToken: bearer.token, on: request.eventLoop) .flatMap { identity in return identifiable.getCredentialForIdentity(identityId: identity, idToken: bearer.token, on: self.request.eventLoop) @@ -62,7 +62,7 @@ public extension Request { } /// extend Vapor Request to provide Cognito context -extension Request: AWSCognitoContextData { +extension Request: CognitoContextData { public var contextData: CognitoIdentityProvider.ContextDataType? { let host = headers["Host"].first ?? "localhost:8080" guard let remoteAddress = remoteAddress else { return nil }