-
Notifications
You must be signed in to change notification settings - Fork 202
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
feat(Auth): Adding TOTP state machine actions #3044
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
141 changes: 141 additions & 0 deletions
141
...th/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SoftwareTokenSetup/CompleteTOTPSetup.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Amplify | ||
import AWSCognitoIdentityProvider | ||
|
||
struct CompleteTOTPSetup: Action { | ||
|
||
var identifier: String = "CompleteTOTPSetup" | ||
let userSession: String | ||
let signInEventData: SignInEventData | ||
|
||
func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { | ||
logVerbose("\(#fileID) Starting execution", environment: environment) | ||
|
||
do { | ||
var deviceMetadata = DeviceMetadata.noData | ||
guard let username = signInEventData.username else { | ||
throw SignInError.unknown(message: "Unable to unwrap username during TOTP verification") | ||
} | ||
let authEnv = try environment.authEnvironment() | ||
let userpoolEnv = try environment.userPoolEnvironment() | ||
let challengeType: CognitoIdentityProviderClientTypes.ChallengeNameType = .mfaSetup | ||
|
||
deviceMetadata = await DeviceMetadataHelper.getDeviceMetadata( | ||
for: username, | ||
with: environment) | ||
|
||
var challengeResponses = [ | ||
"USERNAME": username | ||
] | ||
let userPoolClientId = userpoolEnv.userPoolConfiguration.clientId | ||
|
||
if let clientSecret = userpoolEnv.userPoolConfiguration.clientSecret { | ||
let clientSecretHash = ClientSecretHelper.clientSecretHash( | ||
username: username, | ||
userPoolClientId: userPoolClientId, | ||
clientSecret: clientSecret | ||
) | ||
challengeResponses["SECRET_HASH"] = clientSecretHash | ||
} | ||
|
||
if case .metadata(let data) = deviceMetadata { | ||
challengeResponses["DEVICE_KEY"] = data.deviceKey | ||
} | ||
|
||
let asfDeviceId = try await CognitoUserPoolASF.asfDeviceID( | ||
for: username, | ||
credentialStoreClient: authEnv.credentialsClient) | ||
|
||
var userContextData: CognitoIdentityProviderClientTypes.UserContextDataType? | ||
if let encodedData = CognitoUserPoolASF.encodedContext( | ||
username: username, | ||
asfDeviceId: asfDeviceId, | ||
asfClient: userpoolEnv.cognitoUserPoolASFFactory(), | ||
userPoolConfiguration: userpoolEnv.userPoolConfiguration) { | ||
userContextData = .init(encodedData: encodedData) | ||
} | ||
|
||
let analyticsMetadata = userpoolEnv | ||
.cognitoUserPoolAnalyticsHandlerFactory() | ||
.analyticsMetadata() | ||
|
||
let input = RespondToAuthChallengeInput( | ||
analyticsMetadata: analyticsMetadata, | ||
challengeName: challengeType, | ||
challengeResponses: challengeResponses, | ||
clientId: userPoolClientId, | ||
session: userSession, | ||
userContextData: userContextData) | ||
|
||
let responseEvent = try await UserPoolSignInHelper.sendRespondToAuth( | ||
request: input, | ||
for: username, | ||
signInMethod: signInEventData.signInMethod, | ||
environment: userpoolEnv) | ||
logVerbose("\(#fileID) Sending event \(responseEvent)", | ||
environment: environment) | ||
await dispatcher.send(responseEvent) | ||
// TODO: HS: | ||
// } catch let error where deviceNotFound(error: error, deviceMetadata: deviceMetadata) { | ||
// logVerbose("\(#fileID) Received device not found \(error)", environment: environment) | ||
// // Remove the saved device details and retry verify challenge | ||
// await DeviceMetadataHelper.removeDeviceMetaData(for: username, with: environment) | ||
// let event = SignInChallengeEvent( | ||
// eventType: .retryVerifyChallengeAnswer(confirmSignEventData) | ||
// ) | ||
// logVerbose("\(#fileID) Sending event \(event)", environment: environment) | ||
// await dispatcher.send(event) | ||
} catch let error as SignInError { | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SignInEvent(eventType: .throwAuthError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} catch { | ||
let error = SignInError.service(error: error) | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SignInEvent(eventType: .throwAuthError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} | ||
} | ||
|
||
// TODO: HS: Figure out if this is needed | ||
// func deviceNotFound(error: Error, deviceMetadata: DeviceMetadata) -> Bool { | ||
// | ||
// // If deviceMetadata was not send, the error returned is not from device not found. | ||
// if case .noData = deviceMetadata { | ||
// return false | ||
// } | ||
// | ||
// if let serviceError: RespondToAuthChallengeOutputError = error.internalAWSServiceError(), | ||
// case .resourceNotFoundException = serviceError { | ||
// return true | ||
// } | ||
// return false | ||
// } | ||
|
||
} | ||
|
||
extension CompleteTOTPSetup: CustomDebugDictionaryConvertible { | ||
var debugDictionary: [String: Any] { | ||
[ | ||
"identifier": identifier, | ||
"session": userSession.masked(), | ||
"signInEventData": signInEventData.debugDictionary | ||
] | ||
} | ||
} | ||
|
||
extension CompleteTOTPSetup: CustomDebugStringConvertible { | ||
var debugDescription: String { | ||
debugDictionary.debugDescription | ||
} | ||
} |
40 changes: 40 additions & 0 deletions
40
.../Sources/AWSCognitoAuthPlugin/Actions/SignIn/SoftwareTokenSetup/InitializeTOTPSetup.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Foundation | ||
|
||
struct InitializeTOTPSetup: Action { | ||
|
||
var identifier: String = "InitializeTOTPSetup" | ||
let authResponse: SignInResponseBehavior | ||
|
||
func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { | ||
logVerbose("\(#fileID) Start execution", environment: environment) | ||
let event = SetUpTOTPEvent( | ||
id: UUID().uuidString, | ||
eventType: .setUpTOTP(authResponse)) | ||
logVerbose("\(#fileID) Sending event \(event.type)", environment: environment) | ||
await dispatcher.send(event) | ||
} | ||
} | ||
|
||
extension InitializeTOTPSetup: CustomDebugDictionaryConvertible { | ||
var debugDictionary: [String: Any] { | ||
[ | ||
"identifier": identifier, | ||
"challengeName": authResponse.challengeName?.rawValue ?? "", | ||
"session": authResponse.session?.masked() ?? "", | ||
"challengeParameters": authResponse.challengeParameters ?? [:] | ||
] | ||
} | ||
} | ||
|
||
extension InitializeTOTPSetup: CustomDebugStringConvertible { | ||
var debugDescription: String { | ||
debugDictionary.debugDescription | ||
} | ||
} |
80 changes: 80 additions & 0 deletions
80
...ugins/Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SoftwareTokenSetup/SetUpTOTP.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Amplify | ||
import Foundation | ||
import AWSCognitoIdentityProvider | ||
|
||
struct SetUpTOTP: Action { | ||
|
||
var identifier: String = "SetUpTOTP" | ||
let authResponse: SignInResponseBehavior | ||
let signInEventData: SignInEventData | ||
|
||
func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { | ||
logVerbose("\(#fileID) Starting execution", environment: environment) | ||
|
||
do { | ||
let userpoolEnv = try environment.userPoolEnvironment() | ||
let client = try userpoolEnv.cognitoUserPoolFactory() | ||
let input = AssociateSoftwareTokenInput(session: authResponse.session) | ||
|
||
// Initiate Set Up TOTP | ||
let result = try await client.associateSoftwareToken(input: input) | ||
|
||
guard let username = signInEventData.username else { | ||
throw SignInError.unknown(message: "Unable unwrap username to for use during TOTP setup") | ||
} | ||
|
||
guard let session = result.session, | ||
let secretCode = result.secretCode else { | ||
throw SignInError.unknown(message: "Error unwrapping result associateSoftwareToken result") | ||
} | ||
|
||
let responseEvent = SetUpTOTPEvent(eventType: | ||
.waitForAnswer(.init( | ||
secretCode: secretCode, | ||
session: session, | ||
username: username))) | ||
logVerbose("\(#fileID) Sending event \(responseEvent)", | ||
environment: environment) | ||
await dispatcher.send(responseEvent) | ||
} catch let error as SignInError { | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SetUpTOTPEvent(eventType: .throwError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} catch { | ||
let error = SignInError.service(error: error) | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SetUpTOTPEvent(eventType: .throwError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} | ||
} | ||
|
||
} | ||
|
||
extension SetUpTOTP: CustomDebugDictionaryConvertible { | ||
var debugDictionary: [String: Any] { | ||
[ | ||
"identifier": identifier, | ||
"challengeName": authResponse.challengeName?.rawValue ?? "", | ||
"session": authResponse.session?.masked() ?? "", | ||
"challengeParameters": authResponse.challengeParameters ?? [:], | ||
"signInEventData": signInEventData.debugDictionary | ||
] | ||
} | ||
} | ||
|
||
extension SetUpTOTP: CustomDebugStringConvertible { | ||
var debugDescription: String { | ||
debugDictionary.debugDescription | ||
} | ||
} |
75 changes: 75 additions & 0 deletions
75
...Auth/Sources/AWSCognitoAuthPlugin/Actions/SignIn/SoftwareTokenSetup/VerifyTOTPSetup.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
// | ||
// Copyright Amazon.com Inc. or its affiliates. | ||
// All Rights Reserved. | ||
// | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// | ||
|
||
import Amplify | ||
import Foundation | ||
import AWSCognitoIdentityProvider | ||
|
||
struct VerifyTOTPSetup: Action { | ||
|
||
var identifier: String = "VerifyTOTPSetup" | ||
let session: String | ||
let totpCode: String | ||
let friendlyDeviceName: String? | ||
|
||
func execute(withDispatcher dispatcher: EventDispatcher, environment: Environment) async { | ||
logVerbose("\(#fileID) Starting execution", environment: environment) | ||
|
||
do { | ||
let userpoolEnv = try environment.userPoolEnvironment() | ||
let client = try userpoolEnv.cognitoUserPoolFactory() | ||
let input = VerifySoftwareTokenInput( | ||
friendlyDeviceName: friendlyDeviceName, | ||
session: session, | ||
userCode: totpCode) | ||
|
||
// Initiate TOTP verification | ||
let result = try await client.verifySoftwareToken(input: input) | ||
|
||
guard let session = result.session else { | ||
throw SignInError.unknown(message: "Unable to retrieve the session value from VerifySoftwareToken response") | ||
} | ||
|
||
let responseEvent = SetUpTOTPEvent(eventType: | ||
.respondToAuthChallenge(session)) | ||
logVerbose("\(#fileID) Sending event \(responseEvent)", | ||
environment: environment) | ||
await dispatcher.send(responseEvent) | ||
} catch let error as SignInError { | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SetUpTOTPEvent(eventType: .throwError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} catch { | ||
let error = SignInError.service(error: error) | ||
logError(error.authError.errorDescription, environment: environment) | ||
let errorEvent = SetUpTOTPEvent(eventType: .throwError(error)) | ||
logVerbose("\(#fileID) Sending event \(errorEvent)", | ||
environment: environment) | ||
await dispatcher.send(errorEvent) | ||
} | ||
} | ||
|
||
} | ||
|
||
extension VerifyTOTPSetup: CustomDebugDictionaryConvertible { | ||
var debugDictionary: [String: Any] { | ||
[ | ||
"identifier": identifier, | ||
"session": session.masked(), | ||
"totpCode": totpCode.redacted(), | ||
"friendlyDeviceName": friendlyDeviceName ?? "" | ||
] | ||
} | ||
} | ||
|
||
extension VerifyTOTPSetup: CustomDebugStringConvertible { | ||
var debugDescription: String { | ||
debugDictionary.debugDescription | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remember to remove commented out code before releasing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.. This is something I still need to work on.. Will remove it in a follow up PR.