Skip to content

feat(passkey-login-widget): serverless passkey login and encrypted backup widget (#307) - #308

Draft
goodbounties-nanoclaw-agent[bot] wants to merge 5 commits into
mainfrom
feat/gw305-passkey-login-widget
Draft

goodbounties-nanoclaw-agent[bot] wants to merge 5 commits into
mainfrom
feat/gw305-passkey-login-widget

Conversation

@goodbounties-nanoclaw-agent

Copy link
Copy Markdown
Contributor

Summary

Implements #307 (plan for feature #305): new packages/passkey-login-widget (@goodwidget/passkey-login-widget, <gw-passkey-login>), a fully client-side passkey login and encrypted backup widget.

  • WebAuthn PRF -> HKDF-SHA-256 (three distinct labels: account key, backup encryption key, backup file tag) -> secp256k1 account key; backup is AES-256-GCM with the address as AAD.
  • Backup file gwpk-<prfTag>-<address>.json, JSON v1 {version,address,iv,ciphertext}.
  • One storage interface; Google Drive adapter (appDataFolder, drive.appdata only) and iCloud adapter (CloudKit JS, indexed prfTag query).
  • Screens A1-D6 (light and dark), error codes D1-D6 + AMBIGUOUS_BACKUP (D6 layout).
  • Storybook: screen fixtures, mock-backed interaction stories, and a Live story (VITE_PASSKEY_* env or story controls; "not configured" otherwise).
  • Playwright specs with a Chromium virtual authenticator (real WebAuthn + PRF, in-memory storage) and committed baseline screenshots.

Commits

  1. be04f6e fix(ui): readable Button labels on secondary/text/outline variants + passkey icon. Standalone first commit so it can be reviewed on its own (on this branch it is the API-signed 440a538).
  2. widget package, ProviderAuthError export, Storybook stories/docs, Playwright specs + QA.md.

Heads-up for reviewers

Verification status

Mock / virtual-authenticator results are not live results.

Check Result
Playwright tests/widgets/passkey-login-widget (states 40, flows, errors 13, key-safety 2, version) Pass 71/71 (mock storage, virtual authenticator)
Unit tests (node --test) Pass 73/73
pnpm build Pass 19/19
test-storybook, passkey story suites Pass
Lint, packages/passkey-login-widget Pass
Lint, whole repo Fail, pre-existing and unrelated: unused ProfileTextAreaField in governance-widget, errors in ai-credits-web
test-storybook, whole repo Fail, pre-existing and unrelated: AiCreditsWidgetQA.stories.tsx (5 tests)
pnpm test:demo, whole repo Not run (only the passkey folder was run)
Live real passkey + Google Drive Blocked: needs the GoodDollar Google OAuth client ID
Live iCloud / CloudKit (incl. Apple sign-in overlay) Blocked: needs CloudKit container, API token, record type + queryable prfTag index
Real platform authenticators (Chrome, Safari 18+) Blocked (same dependency; not run on real devices)
Happy-path video Not run
Design-system smoke screenshots of other widgets' buttons Not run

Live setup and CloudKit handover are in tests/widgets/passkey-login-widget/QA.md.

Screenshots (result, 400px; designs are in issue #305)

Screen Light Dark
a1-entry light dark
a2-authenticating light dark
a3-new-account-warning light dark
a4-create-passkey light dark
b1-backup-prompt light dark
b2-backup-provider light dark
b3-backup-progress light dark
b4-backup-success light dark
c1-restore-provider light dark
c2-restore-progress light dark
c3-restore-success light dark
d1-unsupported-browser light dark
d2-passkey-cancelled light dark
d3-backup-not-found-single-provider light dark
d3-backup-not-found light dark
d4-provider-auth-failed light dark
d5-backup-failed-existing-user light dark
d5-backup-failed-new-user light dark
d6-ambiguous-backup light dark
d6-decrypt-failed light dark

Version badge: desktop light, desktop dark, mobile light, mobile dark. The sandbox renders the alert glyph as tofu and falls back to a monospace font; that is an environment artifact.

Acceptance criteria (#307 §6)

  • Package, props contract, <gw-passkey-login>, WidgetVersion; no backend
  • New user: passkey -> PRF key -> backup written -> onLogin + onBackupComplete; not logged in before backup exists (mock)
  • Existing user: backup written, "Not now" -> onSkip; key never changed or returned (mock)
  • Returning user: restore, address verified after decrypt, onLogin({isNewAccount:false}) (mock)
  • Three HKDF labels, versioned PRF salt, PRF verified at registration (unit + virtual authenticator)
  • File name gwpk-<prfTag>-<address>.json; restore by prfTag
  • Versioned backup JSON; key never logged (key-safety spec)
  • Silent overwrite on re-backup; no overwrite-warning state
  • Drive adapter no duplicate names (unit); CloudKit adapter queries by prfTag (unit, fake CloudKit)
  • drive.appdata only; CloudKit JS; one storage interface
  • Provider choice; last provider remembered (id only)
  • Screens A1-D6 light and dark; recovery for every error (mock); visual match to designs awaiting human review
  • Unsupported browser -> D1, nothing created
  • Button fix and passkey icon
  • Live story completing against real providers: story and docs implemented; live run Blocked
  • Unit tests and passkey Playwright specs pass; baselines committed
  • Repo-wide pnpm lint / test:storybook / test:demo: see table (pre-existing failures; test:demo not run)
  • Out of scope respected

Human-reviewer checklist (#307 §7)

  • Reference-vs-result screenshots, light and dark, 400px
  • Happy-path video (new account + backup, restore in a fresh session): not recorded
  • Button fix reviewed as its own commit; other widgets' buttons smoke-checked
  • crypto/*: distinct labels, random IV per encryption, address check after decrypt, no key in logs, file name leaks nothing beyond the public address
  • privateKey not an HTML attribute; onError carries no secrets
  • Scopes: Drive drive.appdata only
  • Drive client ID treated as permanent; CloudKit record type + prfTag index deployed to production before release
  • Live results (Chrome, Safari 18+, Drive, iCloud) reported honestly: currently Blocked
  • No secrets, real keys or client IDs committed; skills folder not committed (checked by author)
  • Copilot review done before leaving draft

🤖 Generated with Claude Code

…, add passkey icon

ButtonText was a named component, so Tamagui resolved its theme by falling
back to the ancestor light_Button/dark_Button component theme (white text)
even inside the `<Theme reset>` that Button applies to non-primary variants.
Secondary/outline/ghost/text labels were therefore white on light surfaces.
Dropping the component name lets the label inherit the nearest theme. No
change to presets.ts/theme.ts was needed.

Also adds a `passkey` glyph to Icon, a Button variants story, and a smoke
test asserting WCAG AA label contrast in light and dark.

Co-Authored-By: Claude Code <noreply@anthropic.com>

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…n widget

WebAuthn PRF key derivation, encrypted Google Drive / iCloud backup
storage, flow state machine, adapter hook, screens A1-D6 and the
custom element, with unit tests for the crypto, storage and flow logic.

Refs #307

Co-Authored-By: Claude Code <noreply@anthropic.com>

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…r custom storage adapters

Host apps and the Storybook mock storage need to signal provider sign-in
failures (D4) from their own PasskeyBackupStorage implementations.

Co-Authored-By: Claude Code <noreply@anthropic.com>

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…tion harness, live story and docs

Adds A1-D6 screen fixtures, interaction stories on in-memory storage, a manual
Live story (VITE_PASSKEY_* values or controls, nothing committed) and the guide.

Co-Authored-By: Claude Code <noreply@anthropic.com>

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)
…uthenticator flows and QA notes

States spec (A1-D6, light and dark), create/backup/restore round trips with a
Chromium virtual authenticator (WebAuthn PRF) on mock storage, error recovery,
key-safety and version specs, committed screenshots and QA.md.

Co-Authored-By: Claude Code <noreply@anthropic.com>

On-Behalf-Of: BountyCoder[onecli] (yaskkeryodtdijpv)

Copilot AI commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

One or more custom setup steps configured for this repository failed during this Copilot code review run:

Install Playwright browsers

Setup steps run before each review. If the review above is missing context, or no review was posted at all, the failing step above may be the cause. See the workflow run for failure details, fix your setup steps configuration, and re-request a review.

Note

You can configure setup steps for Copilot code review separately from Copilot cloud agent with a copilot-code-review.yml file. Read the docs for details.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔵 Needs a closer look

The review could not be completed because the required GoodDollar contribution guidelines were unavailable locally and could not be retrieved externally.

No open issues 🎉

1 file not reviewed
  • pnpm-lock.yaml: Generated file
What changed in this PR

Adds @goodwidget/passkey-login-widget for client-side passkey login and encrypted wallet backup, implementing the flows described in #305 and #307 within GoodWidget’s provider and web-component architecture.

Changes:

  • Adds passkey creation, account derivation, encrypted backup, and restore flows with Google Drive and iCloud storage adapters.
  • Adds themed screens, Storybook fixtures and live configuration, unit tests, and Playwright coverage. Real-provider and physical-authenticator verification remains blocked as documented.
  • Updates shared Button label theming for #303, adds contrast regression coverage, and introduces a passkey icon.
File Description
tests/​widgets/​passkey-login-widget/​webauthnFixture.ts Virtual authenticator and WebAuthn failure fixtures.
tests/​widgets/​passkey-login-widget/​version.spec.ts Version-label checks across themes and viewports.
tests/​widgets/​passkey-login-widget/​states.spec.ts Screen assertions and light/dark screenshots.
tests/​widgets/​passkey-login-widget/​restore-existing-passkey.spec.ts Restore round trips and exact-key checks.
tests/​widgets/​passkey-login-widget/​QA.md Automated coverage limits and live setup instructions.
tests/​widgets/​passkey-login-widget/​provider-choice.spec.ts Provider selection and remembered-provider tests.
tests/​widgets/​passkey-login-widget/​new-account.spec.ts Account creation, backup, and registration fallback tests.
tests/​widgets/​passkey-login-widget/​key-safety.spec.ts Checks for exposed keys and PRF outputs.
tests/​widgets/​passkey-login-widget/​interactionHelpers.ts Shared flow-driving and storage inspection helpers.
tests/​widgets/​passkey-login-widget/​existing-user-backup.spec.ts Existing-account backup and skip tests.
tests/​widgets/​passkey-login-widget/​errors-and-recovery.spec.ts Error screens, tampering, and recovery tests.
tests/​design-system/​smoke.spec.ts Non-primary Button label contrast checks.
pnpm-lock.yaml Workspace dependency entries for the widget.
packages/​ui/​src/​components/​Icon.tsx Adds the passkey icon.
packages/​ui/​src/​components/​Button.tsx Adjusts ButtonText theme inheritance.
packages/​passkey-login-widget/​tsup.config.ts Package build entry points and output formats.
packages/​passkey-login-widget/​tsconfig.json Editor and type-check configuration.
packages/​passkey-login-widget/​tsconfig.build.json Build configuration excluding test sources.
packages/​passkey-login-widget/​src/​widgetRuntimeContract.ts Public props, storage interface, screens, and errors.
packages/​passkey-login-widget/​src/​webauthn/​passkeyErrors.ts Maps failures to widget error codes.
packages/​passkey-login-widget/​src/​webauthn/​passkeyErrors.test.ts Error mapping and message-safety tests.
packages/​passkey-login-widget/​src/​webauthn/​passkeyClient.ts WebAuthn capability checks and PRF retrieval.
packages/​passkey-login-widget/​src/​testFixtures.ts Deterministic PRF and in-memory storage fixtures.
packages/​passkey-login-widget/​src/​storage/​storageAdapter.ts Storage type exports and provider-authentication error.
packages/​passkey-login-widget/​src/​storage/​rememberedProvider.ts Persists the last successful provider ID.
packages/​passkey-login-widget/​src/​storage/​rememberedProvider.test.ts Provider persistence and blocked-storage tests.
packages/​passkey-login-widget/​src/​storage/​loadExternalScript.ts Shared browser SDK loader.
packages/​passkey-login-widget/​src/​storage/​iCloudStorage.ts CloudKit authentication and private backup storage.
packages/​passkey-login-widget/​src/​storage/​iCloudStorage.test.ts Mocked CloudKit adapter tests.
packages/​passkey-login-widget/​src/​storage/​googleDriveStorage.ts Google OAuth and Drive app-data storage.
packages/​passkey-login-widget/​src/​storage/​googleDriveStorage.test.ts Mocked Drive requests and overwrite tests.
packages/​passkey-login-widget/​src/​register.ts Registers the custom element.
packages/​passkey-login-widget/​src/​PasskeyLoginWidget.tsx Provider-wrapped widget and preview entry points.
packages/​passkey-login-widget/​src/​index.ts Public package exports.
packages/​passkey-login-widget/​src/​flow/​passkeyOperations.ts Backup preparation, upload, and verified restore.
packages/​passkey-login-widget/​src/​flow/​passkeyOperations.test.ts Backup and restore operation tests.
packages/​passkey-login-widget/​src/​flow/​passkeyFlowMachine.ts Screen transitions and recovery state machine.
packages/​passkey-login-widget/​src/​flow/​passkeyFlowMachine.test.ts Journey, progress, and retry transition tests.
packages/​passkey-login-widget/​src/​element.ts Web-component bridge and property-only secret input.
packages/​passkey-login-widget/​src/​crypto/​prfDerivation.ts Separate account, encryption-key, and file-tag derivations.
packages/​passkey-login-widget/​src/​crypto/​prfDerivation.test.ts Derivation and scalar-validation tests.
packages/​passkey-login-widget/​src/​crypto/​encoding.ts Byte, hexadecimal, and base64 conversion helpers.
packages/​passkey-login-widget/​src/​crypto/​backupFormat.ts Versioned backup validation and address verification.
packages/​passkey-login-widget/​src/​crypto/​backupFormat.test.ts Format, encryption round-trip, and tampering tests.
packages/​passkey-login-widget/​src/​crypto/​backupFileName.ts Backup filename construction, parsing, and filtering.
packages/​passkey-login-widget/​src/​crypto/​backupErrors.ts Backup format and decryption error types.
packages/​passkey-login-widget/​src/​crypto/​backupCipher.ts AES-GCM encryption with address-bound authentication.
packages/​passkey-login-widget/​src/​config.ts Widget-local provider-tile themes.
packages/​passkey-login-widget/​src/​components/​ScreenCard.tsx Shared screen shell, actions, and waiting state.
packages/​passkey-login-widget/​src/​components/​RestoreScreens.tsx Restore provider, progress, and success screens.
packages/​passkey-login-widget/​src/​components/​ProviderTile.tsx Selectable provider tiles.
packages/​passkey-login-widget/​src/​components/​ProviderLogo.tsx Decorative storage-provider logos.
packages/​passkey-login-widget/​src/​components/​ProviderChoice.tsx Provider selection group.
packages/​passkey-login-widget/​src/​components/​ProgressSteps.tsx Shared progress-step rendering.
packages/​passkey-login-widget/​src/​components/​PasskeyLoginWidgetView.tsx Screen routing and widget version display.
packages/​passkey-login-widget/​src/​components/​LoginScreens.tsx Login, warning, and passkey creation screens.
packages/​passkey-login-widget/​src/​components/​ErrorScreens.tsx Error layouts and recovery actions.
packages/​passkey-login-widget/​src/​components/​BackupScreens.tsx Backup prompt, provider, progress, and success screens.
packages/​passkey-login-widget/​src/​adapter.ts Coordinates asynchronous flows and host callbacks.
packages/​passkey-login-widget/​package.json Package metadata, exports, scripts, and dependencies.
examples/​storybook/​src/​stories/​passkey-login-widget/​PasskeyLoginWidgetQA.stories.tsx Deterministic fixtures for all designed screens.
examples/​storybook/​src/​stories/​passkey-login-widget/​PasskeyLoginWidgetLive.stories.tsx Manual story for real passkeys and providers.
examples/​storybook/​src/​stories/​passkey-login-widget/​PasskeyLoginWidgetInteractions.stories.tsx Real-widget stories backed by mock storage.
examples/​storybook/​src/​stories/​passkey-login-widget/​PasskeyLoginWidget.mdx Widget usage and security-model documentation.
examples/​storybook/​src/​stories/​helpers/​passkeyLoginWidgetStories.tsx Story harnesses, masked event logs, and live configuration.
examples/​storybook/​src/​stories/​design-system/​Button.stories.tsx Button variant fixtures in both themes.
examples/​storybook/​src/​globals.d.ts Typed passkey provider environment variables.
examples/​storybook/​src/​fixtures/​passkeyMockStorage.ts Controllable storage double for interaction tests.
examples/​storybook/​package.json Adds the widget workspace dependency.

Review effort: Balanced

This branch was successfully deployed

3 active deployments
preview-storybook — 4ef6a20f Deployed Nov 11, 2026 by goodbounties-nanoclaw-agent[bot] via preview #603
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