Skip to content

fix(add-factor): concurrent lookup heal + same-session OIDC - #234

Open
orekav wants to merge 2 commits into
uaf/3-universal-add-factorfrom
uaf/4-add-factor-hardening
Open

fix(add-factor): concurrent lookup heal + same-session OIDC#234
orekav wants to merge 2 commits into
uaf/3-universal-add-factorfrom
uaf/4-add-factor-hardening

Conversation

@orekav

@orekav orekav commented Aug 6, 2026

Copy link
Copy Markdown

Summary

  • Same OIDC ID token + session keypair can authorize both sides of a same-account upgrade (nonce consumed once; raw JWT compare is Apple-aud safe).
  • Successful writers always ensure_main_factor_lookup; rollback heals after ambiguous deletes.
  • Regression test for single-session metadata-only Turnkey upgrade.

Split from #136 (PR 4/4). Stacked on #233.

Test plan

  • cargo test --test add_factor_happy_paths test_add_factor_same_oidc_single_session
  • cargo test --lib routes::add_factor
  • cargo clippy --all-targets -- -D warnings

Allow one OIDC JWT+session for same-account upgrades, ensure/heal
FactorLookup around concurrent rollback deletes, and cover the
single-session metadata-only upgrade path.
@orekav orekav mentioned this pull request Aug 6, 2026
15 tasks

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 82d21ae2bf

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/routes/add_factor.rs
Comment on lines +569 to +572
) =>
{
// Another healer (or the concurrent writer) already restored the row.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Verify the owner after a conditional heal failure

When rollback healing races with registration of the same factor for another backup, this insert can fail conditionally because the lookup now belongs to that other backup. Treating every conditional failure as successful restoration leaves the original backup's metadata containing the factor while its lookup points elsewhere, breaking recovery and uniqueness; perform a consistent read and confirm backup_id, as ensure_main_factor_lookup does.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in this PR: d23d611

Heal now consistently re-reads after ConditionalCheckFailed and confirms the row still maps to this backup_id (same pattern as ensure_main_factor_lookup).

Comment thread src/routes/add_factor.rs
Comment on lines +592 to +594
match factor_lookup
.insert(FactorScope::Main, factor_to_lookup, backup_id.to_string())
.await

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid resurrecting lookups after concurrent deletion

If /delete-factor removes the factor (or the last factor and the entire backup) after the earlier metadata write/read but before this call, the unconditional insert recreates a lookup for metadata that no longer contains the factor. That stale row can route recovery to a missing backup and permanently prevent the credential from being registered elsewhere; the reconciliation must account for a concurrent metadata deletion rather than restoring solely from the earlier result.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred to a follow-up stacked PR: #235

There, ensure_main_factor_lookup re-reads metadata before (and during) restore so a concurrent /delete-factor / backup delete does not resurrect a stale lookup.

Comment thread src/routes/add_factor.rs
Comment on lines +573 to +577
Err(err) => {
tracing::error!(
message = "Failed to heal FactorLookup after concurrent factor write during rollback",
error = ?err,
factor_pk = factor_to_lookup.primary_key(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry failed lookup healing before abandoning the row

When request A owns the lookup, request B adopts it and successfully commits the factor, and B's final ensure runs before A's rollback delete, B can return success while A subsequently removes the shared row. If this healing insert then exhausts its DynamoDB retries and returns an error, merely logging it leaves B's successfully registered factor permanently untraceable; the rollback path needs a durable retry or another mechanism that guarantees eventual restoration rather than swallowing the failure.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deferred to a follow-up stacked PR: #235

Rollback heal now retries up to 3 times (with owner verification on conditional conflicts) before logging exhaustion.

…kFailed

Do not treat every conditional insert failure during rollback heal as
success; consistently re-read and confirm the row still maps to this
backup (matching ensure_main_factor_lookup).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant