Files

116 lines
4.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Authentication & Invites
## Shared database
BincioWiki shares its user database with `bincio_activity`. Both services read from and write to the same SQLite file at `/var/bincio/data/instance.db`. There is no separate user store for the wiki.
The relevant tables:
### `users`
```sql
CREATE TABLE users (
handle TEXT PRIMARY KEY,
display_name TEXT NOT NULL DEFAULT '',
password_hash TEXT NOT NULL,
is_admin INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
wiki_access INTEGER NOT NULL DEFAULT 1,
activity_access INTEGER NOT NULL DEFAULT 0
);
```
`wiki_access = 1` grants access to BincioWiki. `activity_access = 1` grants access to BincioActivity. The two flags are independent; a user can have one, both, or neither.
### `sessions`
```sql
CREATE TABLE sessions (
token TEXT PRIMARY KEY,
handle TEXT NOT NULL REFERENCES users(handle) ON DELETE CASCADE,
created_at INTEGER NOT NULL,
expires_at INTEGER NOT NULL
);
```
Sessions expire after **30 days**. The cookie is `bincio_session`, HTTP-only, `SameSite=Lax`, domain `.bincio.org` (shared across all subdomains).
### `invites`
```sql
CREATE TABLE invites (
code TEXT PRIMARY KEY,
created_by TEXT NOT NULL REFERENCES users(handle) ON DELETE CASCADE,
used_by TEXT REFERENCES users(handle) ON DELETE SET NULL,
created_at INTEGER NOT NULL,
used_at INTEGER,
grants_activity INTEGER NOT NULL DEFAULT 0,
grants_wiki INTEGER NOT NULL DEFAULT 0
);
```
Wiki invites are created with `grants_wiki = 1, grants_activity = 0`. The `grants_wiki` column was added by a migration in `edit/server.py` (`_migrate_db()` runs at sidecar startup).
## Session flow
```
POST /api/auth/login → bcrypt.checkpw → INSERT sessions → Set-Cookie: bincio_session
GET /api/me → SELECT sessions JOIN users → 200 + user JSON | 401
POST /api/auth/logout → DELETE sessions → Clear cookie
```
Every protected Astro page calls `GET /api/me` on load via inline JavaScript. A 401 or 403 response triggers an immediate `window.location.replace('/login/')`.
## Security model
This is **client-side enforcement only**. The static HTML is served publicly by nginx without any HTTP-level authentication. A user without JavaScript, or one who crafts direct HTTP requests, can read the raw HTML.
This is an intentional, documented tradeoff. The content is community memories (cycling routes, gear notes, trip reports) — not financial or medical data. The goal is keeping search engine crawlers and casual visitors out, not resisting determined attackers. All members of the community are trusted.
For future pages that require stricter enforcement, the architecture supports adding an Astro SSR mode with server-side cookie validation before any HTML is rendered. This would require migrating from `output: "static"` to `output: "server"`.
## Invite system
### Creating an invite
Any wiki user can generate an invite link from `/invites/`. The sidecar creates a single-use token:
```
POST /api/invites
→ { "code": "abc123def456ghi7" }
```
The invite URL is `https://wiki.bincio.org/join/?code=<token>`.
### Registration
The invitee visits the URL and fills in a handle, optional display name, and password. On submit:
```
POST /api/auth/register
{ code, handle, display_name, password }
```
The sidecar:
1. Validates the code exists and `used_by` is null.
2. Checks `grants_wiki = 1`.
3. Validates the handle (lowercase, 220 chars, `[a-z][a-z0-9_-]{1,19}`).
4. Validates password length ≥ 8 characters.
5. Checks handle uniqueness.
6. Inserts the user (`wiki_access=1, activity_access=0`) and marks the invite as used — in a single transaction.
7. Creates a session and sets the cookie. The user is immediately logged in.
### Revoking an invite
A user can revoke any of their own unused invites via the `/invites/` page. Used invites cannot be revoked.
### User limits
The `settings` table stores a `max_wiki_users` value (currently `100`). The `/api/auth/register` endpoint reads this value and rejects registration if the current wiki user count has reached the limit.
```sql
SELECT value FROM settings WHERE key = 'max_wiki_users';
-- 100
```