Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Soto Cognito Authentication v5 fixes #25

Merged
merged 4 commits into from
Sep 18, 2024
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
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