Skip to content

Commit

Permalink
Soto Cognito Authentication v5 fixes (#25)
Browse files Browse the repository at this point in the history
* Soto Cognito Authentication v5 fixes

* Update CI

* Remove swift 5.9

* format
  • Loading branch information
adam-fowler authored Sep 18, 2024
1 parent 6c4740e commit 1149bfb
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 107 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ jobs:
runs-on: macOS-latest
steps:
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Build
run: swift build

linux:
strategy:
matrix:
os: [ubuntu-latest]
swift: ["swift:5.4", "swift:5.5", "swift:5.6"]
swift: ["swift:5.10", "swift:6.0"]
runs-on: ${{ matrix.os }}
container:
image: ${{ matrix.swift }}
steps:
- name: Checkout
uses: actions/checkout@v1
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Build
Expand Down
10 changes: 5 additions & 5 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.2
// swift-tools-version:5.10
//===----------------------------------------------------------------------===//
//
// This source file is part of the Soto for AWS open source project
Expand All @@ -20,15 +20,15 @@ import PackageDescription
let package = Package(
name: "soto-cognito-authentication",
platforms: [
.macOS(.v10_15),
.iOS(.v13),
.tvOS(.v13),
.macOS(.v13),
.iOS(.v16),
.tvOS(.v16),
],
products: [
.library(name: "SotoCognitoAuthentication", targets: ["SotoCognitoAuthentication"]),
],
dependencies: [
.package(url: "https://github.com/soto-project/soto-cognito-authentication-kit.git", from: "4.0.0"),
.package(url: "https://github.com/soto-project/soto-cognito-authentication-kit.git", from: "5.0.0-rc.3"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"),
],
targets: [
Expand Down

This file was deleted.

32 changes: 20 additions & 12 deletions Sources/SotoCognitoAuthentication/Authenticators.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,26 @@ import NIO
import SotoCognitoAuthenticationKit
import Vapor

#if hasFeature(RetroactiveAttribute)
extension CognitoAuthenticateResponse: @retroactive Authenticatable {}
extension CognitoAccessToken: @retroactive Authenticatable {}
#else
extension CognitoAuthenticateResponse: Authenticatable {}
extension CognitoAccessToken: Authenticatable {}
#endif

public typealias CognitoBasicAuthenticatable = CognitoAuthenticateResponse
public typealias CognitoAccessAuthenticatable = CognitoAccessToken

/// Authenticator for Cognito username and password
public struct CognitoBasicAuthenticator: BasicAuthenticator {
public struct CognitoBasicAuthenticator: AsyncBasicAuthenticator {
public init() {}

public func authenticate(basic: BasicAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.cognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request, on: request.eventLoop).map { token in
public func authenticate(basic: BasicAuthorization, for request: Request) async throws {
do {
let token = try await request.application.cognito.authenticatable.authenticate(username: basic.username, password: basic.password, context: request)
request.auth.login(token)
}.flatMapErrorThrowing { error in
} catch {
switch error {
case is AWSErrorType, is NIOConnectionError:
// report connection errors with AWS, or unrecognised AWSErrorTypes
Expand All @@ -42,13 +48,14 @@ public struct CognitoBasicAuthenticator: BasicAuthenticator {
}

/// Authenticator for Cognito access tokens
public struct CognitoAccessAuthenticator: BearerAuthenticator {
public struct CognitoAccessAuthenticator: AsyncBearerAuthenticator {
public init() {}

public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: request.eventLoop).map { token in
public func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
do {
let token = try await request.application.cognito.authenticatable.authenticate(accessToken: bearer.token)
request.auth.login(token)
}.flatMapErrorThrowing { error in
} catch {
switch error {
case is NIOConnectionError:
// loading of jwk may cause a connection error. We should report this
Expand All @@ -63,13 +70,14 @@ public struct CognitoAccessAuthenticator: 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 CognitoIdAuthenticator<Payload: Authenticatable & Codable>: BearerAuthenticator {
public struct CognitoIdAuthenticator<Payload: Authenticatable & Codable>: AsyncBearerAuthenticator {
public init() {}

public func authenticate(bearer: BearerAuthorization, for request: Request) -> EventLoopFuture<Void> {
return request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: request.eventLoop).map { (payload: Payload) -> Void in
public func authenticate(bearer: BearerAuthorization, for request: Request) async throws {
do {
let payload: Payload = try await request.application.cognito.authenticatable.authenticate(idToken: bearer.token)
request.auth.login(payload)
}.flatMapErrorThrowing { error in
} catch {
switch error {
case is NIOConnectionError:
// loading of jwk may cause a connection error. We should report this
Expand Down
45 changes: 29 additions & 16 deletions Sources/SotoCognitoAuthentication/Request+Cognito.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@
//
//===----------------------------------------------------------------------===//

import NIO
import SotoCognitoAuthenticationKit
import Vapor

// extend AWSCognitoAuthenticateResponse so it can be returned from a Vapor route
#if hasFeature(RetroactiveAttribute)
extension CognitoAuthenticateResponse: @retroactive Content {}
#else
extension CognitoAuthenticateResponse: Content {}
#endif

public extension Request {
var cognito: SotoCognito {
Expand All @@ -27,54 +32,56 @@ public extension Request {
/// 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<CognitoAccessToken> {
public func authenticateAccess() async throws -> CognitoAccessToken {
guard let bearer = request.headers.bearerAuthorization else {
return self.request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
return self.request.application.cognito.authenticatable.authenticate(accessToken: bearer.token, on: self.request.eventLoop)
return try await self.request.application.cognito.authenticatable.authenticate(accessToken: bearer.token)
}

/// helper function that returns if request with bearer token is cognito id authenticated and returns contents in the payload type
/// - returns:
/// The payload contained in the token. See `authenticate<Payload: Codable>(idToken:on:)` for more details
public func authenticateId<Payload: Codable>() -> EventLoopFuture<Payload> {
public func authenticateId<Payload: Codable>() async throws -> Payload {
guard let bearer = request.headers.bearerAuthorization else {
return self.request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
return self.request.application.cognito.authenticatable.authenticate(idToken: bearer.token, on: self.request.eventLoop)
return try await self.request.application.cognito.authenticatable.authenticate(idToken: bearer.token)
}

/// 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<Payload: Codable>(idToken:on:)` for more details
public func refresh(username: String) -> EventLoopFuture<CognitoAuthenticateResponse> {
public func refresh(username: String) async throws -> CognitoAuthenticateResponse {
guard let bearer = request.headers.bearerAuthorization else {
return self.request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
return self.request.application.cognito.authenticatable.refresh(username: username, refreshToken: bearer.token, context: self.request, on: self.request.eventLoop)
return try await self.request.application.cognito.authenticatable.refresh(
username: username,
refreshToken: bearer.token,
context: self.request
)
}

/// helper function that returns AWS credentials for a provided identity. The idToken is provided as a bearer token.
/// If you have setup to use an AWSCognito User pool to identify users then the idToken is the idToken returned from the `authenticate` function
/// - returns:
/// AWS credentials for signing request to AWS
public func awsCredentials() -> EventLoopFuture<CognitoIdentity.Credentials> {
public func awsCredentials() async throws -> CognitoIdentity.Credentials {
guard let bearer = request.headers.bearerAuthorization else {
return self.request.eventLoop.makeFailedFuture(Abort(.unauthorized))
throw Abort(.unauthorized)
}
let identifiable = self.request.application.cognito.identifiable
return identifiable.getIdentityId(idToken: bearer.token, on: self.request.eventLoop)
.flatMap { identity in
return identifiable.getCredentialForIdentity(identityId: identity, idToken: bearer.token, on: self.request.eventLoop)
}
let identity = try await identifiable.getIdentityId(idToken: bearer.token)
return try await identifiable.getCredentialForIdentity(identityId: identity, idToken: bearer.token)
}

let request: Request
}
}

/// extend Vapor Request to provide Cognito context
extension Request: CognitoContextData {
extension Request {
public var contextData: CognitoIdentityProvider.ContextDataType? {
let host = headers["Host"].first ?? "localhost:8080"
guard let remoteAddress = remoteAddress else { return nil }
Expand All @@ -99,3 +106,9 @@ extension Request: CognitoContextData {
return contextData
}
}

#if hasFeature(RetroactiveAttribute)
extension Request: @retroactive CognitoContextData {}
#else
extension Request: CognitoContextData {}
#endif

0 comments on commit 1149bfb

Please sign in to comment.