Skip to content

Merge 2FAS imports instead of replacing existing data#8

Merged
henricook merged 4 commits into
mainfrom
feat/2fas-import-merge
Jul 5, 2026
Merged

Merge 2FAS imports instead of replacing existing data#8
henricook merged 4 commits into
mainfrom
feat/2fas-import-merge

Conversation

@henricook

@henricook henricook commented Jul 5, 2026

Copy link
Copy Markdown
Owner
  • Importing a 2FAS backup now appends new entries instead of wiping current data
  • Duplicates skipped by normalised secret (whitespace stripped, upper-cased)
  • Imported groups merged by id; unresolvable group references fall back to Ungrouped
  • Imported ids that collide with existing services are remapped
  • Storage layer parses and returns data; OtpState merges, persists, then commits state
  • Import shows a summary dialog: N added, M ignored as duplicates
  • Extracted from spotco's fork (RFC: Interested in various features added in my local fork? #4), authored by @spotco

Note: merging means the result is written via the normal save path, so importing an encrypted backup persists decrypted secrets to data.json immediately. Main already does this on the first debounced usage-save, so this is not a new exposure, but the proper fix is the encrypted vault in #5 - suggest merging #5 first and rebasing this on top so imports persist through the vault when active.

spotco and others added 2 commits July 5, 2026 08:50
Importing a 2FAS backup now appends its entries to the current list
rather than wiping and replacing it. Duplicates are detected by
normalised secret (whitespace stripped, upper-cased) and skipped, so
re-importing the same file is a no-op. Imported groups are merged by
id without duplicating existing ones, and services keep a stable per
-group order with newly added entries appended after existing ones.

The storage layer now parses the selected file and returns its data
instead of copying it over the local store; OtpState performs the
merge and persists the combined result. The import action reports a
summary of how many entries were added and how many were ignored as
duplicates.
- Fix dangling imported group references: copyWith(groupId: null)
  null-coalesces and kept the unresolvable id, making those services
  invisible in grouped view; add OtpService.withGroupId and use it
- Persist the merged data before committing it to state so a failed
  save leaves the in-memory data untouched
- Remap imported service ids that collide with existing ones
- Tests: dangling-group fallback, id remapping, save-failure rollback,
  encrypted-backup import (password required / correct / wrong), and
  exact-match merge assertions instead of containsAll
@henricook
henricook force-pushed the feat/2fas-import-merge branch from c85dcca to 5ea67c6 Compare July 5, 2026 11:09
@henricook
henricook marked this pull request as ready for review July 5, 2026 11:31
henricook added 2 commits July 5, 2026 13:19
The persist-then-commit reorder let a debounced usage-save fire during
the import's own save, potentially clobbering the merged data on disk
with pre-import state. Cancel the timer at the start of the import, as
the vault branch already does.
…vault

Reconciles the import merge/append feature with the now-merged local
vault (PR #5). Import persistence goes through the vault-aware
_persistCurrentData() instead of a direct plaintext saveData, so
importing while an encrypted vault is active re-encrypts into data.bin
rather than writing decrypted secrets to data.json. The merge is
committed to state then persisted, rolling back the in-memory merge if
the persist fails. Adds a test asserting imports take the encrypted
path when the vault is active.
@henricook

Copy link
Copy Markdown
Owner Author

Thanks @spotco !

@henricook
henricook merged commit d92fba2 into main Jul 5, 2026
1 check passed
@henricook
henricook deleted the feat/2fas-import-merge branch July 5, 2026 12:57
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.

2 participants