Skip to content

feat(auth): CoinPay sign-in audit: nonce + id_token checks, sealed state, Connect/Disconnect (0.21.0) - #354

Merged
sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
fix/coinpay-oidc-connect
Oct 29, 2026
Merged

sh1pt-actions-fleet[bot] merged 1 commit into
masterfrom
fix/coinpay-oidc-connect

Conversation

@sh1pt-actions-fleet

Copy link
Copy Markdown
Contributor

Audit of qrypt.chat's existing "Log in with CoinPay" (client cp_b81d8f36449d9c8f36f2f3a0) against the STANDING sub-binding rule and the fleet relying-party pattern.

Already right: binds by app_metadata.coinpay_sub (fixed in #343), never by email; new accounts get <sub>@coinpay.qrypt.chat; PKCE S256 + client secret on the code exchange; session handed over by popup postMessage or a 2-minute /auth cookie (not a URL).

Fixed here:

  • OIDC nonce sent and the id_token checked (iss, aud, exp, nonce) with userinfo sub required to match, via @profullstack/coinpay-onboard/oauth 0.3.1 (finishSignIn).
  • Pending state/verifier/nonce cookie is HMAC-sealed under the client secret, 10 min, path /api/auth/coinpay (was unsigned JSON on /).
  • "Sign in with CoinPay" is now first on /auth.
  • Settings: Connect CoinPay (POST /api/auth/coinpay/connect with the Bearer session; the sealed cookie names the signed-in account) and Disconnect (POST /api/auth/coinpay/disconnect, refused when CoinPay is the only way in: placeholder email, no phone, no passkey). GET /api/auth/coinpay/status for the card.

After deploy: the client's legacy mode gets turned off in CoinPay (legacy_oauth2=false, require_pkce=true) once a login start is verified.

Tests: tests/coinpay-flow.test.js (new) + existing tests/coinpay-auth.test.js: 31 pass.

🤖 Generated with Claude Code

…ate, Connect/Disconnect (0.21.0)

Audit of the existing "Log in with CoinPay" against the STANDING sub rule and
the relying-party pattern:

- Sub binding was already right (app_metadata.coinpay_sub, never email).
- Now sends an OIDC nonce and checks the id_token (iss, aud, exp, nonce) and
  that userinfo's sub matches it, via @profullstack/coinpay-onboard/oauth;
  PKCE S256 + client secret on the code exchange as before.
- The pending state/verifier/nonce cookie is HMAC-sealed under the client
  secret and scoped to /api/auth/coinpay (was plain JSON on /).
- "Sign in with CoinPay" moved first on /auth.
- Settings: Connect CoinPay (POST with the Bearer session; the sealed cookie
  names the signed-in account) and Disconnect (refused when CoinPay is the
  only way in: placeholder email, no phone, no passkey).
- Tests: forged/edited/expired pending cookie, connect without a session,
  sub bound elsewhere, disconnect guard, binding ignores email and
  user_metadata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setError('');
try {
const { url } = await api('/api/auth/coinpay/connect', 'POST');
window.location.href = url;
@socket-security

Copy link
Copy Markdown
Contributor

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Added@​profullstack/​coinpay-onboard@​0.3.17610010093100

View full report

@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

18 finding(s)

MEDIUM: 14 | LOW: 4

Severity Rule Location
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:38
MEDIUM js-unescaped-html-sink src/app/blog/[slug]/page.jsx:66
MEDIUM js-unescaped-html-sink src/app/faq/page.jsx:57
MEDIUM js-unescaped-html-sink src/app/layout.jsx:142
MEDIUM js-unescaped-html-sink src/app/layout.jsx:146
MEDIUM js-unescaped-html-sink src/app/page.jsx:50
MEDIUM js-open-redirect src/app/phone/page.jsx:70
MEDIUM redos-nested-quantifier src/lib/auth/dns-name.js:88
MEDIUM redos-nested-quantifier src/lib/chat/voice-mode.js:116
MEDIUM redos-nested-quantifier src/lib/chat/voice-mode.js:121
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:85
MEDIUM js-unescaped-html-sink src/lib/components/chat/MessageItem.jsx:130
MEDIUM js-unescaped-html-sink src/lib/components/PitFooter.jsx:15
MEDIUM js-open-redirect src/lib/components/settings/CoinpaySettings.jsx:62
LOW secret-generic-credential src/app/api/auth/register-anon/route.test.js:32
LOW secret-jwt tests/debug-sms.js:10
LOW secret-generic-credential tests/private-key-import-export.test.js:252
LOW secret-generic-credential tests/private-key-import-export.test.js:264

Snippets are redacted; ThreatCrush never prints matched credential material.

@sh1pt-actions-fleet
sh1pt-actions-fleet Bot merged commit 3976f0a into master Oct 29, 2026
11 checks passed
@sh1pt-actions-fleet
sh1pt-actions-fleet Bot deleted the fix/coinpay-oidc-connect branch October 29, 2026 06:13
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