Skip to content

Commit 4811311

Browse files
committed
docs: v0.7.0 stable — multi-tenant migration complete
1 parent fba9743 commit 4811311

4 files changed

Lines changed: 76 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,52 @@ All notable changes to OpenStudy will be documented here.
44
Format loosely follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
55
versions follow [SemVer](https://semver.org/spec/v2.0.0.html).
66

7+
## v0.7.0 — Multi-tenant ready
8+
9+
The multi-tenant migration (Phases 0-7) is complete. OpenStudy can now be
10+
self-hosted as single-user OR run as a multi-user platform from the same
11+
codebase. All credentials are per-user; the operator's bot, password, and
12+
chat ID never serve as fallbacks for other users.
13+
14+
### Highlights
15+
16+
- **Schema**: every owned table has a `user_id` FK with composite PKs/FKs
17+
enforcing per-user data integrity. Cross-user FK violations are
18+
structurally impossible.
19+
- **RLS policies**: shipped on every owned table. Inert under the current
20+
BYPASSRLS connection role; flip to a non-bypass role to activate
21+
defense-in-depth (see `docs/RLS.md`).
22+
- **Services**: every public service function takes `user_id` and filters
23+
by it. Storage paths resolve under `STUDY_ROOT/<user_id>/...`.
24+
- **Auth**: home-grown signup + email verification + password reset.
25+
Login is `email + password` only — `APP_PASSWORD_HASH` is bootstrap-only.
26+
- **MCP**: bearer tokens bind to user_id; tools operate on the bearer's
27+
data only.
28+
- **Per-user secrets**: Telegram credentials live in `user_secrets`
29+
(Fernet-encrypted), configured via Settings UI. No env-var fallbacks
30+
for `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, `TELEGRAM_WEBHOOK_SECRET`.
31+
- **Tests**: 318 passing (was 213 pre-migration).
32+
33+
### Migration notes
34+
35+
- After upgrade, run `./deploy.sh` once — it applies all 6 phase migrations
36+
in order, runs `scripts/migrate_study_root.sh` (FS layout), and
37+
`scripts/seed_operator_password.py` (operator password).
38+
- Existing operators: log in with `OPERATOR_EMAIL` (default `operator@local`)
39+
+ the password whose hash was in `APP_PASSWORD_HASH`. Reconfigure your
40+
Telegram bot via Settings → Telegram (the env vars no longer work).
41+
- Self-hosters running solo: nothing changes day-to-day. You ARE the
42+
operator user. Multi-user signups are gated by `SIGNUPS_ENABLED=false`
43+
by default; flip it on if you want to invite others.
44+
45+
### Deferred
46+
47+
- Stripe billing — separate batch after this lands.
48+
- n8n Moodle scraper multi-tenancy — operator-only today; per-user Moodle
49+
is a follow-up when first hosted user requests it.
50+
- Connection role flip to non-BYPASSRLS — operational; documented in
51+
`docs/RLS.md`; flip when ready.
52+
753
## v0.7.0-pre.7 (unreleased) — Multi-tenant Phase 6
854

955
### Per-user secrets

INSTALL.md

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@ chmod 600 .env .env.docker
8585
```
8686

8787
The other variables in `.env` are optional and document themselves —
88-
Telegram bot credentials, the internal API secret used by webhooks, the
89-
public URL the app advertises in OAuth flows.
88+
the internal API secret used by webhooks, the public URL the app
89+
advertises in OAuth flows. Telegram credentials are configured per-user
90+
via **Settings → Telegram** after first login (no env vars needed).
9091

9192
---
9293

@@ -120,6 +121,23 @@ curl http://127.0.0.1:8000/api/health
120121
You should also see the running containers (`openstudy`, `openstudy-postgres`,
121122
`openstudy-frontend`) when you run `./deploy.sh --status`.
122123

124+
### First-time operator login
125+
126+
After running `./deploy.sh`:
127+
128+
1. Set `APP_PASSWORD_HASH` in `.env.docker` to your argon2id-hashed password
129+
(use `uv run python -m app.tools.hashpw 'your-password'` to generate).
130+
2. Set `OPERATOR_EMAIL` to your email (or leave the default `operator@local`).
131+
3. Run `./deploy.sh` — it invokes `scripts/seed_operator_password.py` which
132+
sets `users.password_hash` for the operator user from your env var.
133+
4. Log in at `https://your-domain/login` with `OPERATOR_EMAIL` + your password.
134+
5. Configure your Telegram bot in **Settings → Telegram** (per-user; no env
135+
vars needed).
136+
137+
`APP_PASSWORD_HASH` is bootstrap-only — the seed script reads it once at
138+
deploy time. Subsequent logins authenticate against `users.password_hash`
139+
in the database.
140+
123141
### Restoring data into a fresh box
124142

125143
If you're moving from another deployment (or restoring a backup), apply
@@ -305,11 +323,13 @@ and is readable by the container. `docker exec openstudy ls /opt/courses`
305323
should show your course tree.
306324

307325
**Login returns 401 with the right password.**
308-
Re-hash the password and update `APP_PASSWORD_HASH` in `.env`. Common
309-
gotcha: the hash starts with `$argon2id$…` — those `$` characters are
310-
literal, not env interpolation. The compose `env_file: format: raw`
311-
directive prevents mangling, but if you've manually exported the variable
312-
in a shell, the `$` chars need to be single-quoted.
326+
Re-hash the password, update `APP_PASSWORD_HASH` in `.env.docker`, then
327+
run `./deploy.sh` so `scripts/seed_operator_password.py` writes the new
328+
hash into `users.password_hash`. Common gotcha: the hash starts with
329+
`$argon2id$…` — those `$` characters are literal, not env interpolation.
330+
The compose `env_file: format: raw` directive prevents mangling, but if
331+
you've manually exported the variable in a shell, the `$` chars need to
332+
be single-quoted.
313333

314334
**MCP returns 401 from a Claude client.**
315335
The OAuth token cached by the client expired or was revoked. Reconnect:

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ The dashboard is where I see things. Claude is how I edit them. Same database be
114114

115115
The MCP server ships with **44 tools — anything you can do in the UI, Claude can do too**. Create a study topic, mark something studied, upload a file, render a PDF as images, whatever.
116116

117+
- **Multi-tenant SaaS-capable:** per-user data isolation enforced at both the service layer (WHERE filters) and the schema layer (composite FKs + RLS policies). Self-host as single-user or invite many users — same codebase.
118+
117119
Plug it into Claude.ai as a custom connector (full OAuth 2.1) and those tools are live in **Claude Code on your laptop, claude.ai in your browser, and the Claude iOS app on your phone**. Open Claude anywhere and it has the same view of your coursework that you do.
118120

119121
## You'll need

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "openstudy-api"
3-
version = "0.6.0"
3+
version = "0.7.0"
44
description = "FastAPI backend for the OpenStudy app"
55
readme = "README.md"
66
requires-python = ">=3.12"

0 commit comments

Comments
 (0)