Verify email on registration (double opt-in) — stop unverified/bot signups #31

Open
opened 2026-08-25 02:06:48 +00:00 by Cordy · 0 comments
Owner

Today /v1/register creates a usable account immediately with no email verification. Two problems: (1) bots can mass-create accounts (as just happened on Forgejo), and (2) anyone could register whoever@swisscairn.ch without owning the mailbox — which matters because the admin dashboard (license-server dashboard issue) may key admin access off that domain.

Proposal — double opt-in

  • On /v1/register: create the account in an unverified state, generate a single-use, time-limited verification token (memory or a short-lived DB row), email a confirm link (reuse the existing SMTP path from email.go / reset flow).
  • New GET/POST /v1/verify?token=… marks the account verified.
  • Unverified accounts: allow login but gate the sensitive surfaces (purchase/checkout, license listing) behind verified — or block login entirely until verified. Decide which; blocking login is simpler and closes the bot vector hardest.
  • Keep the no-existence-oracle posture: the register response is the same whether or not the email already exists ("check your inbox").
  • Retention: prune unverified accounts after N days so the table doesn't accumulate bot rows.

Interaction with admin auth

If admin access is domain-based (@swisscairn.ch), it MUST additionally require the account be verified (or authenticated via Google OAuth, which already proves the mailbox). An explicit allowlist of admin emails sidesteps this, but verification is still the right default for all accounts.

Notes

  • Pairs with the CAPTCHA issue — verification stops confirmed-account abuse, CAPTCHA stops the flood at the door.
  • Stdlib-only stays intact (token = crypto/rand, email via existing SMTP).
Today `/v1/register` creates a usable account immediately with no email verification. Two problems: (1) bots can mass-create accounts (as just happened on Forgejo), and (2) anyone could register `whoever@swisscairn.ch` without owning the mailbox — which matters because the admin dashboard (license-server dashboard issue) may key admin access off that domain. ## Proposal — double opt-in - On `/v1/register`: create the account in an **unverified** state, generate a single-use, time-limited verification token (memory or a short-lived DB row), email a confirm link (reuse the existing SMTP path from `email.go` / reset flow). - New `GET/POST /v1/verify?token=…` marks the account verified. - Unverified accounts: allow login but gate the sensitive surfaces (purchase/checkout, license listing) behind verified — or block login entirely until verified. Decide which; blocking login is simpler and closes the bot vector hardest. - Keep the no-existence-oracle posture: the register response is the same whether or not the email already exists ("check your inbox"). - Retention: prune unverified accounts after N days so the table doesn't accumulate bot rows. ## Interaction with admin auth If admin access is domain-based (`@swisscairn.ch`), it MUST additionally require the account be **verified** (or authenticated via Google OAuth, which already proves the mailbox). An explicit allowlist of admin emails sidesteps this, but verification is still the right default for all accounts. ## Notes - Pairs with the CAPTCHA issue — verification stops confirmed-account abuse, CAPTCHA stops the flood at the door. - Stdlib-only stays intact (token = crypto/rand, email via existing SMTP).
Cordy referenced this issue from a commit 2026-08-25 02:29:31 +00:00
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/cairn-license-server#31
No description provided.