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 @@ -40,8 +40,12 @@ struct VerifyDevicePasswordSRP: Action {
credentialStoreClient: environment.authEnvironment().credentialsClient
)

// Read with the username the caller signed in with — `ConfirmDevice` writes the
// metadata under `inputUsername` (here `stateData.username`). `username` is
// `parameters["USERNAME"]`, the sub echoed by Cognito on alias pools, and is still the
// correct value to send back in the request below.
let deviceMetadata = await DeviceMetadataHelper.getDeviceMetadata(
for: username,
for: inputUsername,
with: environment
)
guard
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ struct VerifyPasswordSRP: Action {
credentialStoreClient: environment.authEnvironment().credentialsClient
)

// Device metadata is stored under the username the caller signed in with
// (`ConfirmDevice` uses `signedInData.inputUsername`), so it must be read back with
// the same value. `username` here is `parameters["USERNAME"]`, which for pools with
// alias sign-in is the sub — looking metadata up with it never matches what was
// written, so DEVICE_KEY would be omitted and every sign-in would register a new device.
deviceMetadata = await DeviceMetadataHelper.getDeviceMetadata(
for: username,
for: inputUsername,
with: environment
)
let signature = try signature(
Expand Down Expand Up @@ -100,8 +105,11 @@ struct VerifyPasswordSRP: Action {
await dispatcher.send(event)
} 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 password verify
await DeviceMetadataHelper.removeDeviceMetaData(for: username, with: environment)
// Remove the saved device details and retry password verify. This must use the same
// value the metadata was read with above — deleting under `username` (the echoed sub)
// leaves the real entry in place, so the retry re-reads the same stale device key,
// Cognito rejects it again, and sign-in loops here indefinitely.
await DeviceMetadataHelper.removeDeviceMetaData(for: inputUsername, with: environment)
let event = SignInEvent(eventType: .retryRespondPasswordVerifier(stateData, authResponse, clientMetadata))
logVerbose(
"\(#fileID) Sending event \(event)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,11 @@ struct VerifySignInChallenge: Action {
credentialStoreClient: environment.authEnvironment().credentialsClient
)

// Read with the username the caller signed in with — `ConfirmDevice` writes the
// metadata under `inputUsername`, and `challenge.username` is the value Cognito
// returned (the sub, for pools using alias sign-in).
deviceMetadata = await DeviceMetadataHelper.getDeviceMetadata(
for: username,
for: challenge.inputUsername ?? username,
with: environment
)

Expand Down Expand Up @@ -107,8 +110,13 @@ struct VerifySignInChallenge: Action {
await dispatcher.send(responseEvent)
} 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)
// Remove the saved device details and retry verify challenge. Must match the value the
// metadata was read with above — deleting under `username` (the echoed sub) leaves the
// real entry in place and the retry loops on the same stale device key.
await DeviceMetadataHelper.removeDeviceMetaData(
for: challenge.inputUsername ?? username,
with: environment
)
let event = SignInChallengeEvent(
eventType: .retryVerifyChallengeAnswer(confirmSignEventData, currentSignInStep)
)
Expand Down Expand Up @@ -139,7 +147,7 @@ struct VerifySignInChallenge: Action {
) async throws {

let newDeviceMetadata = await DeviceMetadataHelper.getDeviceMetadata(
for: username,
for: challenge.inputUsername ?? username,
with: environment
)
if challenge.challenge == .password {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,13 @@ struct UserPoolSignInHelper: DefaultLogger {
eventType: .respondPasswordVerifier(srpStateData, response, [:])
)
case .deviceSrpAuth:
return SignInEvent(eventType: .initiateDeviceSRP(username, response))
// Carry the caller's `inputUsername` into the device SRP flow. It has to look
// the stored device metadata back up, and that's keyed on the username the
// caller signed in with (see `ConfirmDevice`). `username` here is the value
// Cognito echoed — the sub, on pools with alias sign-in — so using it makes the
// lookup miss and the request omits DEVICE_KEY, which Cognito rejects with
// "Missing required parameter DEVICE_KEY".
return SignInEvent(eventType: .initiateDeviceSRP(inputUsername ?? username, response))
case .webAuthn:
let signInData = WebAuthnSignInData(
username: username,
Expand Down
Loading