Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ struct CognitoUserPoolASF: AdvancedSecurityBehavior {
contextData: [String: String],
userPoolId: String
) throws -> String {
let timestamp = String(format: "%lli", floor(Date().timeIntervalSince1970 * 1_000))
let timestamp = String(format: "%lli", Int64(Date().timeIntervalSince1970 * 1_000))
let payload = [
"contextData": contextData,
"username": username,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,12 @@ struct MigrateLegacyCredentialStore: Action {
scopes: scopes ?? [],
providerInfo: provider,
presentationAnchor: nil,
preferPrivateSession: false
preferPrivateSession: false,
nonce: nil,
language: nil,
loginHint: nil,
prompt: nil,
resource: nil
))
default:
return .apiBased(.userSRP)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct InformSessionError: Action {
logVerbose("\(#fileID) Starting execution", environment: environment)
let event: AuthorizationEvent = switch error {
case .service(let serviceError):
if isNotAuthorizedError(serviceError) {
if serviceError is AWSCognitoIdentityProvider.NotAuthorizedException {
.init(eventType: .throwError(
.sessionExpired(error: serviceError)))
} else {
Expand All @@ -34,11 +34,6 @@ struct InformSessionError: Action {
logVerbose("\(#fileID) Sending event \(event.type)", environment: environment)
await dispatcher.send(event)
}

func isNotAuthorizedError(_ error: Error) -> Bool {
error is AWSCognitoIdentity.NotAuthorizedException
|| error is AWSCognitoIdentityProvider.NotAuthorizedException
}
}

extension InformSessionError: DefaultLogger {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ struct ConfirmSignUp: Action {
await dispatcher.send(SignUpEvent(eventType: .signedUp(dataToSend, .init(.done))))
}
} catch let error as SignUpError {
let errorEvent = SignUpEvent(eventType: .throwAuthError(error))
let errorEvent = SignUpEvent(eventType: .throwAuthError(error, data))
logVerbose(
"\(#fileID) Sending event \(errorEvent)",
environment: environment
)
await dispatcher.send(errorEvent)
} catch {
let error = SignUpError.service(error: error)
let errorEvent = SignUpEvent(eventType: .throwAuthError(error))
let errorEvent = SignUpEvent(eventType: .throwAuthError(error, data))
logVerbose(
"\(#fileID) Sending event \(errorEvent)",
environment: environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,15 @@ struct InitiateSignUp: Action {
}
await dispatcher.send(event)
} catch let error as SignUpError {
let errorEvent = SignUpEvent(eventType: .throwAuthError(error))
let errorEvent = SignUpEvent(eventType: .throwAuthError(error, data))
logVerbose(
"\(#fileID) Sending event \(errorEvent)",
environment: environment
)
await dispatcher.send(errorEvent)
} catch {
let error = SignUpError.service(error: error)
let errorEvent = SignUpEvent(eventType: .throwAuthError(error))
let errorEvent = SignUpEvent(eventType: .throwAuthError(error, data))
logVerbose(
"\(#fileID) Sending event \(errorEvent)",
environment: environment
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,76 @@ public struct AWSAuthWebUISignInOptions {
/// Safari always honors the request.
public let preferPrivateSession: Bool

/// A random value that you can add to the request. The nonce value that you provide is included in the ID token
/// that Amazon Cognito issues. To guard against replay attacks, your app can inspect the nonce claim in the ID
/// token and compare it to the one you generated.
public let nonce: String?

/// The language that you want to display user-interactive pages in
/// For more information, see Managed login localization -
/// https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-managed-login.html#managed-login-localization
public let language: String?

/// A username prompt that you want to pass to the authorization server. You can collect a username, email
/// address or phone number from your user and allow the destination provider to pre-populate the user's
/// sign-in name.
public let loginHint: String?

/// An OIDC parameter that controls authentication behavior for existing sessions.
public let prompt: [Prompt]?

/// The identifier of a resource that you want to bind to the access token in the `aud` claim. When you include
/// this parameter, Amazon Cognito validates that the value is a URL and sets the audience of the resulting
/// access token to the requested resource. Values for this parameter must begin with "https://", "http://localhost",
/// or a custom URL scheme like "myapp://".
public let resource: String?

public init(
idpIdentifier: String? = nil,
preferPrivateSession: Bool = false
preferPrivateSession: Bool = false,
nonce: String? = nil,
language: String? = nil,
loginHint: String? = nil,
prompt: [Prompt]? = nil,
resource: String? = nil
) {
self.idpIdentifier = idpIdentifier
self.preferPrivateSession = preferPrivateSession
self.nonce = nonce
self.language = language
self.loginHint = loginHint
self.prompt = prompt
self.resource = resource
}
}

public extension AWSAuthWebUISignInOptions {

enum Prompt: String, Codable {
/// Amazon Cognito silently continues authentication for users who have a valid authenticated session.
/// With this prompt, users can silently authenticate between different app clients in your user pool.
/// If the user is not already authenticated, the authorization server returns a login_required error.
case none

/// Amazon Cognito requires users to re-authenticate even if they have an existing session. Send this
/// value when you want to verify the user's identity again. Authenticated users who have an existing
/// session can return to sign-in without invalidating that session. When a user who has an existing
/// session signs in again, Amazon Cognito assigns them a new session cookie. This parameter can also
/// be forwarded to your IdPs. IdPs that accept this parameter also request a new authentication
/// attempt from the user.
case login

/// This value has no effect on local sign-in and must be submitted in requests that redirect to IdPs.
/// When included in your authorization request, this parameter adds prompt=select_account to the URL
/// path for the IdP redirect destination. When IdPs support this parameter, they request that users
/// select the account that they want to log in with.
case selectAccount = "select_account"

/// This value has no effect on local sign-in and must be submitted in requests that redirect to IdPs.
/// When included in your authorization request, this parameter adds prompt=consent to the URL path for
/// the IdP redirect destination. When IdPs support this parameter, they request user consent before
/// they redirect back to your user pool.
case consent
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,12 @@ struct HostedUISignInHelper: DefaultLogger {
scopes: request.options.scopes ?? scopeFromConfig,
providerInfo: providerInfo,
presentationAnchor: request.presentationAnchor,
preferPrivateSession: privateSession
preferPrivateSession: privateSession,
nonce: pluginOptions?.nonce,
language: pluginOptions?.language,
loginHint: pluginOptions?.loginHint,
promptValues: pluginOptions?.prompt,
resource: pluginOptions?.resource
)
let signInData = SignInEventData(
username: nil,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ struct HostedUIOptions {
let presentationAnchor: AuthUIPresentationAnchor?

let preferPrivateSession: Bool

let nonce: String?

let language: String?

let loginHint: String?

let prompt: String?

let resource: String?
}

extension HostedUIOptions: Codable {
Expand All @@ -28,6 +38,16 @@ extension HostedUIOptions: Codable {
case providerInfo

case preferPrivateSession

case nonce

case language = "lang"

case loginHint = "login_hint"

case prompt

case resource
}

init(from decoder: Decoder) throws {
Expand All @@ -36,14 +56,52 @@ extension HostedUIOptions: Codable {
self.providerInfo = try values.decode(HostedUIProviderInfo.self, forKey: .providerInfo)
self.preferPrivateSession = try values.decode(Bool.self, forKey: .preferPrivateSession)
self.presentationAnchor = nil
self.nonce = try values.decode(String.self, forKey: .nonce)
self.language = try values.decode(String.self, forKey: .language)
self.loginHint = try values.decode(String.self, forKey: .loginHint)
self.prompt = try values.decode(String.self, forKey: .prompt)
self.resource = try values.decode(String.self, forKey: .resource)
}

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(scopes, forKey: .scopes)
try container.encode(providerInfo, forKey: .providerInfo)
try container.encode(preferPrivateSession, forKey: .preferPrivateSession)
try container.encode(nonce, forKey: .nonce)
try container.encode(language, forKey: .language)
try container.encode(loginHint, forKey: .loginHint)
try container.encodeIfPresent(prompt, forKey: .prompt)
try container.encode(resource, forKey: .resource)
}
}

extension HostedUIOptions: Equatable { }

#if os(iOS) || os(macOS) || os(visionOS)
extension HostedUIOptions {
init(
scopes: [String],
providerInfo: HostedUIProviderInfo,
presentationAnchor: AuthUIPresentationAnchor?,
preferPrivateSession: Bool,
nonce: String?,
language: String?,
loginHint: String?,
promptValues: [AWSAuthWebUISignInOptions.Prompt]?,
resource: String?
) {
self.init(
scopes: scopes,
providerInfo: providerInfo,
presentationAnchor: presentationAnchor,
preferPrivateSession: preferPrivateSession,
nonce: nonce,
language: language,
loginHint: loginHint,
prompt: promptValues?.map { "\($0.rawValue)" }.joined(separator: " "),
resource: resource
)
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct SignUpEvent: StateMachineEvent {
case initiateSignUpComplete(SignUpEventData, AuthSignUpResult)
case confirmSignUp(SignUpEventData, ConfirmationCode, ForceAliasCreation?)
case signedUp(SignUpEventData, AuthSignUpResult)
case throwAuthError(SignUpError)
case throwAuthError(SignUpError, SignUpEventData)
}

init(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ enum SignUpState: State {
case awaitingUserConfirmation(SignUpEventData, AuthSignUpResult)
case confirmingSignUp(SignUpEventData)
case signedUp(SignUpEventData, AuthSignUpResult)
case error(SignUpError)
case error(SignUpError, SignUpEventData)
}

extension SignUpState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ extension SignUpState {
case .confirmSignUp(let data, let code, let forceAliasCreation):
let action = ConfirmSignUp(data: data, confirmationCode: code, forceAliasCreation: forceAliasCreation)
return .init(newState: .confirmingSignUp(data), actions: [action])
case .throwAuthError(let error):
return .init(newState: .error(error))
case .throwAuthError(let error, let signUpData):
return .init(newState: .error(error, signUpData))
default:
return .from(oldState)
}
Expand Down Expand Up @@ -84,8 +84,8 @@ extension SignUpState {
return .init(newState: .confirmingSignUp(data), actions: [action])
case .signedUp(let data, let result):
return .init(newState: .signedUp(data, result))
case .throwAuthError(let error):
return .init(newState: .error(error))
case .throwAuthError(let error, let signUpData):
return .init(newState: .error(error, signUpData))
}
}

Expand All @@ -100,8 +100,8 @@ extension SignUpState {
case .confirmSignUp(let data, let code, let forceAliasCreation):
let action = ConfirmSignUp(data: data, confirmationCode: code, forceAliasCreation: forceAliasCreation)
return .init(newState: .confirmingSignUp(data), actions: [action])
case .throwAuthError(let error):
return .init(newState: .error(error))
case .throwAuthError(let error, let signUpData):
return .init(newState: .error(error, signUpData))
default:
return .from(oldState)
}
Expand All @@ -120,8 +120,8 @@ extension SignUpState {
return .init(newState: .confirmingSignUp(data), actions: [action])
case .signedUp(let data, let result):
return .init(newState: .signedUp(data, result))
case .throwAuthError(let error):
return .init(newState: .error(error))
case .throwAuthError(let error, let signUpData):
return .init(newState: .error(error, signUpData))
default:
return .from(oldState)
}
Expand All @@ -138,8 +138,8 @@ extension SignUpState {
case .confirmSignUp(let data, let code, let forceAliasCreation):
let action = ConfirmSignUp(data: data, confirmationCode: code, forceAliasCreation: forceAliasCreation)
return .init(newState: .confirmingSignUp(data), actions: [action])
case .throwAuthError(let error):
return .init(newState: .error(error))
case .throwAuthError(let error, let signUpData):
return .init(newState: .error(error, signUpData))
default:
return .from(oldState)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,19 @@ extension AWSCognitoUserPoolTokens {

func doesExpire(in seconds: TimeInterval = 0) -> Bool {

let currentTime = Date(timeIntervalSinceNow: seconds)
guard let idTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: idToken).get(),
let accessTokenClaims = try? AWSAuthService().getTokenClaims(tokenString: accessToken).get(),
let idTokenExpiration = idTokenClaims["exp"]?.doubleValue,
let accessTokenExpiration = accessTokenClaims["exp"]?.doubleValue
else {
return currentTime > expiration
// If token parsing fails, return as expired, to just force refresh
return true
}

let idTokenExpiry = Date(timeIntervalSince1970: idTokenExpiration)
let accessTokenExpiry = Date(timeIntervalSince1970: accessTokenExpiration)

let currentTime = Date(timeIntervalSinceNow: seconds)
return currentTime > idTokenExpiry || currentTime > accessTokenExpiry
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,21 @@ enum HostedUIRequestHelper {
components.queryItems?.append(
.init(name: "identity_provider", value: authProvider.userPoolProviderName))
}
if let nonce = options.nonce {
components.queryItems?.append(.init(name: "nonce", value: nonce))
}
if let language = options.language {
components.queryItems?.append(.init(name: "lang", value: language))
}
if let loginHint = options.loginHint {
components.queryItems?.append(.init(name: "login_hint", value: loginHint))
}
if let prompt = options.prompt {
components.queryItems?.append(.init(name: "prompt", value: prompt))
}
if let resource = options.resource {
components.queryItems?.append(.init(name: "resource", value: resource))
}

guard let url = components.url else {
throw HostedUIError.signInURI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class AWSAuthConfirmSignUpTask: AuthConfirmSignUpTask, DefaultLogger {
switch signUpState {
case .signedUp(_, let result):
return result
case .error(let signUpError):
case .error(let signUpError, _):
throw signUpError.authError
default:
continue
Expand All @@ -81,6 +81,11 @@ class AWSAuthConfirmSignUpTask: AuthConfirmSignUpTask, DefaultLogger {
// only include session if the cached username matches
// the username in confirmSignUp() call
session = data.session
} else if case .error(_, let data) = signUpState,
request.username == data.username {
// only include session if the cached username matches
// the username in confirmSignUp() call
session = data.session
}

let pluginOptions = request.options.pluginOptions as? AWSAuthConfirmSignUpOptions
Expand Down
Loading
Loading