Skip to content

feat: serve B3Note at note.b3pay.net with the same principals - #11

Merged
b3hr4d merged 1 commit into
mainfrom
claude/project-thread-fp9vgu
Oct 26, 2026
Merged

b3hr4d merged 1 commit into
mainfrom
claude/project-thread-fp9vgu

Conversation

@b3hr4d

@b3hr4d b3hr4d commented Oct 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Serve B3Note at https://note.b3pay.net without giving its users new accounts. Internet Identity derives a principal per origin, so at the custom domain the app now signs in with derivationOrigin set to the canister's own address, https://4lidq-zqaaa-aaaap-abkbq-cai.icp0.io, which lists the domain in /.well-known/ii-alternative-origins. A user has the same principal, and the same notes, at both addresses.

Changes

  • frontend/src/lib/origins.ts (new): production's frontend origin, backend id and custom domains, and customDomainAt(origin).
  • frontend/src/reactor.ts: at a custom domain, AuthClient gets derivationOrigin, and the backend is called by its id. ic-reactor does not trust the ic_env cookie there (any b3pay.net subdomain can set it), so the id comes from origins.ts. Nothing changes at the canister's own address or locally.
  • frontend/public/.well-known/ic-domains (read by the HTTP gateways before they serve the domain) and ii-alternative-origins (read by Internet Identity).
  • frontend/public/.ic-assets.json5: keeps .well-known, which the asset canister skips as a hidden folder, and serves ii-alternative-origins as JSON with Access-Control-Allow-Origin: *.
  • frontend/tests/customDomain.test.ts (new, 6 tests): the origin mapping, that origins.ts and both .well-known files list the same domains, the asset rules, and the app's client loaded at note.b3pay.net and at the canister's address (the AuthClient options, the backend it calls even with a hostile ic_env cookie, the agent host).
  • README.md: a "Custom domain" section with the DNS records and the registration calls.

Verification

  • Fact (run here): in frontend/, pnpm format:check, pnpm typecheck, pnpm test (6 files, 48 tests) and pnpm build pass. dist/ holds .well-known/ic-domains, .well-known/ii-alternative-origins and .ic-assets.json5, the bundle holds the domain and both ids, and src/canisters is unchanged.
  • Fact (run here): each new test bites. Five injected faults (no derivationOrigin, no custom-domain backend id, every origin treated as a custom domain, no .well-known rule, a wrong alternative origin) each failed the matching test, and were reverted.
  • Fact (Internet Identity specification, read 2026-10-26): Internet Identity fetches the list from https://<canister id>.icp0.io/.well-known/ii-alternative-origins, and issues an alternative origin listed there the derivation origin's principals.
  • Fact (IC docs, asset canister and custom domains guides, read 2026-10-26): the @dfinity/asset-canister recipe uploads .well-known only with ignore: false, and the gateways need /.well-known/ic-domains. The shared sessions guide says icp.net, icp0.io and ic0.app already give a canister one principal, and recommends the canister's own address as the derivation origin, pinned before the app has users.
  • Fact (public DNS and the registration API, 2026-10-26): forge.b3pay.net has the same three records, DNS only, and its registration status is registered. note.b3pay.net has no records yet, and its validation fails on the missing _acme-challenge CNAME, as expected.
  • Open: not deployed to a local network here, so the asset canister serving the two files with these headers is first checked by step 2 below. Today that URL returns 200 with the app's HTML, the fallback page.

After merge (mainnet, yours)

  1. icp deploy -e production frontend
  2. curl -si https://4lidq-zqaaa-aaaap-abkbq-cai.icp0.io/.well-known/ii-alternative-origins shows access-control-allow-origin: * and the JSON, and curl -s https://4lidq-zqaaa-aaaap-abkbq-cai.icp0.io/.well-known/ic-domains prints note.b3pay.net.
  3. The three DNS records in the README, on Cloudflare, DNS only.
  4. validate, then POST, then poll the status until registered (README).
  5. Sign in at both addresses and compare the principal.

Notes

  • A guest's key (localStorage) and the cached vault key (IndexedDB) are per origin, so a guest imports their recovery key at the new address, and an Internet Identity user's first unlock there derives the vault key again.
  • The hub canister's b3note app already lists https://note.b3pay.net; B3Pay/b3pay-hub#13 adds it to the hub worker's CORS origins.
  • Seen, not changed here: servedByTheIc trusts the ic_env cookie on ic0.app, which is not on the Public Suffix List (icp0.io and *.icp.net are, list of 2026-10-22), so another ic0.app canister could set that cookie. This canister answers 400 on ic0.app, so no B3Note page runs there today.

Generated by Claude Code

Internet Identity derives a principal per origin, so a custom domain is a
new origin and would give every user a new, empty account. At
https://note.b3pay.net the app now signs in with derivationOrigin set to
the canister's own address, which lists the domain in
/.well-known/ii-alternative-origins, so a user keeps their principal and
their notes at both addresses.

- src/lib/origins.ts: production's frontend origin, backend id and custom
  domains, and customDomainAt(origin) for the page.
- src/reactor.ts: on a custom domain, AuthClient gets derivationOrigin and
  the backend is called by its id. The ic_env cookie is not trusted there
  (any b3pay.net subdomain can set it), so the id comes from origins.ts.
  Nothing changes at the canister's own address or locally.
- public/.well-known/ic-domains (read by the HTTP gateways before they
  serve the domain) and ii-alternative-origins (read by Internet Identity).
- .ic-assets.json5: keep .well-known, which the asset canister skips as a
  hidden folder, and serve ii-alternative-origins as JSON with CORS.
- tests/customDomain.test.ts: the origin mapping, the three lists agree,
  the asset rules, and the app's client loaded at note.b3pay.net and at
  the canister's address (AuthClient options, backend id, agent host).
- README: a Custom domain section with the DNS records and registration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017cSwEz3Q2o3DpuJ8qSQzY5
@b3hr4d b3hr4d self-assigned this Oct 26, 2026

@b3hr4d b3hr4d left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LGTM!

@b3hr4d
b3hr4d merged commit 104877d into main Oct 26, 2026
2 checks passed
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