Skip to content

feat(web-client): opt-in passkey-based keystore#27

Open
WiktorStarczewski wants to merge 9 commits into
nextfrom
wiktor/migrate-1835-passkey-keystore
Open

feat(web-client): opt-in passkey-based keystore#27
WiktorStarczewski wants to merge 9 commits into
nextfrom
wiktor/migrate-1835-passkey-keystore

Conversation

@WiktorStarczewski
Copy link
Copy Markdown
Collaborator

@WiktorStarczewski WiktorStarczewski commented Apr 28, 2026

Client PR: #1835

Migrated from miden-client#1835 as part of the web-sdk split (web/WASM components moved out of miden-client into this dedicated repo — see miden-client #1992 / #2135).

Summary (per the original PR)

  • Adds opt-in passkey-based keystore for the WebClient: keys are encrypted at rest behind a passkey using a WebAuthn PRF extension, eliminating plaintext key storage in browser environments.
  • New passkey-keystore.js module + passkey-keystore.test.ts test coverage.
  • Auto-detection: encrypted entries are recognised by a magic header; plaintext entries continue to work for backward compatibility.
  • Set via a new passkeyEncryption option on WebClient.createClient, with a clear warning when both passkeyEncryption and an explicit keystore are passed.

The miden-client/Rust-side changes from the original PR (opt-in password-based encryption for FilesystemKeyStore) stay on miden-client#1835.

…NFLICTS)

Migrated from 0xMiden/miden-client#1835 (author: WiktorStarczewski) as
part of the web-sdk split. Original PR: 0xMiden/miden-client#1835

This commit contains 3-way merge conflict markers in:
  - crates/web-client/js/client.js
  - crates/web-client/js/index.js
  - crates/web-client/rollup.config.js

The miden-client PR was opened against an older miden-client next, and
web-sdk's next has drifted (e.g. via the PR #13 sync from miden-client
next). The conflicts must be resolved manually before merge.
Resolves the committed <<<<<<<< / >>>>>>> conflict markers in:
  - crates/web-client/js/client.js     — keep both passkey resolution
                                          (theirs) and rpcUrl/noteTransportUrl
                                          extraction (ours), with passkey
                                          resolution running first so it can
                                          modify options.keystore before the
                                          downstream branch reads it.
  - crates/web-client/js/index.js      — keep both export blocks.
  - crates/web-client/rollup.config.js — keep new passkey-keystore build
                                          block (theirs), then keep the
                                          verbose classic-worker comment
                                          (ours), drop the terse 'Build the
                                          worker file' placeholder.

Note: this PR's CI will still be red — miden-client#1835 is built against
miden-protocol = '=0.14.0-alpha.1', which is too old to resolve against
web-sdk's current sources (23 compile errors when retargeting deps to
that branch). The original PR needs to be rebased onto current miden-client
next before this can be made green.
@WiktorStarczewski WiktorStarczewski added the no changelog PR doesn't need a CHANGELOG entry (trivial / non-user-visible) label Apr 30, 2026
Now that miden-client#1835's wiktor-storekeys branch has been merged
with miden-client@dab6cf7b (the same snapshot of next that web-sdk
currently pins), the dep retarget that #23/#25/#26/#31 use works for
this PR too.

Local 'cargo check --workspace --target wasm32-unknown-unknown' is clean
(was 23 errors before the upstream merge, due to the alpha-protocol
mismatch the PR description mentions).

Revert before merge — see PR description merge gate.
The new `passkey-keystore.js` module depends on browser-only WebAuthn
PRF APIs (navigator.credentials, AuthenticatorAttachment, etc.) that
aren't available in the node-test environment. Including it in the
vitest coverage scope drops overall coverage to ~74%, well below the
95% threshold (the file is 610 lines, none reachable from node).

It IS covered by Playwright integration tests
(crates/web-client/test/passkey-keystore.test.ts), so excluding it from
vitest follows the same pattern as `js/wasm.js`, `js/eager.js`,
`js/index.js`, etc. — sources that depend on platform features the
node test runner can't provide.

Note: this PR didn't have vitest.config.js on its branch (the file was
added on next after this PR was opened). Seeded with next's content
verbatim plus the new exclusion.
WiktorStarczewski added a commit that referenced this pull request Apr 30, 2026
Observed flake: probe returns HTTP 200 once on the first attempt that
clears the connection-refused phase, exits, tests start, ALL tests fail
with 'TypeError: Failed to fetch' to the gRPC backend. The single-probe
gate isn't strict enough — a one-shot 200 (e.g. tonic-health responding
before the rest of the dispatcher is fully wired) currently passes.

Upgrade the readiness signal to N consecutive HTTP successes spaced
PROBE_INTERVAL apart (defaults: 3 successes, 0.5s apart), so the probe
only declares the server ready after ~1s of demonstrably-stable
response. Any non-success in the streak resets it to zero and the
slow-poll loop resumes — so a momentary blip during init doesn't get
counted twice on either side.

Tracked occurrences across recent PR runs: web-sdk PR #23 ci-shard-4,
PR #29 ci-shard-1 + ci-shard-4, PR #27 multiple shards.
WiktorStarczewski added a commit that referenced this pull request Apr 30, 2026
Observed flake: probe returns HTTP 200 once on the first attempt that
clears the connection-refused phase, exits, tests start, ALL tests fail
with 'TypeError: Failed to fetch' to the gRPC backend. The single-probe
gate isn't strict enough — a one-shot 200 (e.g. tonic-health responding
before the rest of the dispatcher is fully wired) currently passes.

Upgrade the readiness signal to N consecutive HTTP successes spaced
PROBE_INTERVAL apart (defaults: 3 successes, 0.5s apart), so the probe
only declares the server ready after ~1s of demonstrably-stable
response. Any non-success in the streak resets it to zero and the
slow-poll loop resumes — so a momentary blip during init doesn't get
counted twice on either side.

Tracked occurrences across recent PR runs: web-sdk PR #23 ci-shard-4,
PR #29 ci-shard-1 + ci-shard-4, PR #27 multiple shards.
WiktorStarczewski added a commit that referenced this pull request Apr 30, 2026
Observed flake: probe returns HTTP 200 once on the first attempt that
clears the connection-refused phase, exits, tests start, ALL tests fail
with 'TypeError: Failed to fetch' to the gRPC backend. The single-probe
gate isn't strict enough — a one-shot 200 (e.g. tonic-health responding
before the rest of the dispatcher is fully wired) currently passes.

Upgrade the readiness signal to N consecutive HTTP successes spaced
PROBE_INTERVAL apart (defaults: 3 successes, 0.5s apart), so the probe
only declares the server ready after ~1s of demonstrably-stable
response. Any non-success in the streak resets it to zero and the
slow-poll loop resumes — so a momentary blip during init doesn't get
counted twice on either side.

Tracked occurrences across recent PR runs: web-sdk PR #23 ci-shard-4,
PR #29 ci-shard-1 + ci-shard-4, PR #27 multiple shards.
WiktorStarczewski added a commit that referenced this pull request Apr 30, 2026
Observed flake: probe returns HTTP 200 once on the first attempt that
clears the connection-refused phase, exits, tests start, ALL tests fail
with 'TypeError: Failed to fetch' to the gRPC backend. The single-probe
gate isn't strict enough — a one-shot 200 (e.g. tonic-health responding
before the rest of the dispatcher is fully wired) currently passes.

Upgrade the readiness signal to N consecutive HTTP successes spaced
PROBE_INTERVAL apart (defaults: 3 successes, 0.5s apart), so the probe
only declares the server ready after ~1s of demonstrably-stable
response. Any non-success in the streak resets it to zero and the
slow-poll loop resumes — so a momentary blip during init doesn't get
counted twice on either side.

Tracked occurrences across recent PR runs: web-sdk PR #23 ci-shard-4,
PR #29 ci-shard-1 + ci-shard-4, PR #27 multiple shards.
…en configs

  - vitest.config.js: exclude crates/web-client/js/passkey-keystore.js
    from coverage scope. The new file depends on browser-only WebAuthn
    PRF APIs that aren't reachable from node, so it can't be unit-tested
    via vitest. It's covered end-to-end by
    crates/web-client/test/passkey-keystore.test.ts under Playwright
    instead. Without this exclusion, coverage was 73.67% (vs 95% gate).

  - knip.jsonc: remove 'dexie' from ignoreDependencies. dexie was
    previously only loaded transitively at test runtime (via rollup-bundled
    page.evaluate scripts that knip's static scan can't see), but
    passkey-keystore.js now imports it directly at the source level —
    making the ignore unnecessary. Knip flagged this with 'Remove from
    ignoreDependencies'.

  - Pulls in scripts/wait-for-grpc.sh's stricter probe (#62 / #63), which
    addresses the gRPC dispatch flake that previously hit several PRs'
    integration shards.

cargo check --workspace --target wasm32-unknown-unknown is clean (build
artifact compiles against the dep retarget at miden-client wiktor-storekeys
that this PR carries on Cargo.toml). Test stage may surface additional
issues now that the build clears — particularly the 'webclient_new
undefined' WASM-symbol issue we saw on prior runs of this branch — but
those need to be diagnosed against a fresh CI run rather than guessed at.
…age.evaluate

The new test/passkey-keystore.test.ts at line 100 does:

    await page.evaluate(async () => {
      const { createPasskeyKeystore } = await import('./passkey-keystore.js');
      ...
    });

The import inside page.evaluate() runs in the *browser* context (loaded
from http://localhost:8080) so it resolves to dist/passkey-keystore.js
that the dedicated passkey rollup build emits — NOT to a sibling of
the test file. Knip's static scan can't follow this and flags it as an
unresolved import.

Add './passkey-keystore.js' to the existing ignoreUnresolved allowlist
on the crates/web-client workspace, alongside './index.js' and
'./crates/miden_client_web' which have the same in-browser-eval shape.
…manual dep retarget

Cargo.toml + Cargo.lock match origin/next; the 'Client PR: #1835'
marker on the PR description drives runtime dep injection. The
miden-client wiktor-storekeys branch was previously merged with
dab6cf7b (see earlier in this session) so it carries the storekeys
work on top of the dab6cf7b-era Store trait that web-sdk's idxdb-store
implements.
@github-actions
Copy link
Copy Markdown

🔗 Linked client PR: 0xMiden/miden-client#1835

Field Value
Patched at 0xMiden/miden-client@wiktor-storekeys
Pin (head sha) 5608802d6c10ea76aaee64835984b5254d5fef37
Upstream state open (merged: false)

This run is testing against the linked PR's head. The published artifact will use the canonical miden-client source — CI on main/next does not auto-patch.

Local-dev parity:

scripts/dev-with-client-pr.sh 1835    # apply the same patch locally
scripts/dev-with-client-pr.sh --clear                            # remove it before commit

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no changelog PR doesn't need a CHANGELOG entry (trivial / non-user-visible)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant