# bincio_wiki — deployment plan ## Architecture overview Three domains, one shared user database: ``` bincio.org — auth hub: login, registration, links to the two apps activity.bincio.org — bincio_activity (moved from bincio.org) wiki.bincio.org — bincio_wiki (new) Shared DB: /var/bincio/data/instance.db ↑ used by all three, lives with bincio_activity's data ``` Login happens at `bincio.org`. The session cookie is set with `domain=.bincio.org` so it is automatically valid on `activity.bincio.org` and `wiki.bincio.org`. No per-app login page needed. Each app's FastAPI validates the shared session token. After login, the `bincio.org` home page shows the apps the user has access to (based on their access flags). If not authenticated, the landing page IS the login form. --- ## User model One unified `users` table with two access flags: | flag | meaning | cap | |------------------|-----------------------------------|-----| | `wiki_access` | can log in to wiki.bincio.org | 100 | | `activity_access`| can log in to activity.bincio.org | 30 | A user can have one or both. Registration is always for wiki first; activity access is granted separately (invite flag or admin toggle). The caps are independent: 100 wiki users total, 30 activity users total. All existing bincio_activity users get `wiki_access=1, activity_access=1`. New wiki-only users get `wiki_access=1, activity_access=0`. --- ## Schema changes (bincio_activity DB) ### New columns on `users` ```sql ALTER TABLE users ADD COLUMN wiki_access INTEGER NOT NULL DEFAULT 1; ALTER TABLE users ADD COLUMN activity_access INTEGER NOT NULL DEFAULT 0; ``` Migration for existing users: ```sql UPDATE users SET wiki_access = 1, activity_access = 1; ``` ### New column on `invites` ```sql ALTER TABLE invites ADD COLUMN grants_activity INTEGER NOT NULL DEFAULT 0; ``` The invite creator chooses whether the invite grants activity access, subject to this rule: **you can only grant access you yourself have.** | Inviter type | Can create wiki invite | Can set grants_activity=1 | |---------------------|------------------------|---------------------------| | Wiki-only member | Yes (up to 3) | No | | Activity member | Yes (up to 3) | Yes — their choice | | Admin | Yes, unlimited | Yes | The API enforces this: `POST /api/invites` returns 403 if the caller tries to set `grants_activity=1` without having `activity_access=1` themselves. The UI hides the toggle entirely for wiki-only users. Caps are enforced at registration time regardless of who issued the invite: if the wiki is at 100 users or activity is at 30 users, registration fails even with a valid unused code. ### Settings table ```sql INSERT OR REPLACE INTO settings VALUES ('max_wiki_users', '100'); INSERT OR REPLACE INTO settings VALUES ('max_activity_users', '30'); -- remove or ignore the old generic 'max_users' key ``` --- ## What needs to be built ### 1. bincio.org — auth hub (changes to bincio_activity) **FastAPI (`bincio_activity`)** - `POST /api/auth/login`: after bcrypt check, also verify the user's access flag for the app they're logging in from (sent as `app` parameter, or inferred from `Referer`). Actually: login at bincio.org grants a general session; the flag check happens at `/api/me` on each subdomain. - `GET /api/me`: add `wiki_access` and `activity_access` to the response. - `POST /api/invites`: accepts optional `grants_activity: bool` field. - Session cookie: change `domain` from unset (host-only) to `.bincio.org` so it propagates to subdomains. **This is the key change.** - Cap logic: registration checks `max_wiki_users` (total users with `wiki_access=1`) and optionally `max_activity_users` if the invite has `grants_activity=1`. **Astro (`bincio.org` landing page)** - The landing page (`/`) becomes: login form if not authenticated, app selector if authenticated. - App selector shows links to `activity.bincio.org` and `wiki.bincio.org` based on the user's access flags returned by `/api/me`. - **Invite management** moves here from bincio_activity. The `/invites/` page stays at `bincio.org` (not at either subdomain) so admins can issue both wiki-only and wiki+activity invites from one place. The invite creation form gets a toggle: "wiki only" (default) vs "wiki + activity". - Invite links always point to `bincio.org/register/?code=XXXXXXXX`. After registration the user gets `wiki_access=1` always, and `activity_access=1` only if the invite had `grants_activity=1`. - The existing `/register/`, `/reset-password/` pages stay at bincio.org. - Remove the activity app content from bincio.org (it moves to the subdomain). **bincio_activity moves to `activity.bincio.org`** - nginx: add `activity.bincio.org` server block (same webroot and proxy as current `bincio.org` block). - bincio.org nginx: strip activity routes (`/u/`, `/activity/`, `/data/`) and serve only the auth hub static files + proxy `/api/` to port 4041. - All internal links in bincio_activity site that are root-relative (`/u/dave`, `/activity/123`) stay as-is since the app now owns its own domain. ### 2. bincio_wiki auth (`edit/server.py`) - **Shared DB**: connect to `/var/bincio/data/instance.db` (configurable via `SHARED_DB_PATH` env var, defaults to `../bincio_activity/data/instance.db` locally). - **`GET /api/me`**: validate session token from `bincio_session` cookie, check `wiki_access=1`, return `{handle, display_name, is_admin}` or 401. - **`POST /api/auth/logout`**: delete session from shared DB. - No `/api/auth/login` in wiki: login happens at `bincio.org`. - All CRUD endpoints (`/pages`, `/stories`) require a valid session with `wiki_access=1`. ### 3. bincio_wiki auth wall (Astro) - **`Base.astro`**: add `fetch('/api/me')` on load → on 401, redirect to `https://bincio.org/login/?next=https://wiki.bincio.org` (or just bincio.org with no next param, since the app selector handles it). - No login page in bincio_wiki — login is centralised at bincio.org. - The `?next=` redirect is optional / nice-to-have for first iteration. --- ## Phase plan ### Phase 0 — Schema migration (local + VPS) - Add `wiki_access`, `activity_access` to users; add `grants_activity` to invites. - Update settings: `max_wiki_users=100`, `max_activity_users=30`. - Migration script: `deploy/migrate.sql`. ### Phase 1 — bincio_activity auth changes - Cookie domain → `.bincio.org`. - `/api/me` response: include access flags. - Login: no flag check (session is general), flag check is per-app at `/api/me`. - Registration: enforce `max_wiki_users` (wiki_access count). If invite has `grants_activity=1`, also enforce `max_activity_users`. - Invite creation: add `grants_activity` field. - On registration: set `wiki_access=1` always, `activity_access=invite.grants_activity`. ### Phase 2 — bincio_wiki FastAPI auth - Connect to shared DB. - Implement `GET /api/me` with `wiki_access` check. - Implement `POST /api/auth/logout`. - Add `require_session()` dependency to all CRUD endpoints. ### Phase 3 — Astro auth wall (bincio_wiki) - `Base.astro`: `/api/me` check → redirect to `bincio.org` on 401. - No login page in wiki. ### Phase 4 — bincio.org landing page - Update home page: login form (unauthenticated) / app selector (authenticated). - Invite form: add activity toggle. - Keep existing register/reset-password pages. ### Phase 5 — nginx migration - Add `activity.bincio.org` server block (certbot for the new subdomain). - Update `bincio.org` block: serve only auth hub, strip activity routes. - Add `wiki.bincio.org` server block. ### Phase 6 — Deploy & verify - Push both apps to VPS. - Run migration SQL on the live DB. - Restart services. - Smoke test: login at bincio.org, verify cookie reaches both subdomains. --- ## Notes - **Local dev**: both apps set `SESSION_DOMAIN` env var; if unset, cookie is host-only (fine for localhost). In production always set `.bincio.org`. - **bincio_activity data dir**: stays at `/var/bincio/data/`. The wiki just opens the DB there; it doesn't own it. - **Wiki content**: lives at `/var/bincio/wiki/` (pages and stories markdown). - **Admin tools**: `is_admin=1` users can toggle access flags on other users via an admin endpoint. First iteration: do it directly in sqlite on the VPS if needed.