Files
netris-nestri/packages/auth
Wanjohi 355d1492d9 fix(auth): give a sign-in code a budget of guesses and a short life
A six-digit code has a million values, and nothing was counting how many
of them a caller tried. The code travelled in an encrypted cookie the
caller held, verification compared against that cookie, and a wrong
answer simply re-rendered the form. Nobody has to be the person the code
was mailed to: type somebody else's address into the first screen and the
code goes to their mailbox while the cookie stays with you. At that point
the only thing between a stranger and an account is a million requests,
and the constant-time comparison protecting the code was guarding a door
you could just keep knocking on.

Guesses are now counted on the server, under a name that changes with
every code. That placement is the point: a counter kept beside the code,
in the cookie, is a counter the guesser can wind back by replaying an
older copy. Starting over is still allowed and still costs a fresh code
sent to the mailbox being aimed at, which is where somebody notices. A
correct code spends its record too, so its remaining guesses do not carry
into the next one.

The cookie also lived for twenty-four hours, which made the pin a
password with a million possible values and a day to try them. Ten
minutes now, and the code stops being accepted when the clock says so
rather than when the cookie happens to go away.

Resend had no limit either, so the button was a way to mail a stranger as
fast as requests go out. Codes to one address are spaced, and one attempt
at signing in can only ask for so many.

Both refusals say the same thing on purpose. Which of the two it was is a
fact about somebody else's mailbox.
2026-09-05 09:44:32 +03:00
..
2026-08-06 22:13:51 +03:00
2026-08-06 22:13:51 +03:00
2026-08-06 22:32:33 +03:00
2026-08-06 22:13:51 +03:00

packages/auth (@nestri/auth)

Framework-agnostic OpenAuth implementation for Nestri — the OAuth/OIDC issuer, client, subjects, and the login UI. A vendored/forked build of OpenAuth.

What it does

Everything needed to run your own authentication provider:

  • issuer.ts — the authorization server: routes for /authorize, /callback, /token, /userinfo, .well-known/*, plus the login UI (React renderer).
  • client.tscreateClient to verify JWTs against the issuer ("who is this token?").
  • subject.ts — typed JWT subjects (zod schemas for the token payload).
  • provider/* — drop-in OAuth/OIDC providers (steam, discord, github, google, apple, microsoft, slack, spotify, twitch, x, yahoo, facebook, linkedin, cognito, keycloak, jumpcloud, oauth2, oidc, password, ssh, code, arctic).
  • storage/* — persistence adapters for keys/sessions/codes: memory, cloudflare (KV), aws, dynamo.
  • ui/* — the login page components (forms, password, code, theme, CSS).
  • jwt.ts, keys.ts, pkce.ts, random.ts — signing, keypair management, PKCE, randomness.

Usage

Consumed by the apps/auth worker, e.g.:

import { issuer } from '@nestri/auth/index';
import { CloudflareStorage } from '@nestri/auth/storage/cloudflare';
import { SteamProvider } from '@nestri/auth/provider/steam';

The API uses createClient (from @openauth/openauth/client) or the bundled client.ts to verify tokens against the issuer URL.

Scripts

bun test      # run tests
bun run build # build (see script/build.ts)

Note

@openauthjs is the upstream project; this package's exports are meant to be API-compatible with a pinned preference toward tree-shaking-friendly imports. Prefer importing subpaths over the barrel (@nestri/auth/index).