Setup
Pass asecret and a list of users to new AwaitIO():
app.ts
The app refuses to start when the secret is too short, an email is listed twice or a hash is not one from Create new account.
The hashes belong in your code, next to the users they log in: a hash cannot be turned back into the password. The secret does not: keep it in an environment variable or your secret manager, never
in the repository.
Generate a secret
Any random string of at least 32 characters works. This prints one:SESSION_SECRET (or wherever your app reads it from).
Create a password hash
The login page has a Create new account link (/access). Someone who should get access fills in their email, name and a password of at least 10 characters, and gets one line to copy:
users. The line holds only a hash, so it can go by email or chat; their password never reaches you and AwaitIO stores nothing. The page works without a login, and the server allows a few requests per address before it asks to wait.
Manage users
Users live in your code, so every change is an edit and a restart.Add a user
Add a user
- The new user clicks Create new account on the login page and sends you the line it gives them.
- Paste it into
users. - Restart the server. The user can log in.
Change a password
Change a password
- The user makes a new line with Create new account, using their new password.
- Replace their
passwordHashwith the one from the line and restart.
Remove a user
Remove a user
Remove the user from
users and restart. Their cookie no longer matches anyone, so the next request returns them to the login page.Log everyone out
Log everyone out
Generate a new secret, replace
secret and restart. Every existing cookie stops working. Use this when you suspect a cookie or the secret has leaked.How login works
1
Login page
Without a valid session, the UI shows the login page and every API call except login returns
401.2
Password check
The server hashes the submitted password with the salt and parameters stored in the user’s hash and compares the result in constant time. An unknown email is checked against a dummy hash, so it
takes as long as a wrong password and does not reveal which emails exist.
3
Session cookie
On success the server sets the
awaitio_session cookie: the email and an expiry, signed with the secret and the user’s password hash. It is valid for 7 days, then the user logs in again.4
Every request
The server checks the signature, the expiry and that the user is still listed. If any of it fails, the user is back on the login page.
5
Log out
The Log out button in the header deletes the cookie in that browser.
Nothing is stored on the server
Sessions are stateless: the cookie carries everything the server needs. A restart or a deploy keeps everyone logged in, and there is nothing to clean up. The flip side is that a single session cannot be revoked on its own. To end sessions early, change the user’s password (ends that user’s sessions) or the secret (ends all of them).Password hashes
Hashes use scrypt with a random 16-byte salt. One hash takes about 100 ms, so guessing passwords from a leaked hash is slow too. The format isscrypt$N$r$p$salt$hash: the cost parameters travel with each hash, so they can be raised in a later version without breaking the hashes already in your configuration.
Too many failed attempts
After 10 failed logins for one email within 15 minutes, that email is locked until the 15 minutes run out and the login page shows Too many failed attempts. A successful login resets the count. The count is kept in memory, so a restart clears it.Cookie and requests from other sites
The cookie isHttpOnly, so scripts on the page cannot read it, and SameSite=Strict, so the browser does not send it with requests that another site starts. It is marked Secure when the request came over HTTPS, directly or through a proxy that sets X-Forwarded-Proto. Every API request that changes something, the login included, must come from the AwaitIO UI itself; requests started by another origin get 403. So another site cannot act as a logged-in user, nor log the browser into its own account.
The user in an action
The handler gets the user who started the run inctx.user, see User.
Exposing the server
By defaultapp.listen() binds to 127.0.0.1, so only the machine the server runs on can reach it. To serve other people, put it behind a reverse proxy with HTTPS and pass host: '0.0.0.0' to listen() when the proxy runs on another machine or container. Without HTTPS the password and the cookie travel in plain text.