Repository navigation
feat: account handoff relay and encrypted backups - #45
Merged
Merged
Conversation
Set Up Another Device and backups for the server account (witness-work
ADR 0026). POST /accounts/v1/handoff/{open,claim,status,send,receive,done,
cancel} relays a sealed hello and account key through one AccountHandoff
Durable Object per channel (10 minutes, first claim wins, five bad tokens
burn it). POST /accounts/v1/backup/{put,list,get,delete} keeps one
encrypted object per device slot in the new ACCOUNT_BACKUPS R2 bucket.
GET /h is the camera fallback page, matched by the AASA.
This file contains hidden or 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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Backend half of witness-work ADR 0026 (move, recover, and back up the server account), built to
docs/accounts-protocol.md("Handoff", "Backup", "Privacy").What changed
Handoff relay (Set Up Another Device)
AccountHandoff(src/accounts/handoffDO.ts), one perchannelId, bindingACCOUNT_HANDOFF, migrationv7(prod andenv.dev).POST /accounts/v1/handoff/{open,claim,status,send,receive,done,cancel}(src/accounts/handoff.ts).ww-account/v1and need a registered account. An unknown key getsunknown_account.b64u(SHA-256(claimToken))withverifierin constant time.open → claimed → sent → done, or tocancelledfrom any state.ok; any other claim getsconflict.doneandcancelclear the blobs at once. The alarm deletes everything 10 minutes afteropen.forbidden.too_large.openuses the register edge tier plus 20 opens per account per rolling day. That quota isBuddyRegistrationQuotaunder a hashedhandoff-openname;admitnow takes a limit. Every other op uses the read tier.Backups
ACCOUNT_BACKUPS:ww-account-backupsin prod,ww-account-backups-devin dev. Each object is stored atv1/<accountId>/<slotId>withcustomMetadata.uploadedAt.POST /accounts/v1/backup/{put,list,get,delete}(src/accounts/backup.ts).putreads its envelope fromx-ww-p/x-ww-s.Content-Lengthor signedsizeover 20 MiB is refused (too_large, 413) before the body is read.sizeandsha256.limit) and one put per slot per minute (rate_limitedwithRetry-After).getstreamsapplication/octet-stream.deletewithoutslotIddeletes every slot.ACCOUNTS_DB, every op returnsdisabled. Suspended accounts can still back up.scripts/r2-lifecycle.mjsnow handles both bucket families.ww-account-backups*gets a rule that deletesv1/objects 400 days after their last upload.deploy,deploy:dev, and the tag deploy run it for the backup bucket too.Other
GET /his the camera fallback page. It is localized with the same resolver and locale set as/c, runs no script, and reads nothing from the request except the language. The AASA now matches/h#1….assetlinks.jsonalready covers the whole host.forbidden(403).too_large,limit, andnot_foundalready existed.x-ww-p/x-ww-s./h.src/accounts/signed.ts).account/registerandaccount/infonow use them, with the same behavior and quota names.Prod setup (not done here)
The GitHub deploy's
CLOUDFLARE_API_TOKENalready needs Workers R2 Storage: Edit for the Buddies bucket. iOS caches the AASA, so the/hentry only goes live with a prod deploy and should ship ahead of the app UI.Dev: the
ww-account-backups-devbucket was created with its 400-day rule (node scripts/r2-lifecycle.mjs ww-account-backups-dev). The dev worker has not been deployed.Verified
pnpm test: 771 passed. New suites:src/accounts/handoff.test.ts,src/accounts/backup.test.ts,src/accounts/handoffPage.test.ts, plus lifecycle and Sentry cases.too_large(declared, signed, and streamed), and disabled.pnpm exec tsc --noEmitpasses. This repo has no lint script.wrangler deploy --dry-runand--env dev --dry-runboth showACCOUNT_HANDOFFandACCOUNT_BACKUPS.wrangler dev --env dev:doctorok.pnpm test:e2e: 47 passed, 3 skipped. This includes new e2e for a full handoff, burning, a backup round trip through local R2, a sha mismatch, and/h.too_large.pnpm fuzz:buddies --runs 150ok.Not verified
list({ include: ['customMetadata'] }), and a 20 MiB upload through the edge.