Sessions without sign-up
Nobody signs up. The first domain you enter creates an account and signs that browser in with one cookie. This chapter is how that works, what each part buys, and what it costs.
Why there are accounts at all
The proof that matters is in DNS, not in a password, so there's nothing to sign up for. But "only one account can own a domain at a time" needs an account to point at, and your claims have to come back when you do. So accounts stay and sign-up goes. An is the link between a browser and an account.
A first claim signs the browser in
Only the hash is stored
What each cookie attribute buys
Who gets in where
Two things here are deliberate. Listing claims with no session returns an empty list, because a new visitor having no domains isn't an error. And visiting the site writes nothing: only creating a claim makes an account.
The per-IP limit, and what it costs
Each account can start 20 claims an hour. Without sign-up, a client could get around that by throwing its cookie away and becoming a new account each time. So new sessions are limited to 10 an hour per IP address. It's the only thing in the way.
| Detail | What it means |
|---|---|
| The key is the first address in x-forwarded-for | With no such header, every request shares the key sessions:unknown. |
| It's only spent when a session is made | A browser that already has a cookie never touches it. |
| Everyone behind one address shares it | An office or a mobile carrier can look like one IP. The eleventh new visitor that hour gets a 429. |
| The counter lives in Postgres | Every function instance shares one tally in rate_limit_hits, and an advisory lock per key stops two requests both reading 'under the limit'. It used to be in memory, where each instance counted on its own. |
What this design costs
- Clearing cookies loses access from that browser. The domain stays verified and the daily re-checks carry on. Claiming it again from the new session and passing a check moves ownership over, because DNS is the authority.
- No second device yet. The table allows many sessions per account, and nothing creates the second one. It matters most on iPhone, where an installed web app has its own cookies.
- An account is not a person. Every account is named "Guest". Nothing here should be used to show a name or to decide what a team is.
- There's no sign-out. No route deletes a session, and expired rows aren't cleaned up. They just stop matching.
What else was considered
| Option | What it would buy | Why not |
|---|---|---|
| A signed cookie holding the account id | No table to keep | Needs a server secret, and one session can't be revoked without changing it for everyone |
| A session column on accounts | Simpler today | One account could never be open on two devices |
| Magic-link email sign-in | A real identity, and it fits this same table | Puts an inbox between a visitor and their first claim. Left for later |
| The claim URL as the credential | Nothing to store at all | Anyone the link is forwarded to could get a new code or stop the claim. Sharing needs its own, weaker link |
The decision is written up in docs/decisions/0008-sessions-without-sign-up.md.