Skip to content

Sign in with CoinPay (bound by sub) + Connect/Disconnect - #134

Merged
sh1pt-actions-fleet[bot] merged 1 commit into
mainfrom
coinpay-signin
Oct 29, 2026
Merged

sh1pt-actions-fleet[bot] merged 1 commit into
mainfrom
coinpay-signin

Conversation

@sh1pt-actions-fleet

Copy link
Copy Markdown
Contributor

Adds Sign in with CoinPay to nightcell7.com.

  • Provider coinpay via Better Auth's genericOAuth (1.7.13): OIDC discovery, PKCE S256, state, nonce bound to the id_token, id_token verified against CoinPay's JWKS (the new client is RS256), confidential client secret on the exchange. Callback (Better Auth convention): https://nightcell7.com/api/v1/auth/callback/coinpay.
  • Binding (STANDING rule): the account is accounts(provider_id='coinpay', account_id=<sub>). The CoinPay profile maps to an unverified placeholder users.email <sub>@coinpay.nightcell7.invalid; accountLinking.disableImplicitLinking is on, so no sign-in ever joins an account by email; signing up (password or magic link) with the placeholder domain is refused. No admin/role is derived from CoinPay.
  • Connect (account page) uses Better Auth link-social, which needs the session (trustedProviders: ['coinpay'], allowDifferentEmails: true only make explicit linking possible). Disconnect: DELETE /api/v1/me/coinpay, refused when CoinPay is the only way in (placeholder email, no password, no passkey).
  • Login and register show the CoinPay button first; hidden when COINPAY_OAUTH_CLIENT_ID/SECRET are unset.
  • Gated, unchanged: multiplayer and invites require a verified email, which a CoinPay-only account never has. PRD auth row updated.

Tests: packages/auth/src/coinpay.test.ts runs the whole flow against a fake CoinPay (discovery, JWKS, token, userinfo) on Better Auth's memory adapter: a CoinPay login carrying a victim's email gets its own account bound to the sub; forged state refused; wrong nonce refused; link-social without a session is 401; the placeholder domain cannot sign up. services/api disconnect tests. Full suite 534+4 pass, bun run typecheck clean.

🤖 Generated with Claude Code

…/Disconnect on the account page

- Provider "coinpay" through Better Auth's generic OAuth: OIDC discovery,
  PKCE S256, state, nonce bound to the id_token, id_token verified against
  CoinPay's JWKS (RS256), account bound to the CoinPay sub
  (accounts provider_id=coinpay, account_id=sub). Callback
  /api/v1/auth/callback/coinpay.
- The profile maps to an unverified placeholder email
  (<sub>@coinpay.nightcell7.invalid), implicit linking by email is off, and
  nobody can sign up with the placeholder domain. CoinPay-only accounts are
  unverified, so multiplayer stays locked for them (PRD §23.1 unchanged).
- Connect via link-social (needs the session); Disconnect via
  DELETE /api/v1/me/coinpay, refused when CoinPay is the only way in.
- Sign in and register show the CoinPay button first; env
  COINPAY_OAUTH_CLIENT_ID/SECRET (both unset: no CoinPay sign-in).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
providerId: "credential",
accountId: "u1",
userId: "u1",
password: "salt:hash",
@github-actions

Copy link
Copy Markdown

ThreatCrush Security Scan

2 finding(s)

MEDIUM: 1 | LOW: 1

Severity Rule Location
MEDIUM js-unescaped-html-sink apps/site/app/_components/pit-footer.tsx:17
LOW secret-generic-credential packages/auth/src/server.test.ts:57

Snippets are redacted; ThreatCrush never prints matched credential material.

@sh1pt-actions-fleet
sh1pt-actions-fleet Bot merged commit 5d1ef22 into main Oct 29, 2026
7 checks passed
@sh1pt-actions-fleet
sh1pt-actions-fleet Bot deleted the coinpay-signin branch October 29, 2026 06:01
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