Skip to content

fix(auth)!: give no session until a required email is verified - #143

Merged
juicycleff merged 4 commits into
mainfrom
fix/verify-before-session
Nov 2, 2026
Merged

juicycleff merged 4 commits into
mainfrom
fix/verify-before-session

Conversation

@juicycleff

@juicycleff juicycleff commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

With auth.require_email_verification on, you could sign up, open the app in another tab and use it without ever entering the code. Sign-up minted a session for every new account and set the cookie, and only password sign-in checked verification. So the UI said "verify your email" while the cookie already worked.

What changes

While verification is required, Engine.SignUp creates the account, sends the code and returns no session. Over HTTP, /v1/signup answers 201 with the user, empty tokens and no cookie. /v1/verify-email already signs the user in once the code is right, so that's the way in.

IssueSession refuses an unverified email too. Every sign-in method mints its session there, so passkeys, magic links and social sign-in follow the same rule as passwords. Accounts with no email (phone, anonymous) aren't affected, and the verify-email paths mark the user verified before they sign anyone in.

The rule lives in one place now, EmailVerificationRequired. Sign-in worked it out from the dynamic setting, then the app's override, then the environment, while the client config the UI reads only looked at the environment's SkipEmailVerification. That meant the UI could ask for a code the server didn't need (which is how this showed up in Loomplane) or skip one it did. Sign-in, sign-up, IssueSession and the client config all ask the same function.

Two smaller fixes ride along. The duplicate-email decoy returns the same pending shape as a real sign-up when verification is on, which its own comment had been waiting for. And the subscription plugin read the app ID off the session in OnAfterSignUp and bailed out without one; it reads it off the user now.

Herald

This also moves Authsome to Herald v1.10.0, which has the new file driver, so you can write mail to a folder in development. Herald needs forge v1.13.2, grove v1.10.1, chronicle v1.9.1 and go-utils v1.5.0, so those come along. Loomplane already runs that exact set, and the whole suite passes on it.

UI dependencies

CI's pnpm audit --audit-level=high has failed on main since 1a2d53f, so it failed here too. The third commit clears it: Storybook 8.6.19, Next 16.4.1, Vite 6.4.4 under Storybook, undici 7.30.1 and zod 4.6.6, all on the same major. The four packages that run Vitest now declare vite ^8.2.4 themselves, because pnpm kept auto-installing Vitest's peer at 8.2.2 and no override reached it. Build, typecheck, lint and tests pass locally, and six moderate advisories are left under the threshold.

Breaking

While verification is required, /v1/signup returns empty tokens and Engine.SignUp returns a nil session. If you relied on getting a session straight from sign-up, sign the user in from /v1/verify-email instead, or turn the setting off. The UI packages already cope: signUp() moves to verification_pending when verification is required.

Setup

The console's first-run setup writes no cookie until the owner verifies. That matches the rule from e0286f4: whoever reaches a fresh deployment first can't take it with an address they don't control. If your app already proves the operator some other way, a setup token for example, you can verify the owner yourself with AdminUpdateUser and then call IssueSession. That's what Loomplane does.

Checked

  • New tests cover no session from sign-up until verified, the IssueSession gate and accounts with no email, one rule for enforcement and the client config, and the HTTP contract (no token, no cookie, a matching decoy).
  • Four existing tests assumed sign-up always hands back a session. The DPoP provider test relaxes verification now, since it's about DPoP. The setup test expects no cookie before verification, testutil.CreateUser issues a session after verifying, and the SDK cookie tests sign in a verified user.
  • go test ./... passes, and golangci-lint is clean on the changed packages.
  • Loomplane's setup, sign-up gate and session integration tests pass against this branch, including a new one that fails on v1.8.3 with this exact bug.
  • The getting-started guide walks through sign up, verify, then /me.

Sign-up minted a session for every new account, so with
auth.require_email_verification on you could sign up, open another tab
and use the app before entering the code. The UI showed "verify your
email" while the cookie already worked.

Sign-up now creates the account and sends the code, and hands out no
session or cookie while the email has to be verified. IssueSession
refuses an unverified email too, so passkeys, magic links and social
sign-in follow the same rule. Accounts with no email aren't affected.

The rule itself lives in one place, EmailVerificationRequired. Sign-in
and the client config used to resolve it differently, so the UI could
ask for a code the server didn't need, or the other way round. The
duplicate-email decoy now matches a pending sign-up exactly.

BREAKING CHANGE: while verification is required, /v1/signup and
Engine.SignUp return the user with no session (empty tokens over HTTP,
a nil session in Go). Sign the user in from /v1/verify-email, which
returns a session once the code is right, or turn the setting off.
Herald v1.10.0 brings the file driver, so an app on Authsome can write
mail to a folder in development. It needs forge v1.13.2, grove v1.10.1,
chronicle v1.9.1 and go-utils v1.5.0, which come along with it; those
are the versions Loomplane already runs.
CI's pnpm audit --audit-level=high has failed on main since 1a2d53f.
This clears it: Storybook 8.6.19, Next 16.4.1 in the Next.js package,
Vite 6.4.4 under Storybook, undici 7.30.1 and zod 4.6.6. All are patch
or minor releases on the same major.

Vitest's Vite was the stubborn one. pnpm installs it as an automatic
peer and kept the 8.2.2 in the lockfile, which no override reached, so
the four packages that run Vitest now declare vite ^8.2.4 themselves.
It's their peer anyway. Six moderate advisories remain, under the
audit's threshold.
govulncheck flags GO-2026-6809 in crypto/x509 on go1.26.9: the SMTP
mailer reaches certificate verification through tls.Dialer. Go 1.26.10
fixes it. CI's setup-go reads the version from go.mod, so this is the
whole change.
@juicycleff
juicycleff merged commit 3bd1154 into main Nov 2, 2026
21 checks passed
@juicycleff
juicycleff deleted the fix/verify-before-session branch November 2, 2026 22:36
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.

1 participant