Skip to content

feat: add optional authentication with login, registration, and admin dashboard#252

Open
xor-xe wants to merge 3 commits intoHKUDS:devfrom
xor-xe:feature/simple-login-page
Open

feat: add optional authentication with login, registration, and admin dashboard#252
xor-xe wants to merge 3 commits intoHKUDS:devfrom
xor-xe:feature/simple-login-page

Conversation

@xor-xe
Copy link
Copy Markdown

@xor-xe xor-xe commented Apr 6, 2026

Summary

Closes #227.

Implements optional authentication for public deployments. Auth is disabled by default (AUTH_ENABLED=false), so all existing localhost users are completely unaffected. Enabling it requires two env vars (AUTH_ENABLED=true + NEXT_PUBLIC_AUTH_ENABLED=true).

  • Backend: bcrypt password hashing + JWT sessions (python-jose), file-based multi-user store (data/user/auth_users.json) with role/created_at schema, require_auth / require_admin FastAPI dependencies applied to all 14 protected routers and the WebSocket endpoint, new public endpoints (/register, /is_first_user) and admin-only endpoints (/users, /users/{u}/role)
  • Frontend: Next.js middleware for route protection, login page with auto-redirect to /register when no users exist, registration page with first-user admin notice, /admin/users dashboard for role management and deletion, AdminLink and LogoutButton hidden when auth is disabled, credentials: include added to all API fetches, expectJson redirects to /login on 401 instead of throwing
  • Docs: .env.example and README.md updated with auth variables and setup guide

First-time setup (when auth is enabled)

  1. Set AUTH_ENABLED=true and NEXT_PUBLIC_AUTH_ENABLED=true
  2. Navigate to /register — the first user automatically becomes admin
  3. Manage users at /admin/users

Single-user alternative: set AUTH_USERNAME + AUTH_PASSWORD_HASH in .env (no registration needed).

Test plan

  • With AUTH_ENABLED=false (default): app loads normally, no login prompt, no logout/admin buttons visible
  • With AUTH_ENABLED=true and no users: visiting / redirects to /register
  • First registered user gets admin role; subsequent users get user role
  • Login with correct credentials sets dt_token cookie and redirects to app
  • Invalid credentials return 401 on login page
  • Admin at /admin/users can promote/demote and delete other users (not themselves)
  • Non-admin users cannot access /admin/users (redirected)
  • Logging out clears cookie and redirects to /login
  • All API routes return 401 without a valid token when auth is enabled
  • WebSocket connection rejected (code 4001) without valid token when auth is enabled

Made with Cursor

@pancacake
Copy link
Copy Markdown
Collaborator

Thanks for your proposal. This is something we wanna DeepTutor have in the future.

@xor-xe
Copy link
Copy Markdown
Author

xor-xe commented Apr 8, 2026

Understood, thanks, I would advise taking PocketBase in consideration too in case you decide on expanding on this feature from my experience its quite reliable, lightweight and has nice authentication module, the codebase would need refactoring though especially the data storage part

@pancacake
Copy link
Copy Markdown
Collaborator

Understood, thanks, I would advise taking PocketBase in consideration too in case you decide on expanding on this feature from my experience its quite reliable, lightweight and has nice authentication module, the codebase would need refactoring though especially the data storage part

Yeah please go ahead and updating anything that comes in your mind. I'll review this pr several days later. Thanks!

xor-xe added 2 commits April 9, 2026 20:57
Closes HKUDS#227. Auth is disabled by default (AUTH_ENABLED=false) so
localhost usage is unaffected. Set AUTH_ENABLED=true +
NEXT_PUBLIC_AUTH_ENABLED=true to require login when hosting publicly.

Backend
- New deeptutor/services/auth.py: bcrypt password hashing, JWT
  create/decode, multi-user JSON store with role + created_at schema,
  auto-migration of old flat-hash format, first-user → admin bootstrap
- New deeptutor/api/routers/auth.py: require_auth / require_admin
  FastAPI dependencies; public endpoints /login /logout /status
  /register /is_first_user; admin-only /users /users/{u}/role
- deeptutor/api/main.py: Depends(require_auth) applied to all 14
  protected routers
- deeptutor/api/routers/unified_ws.py: cookie-based JWT check before
  ws.accept() when AUTH_ENABLED
- Added bcrypt>=4.0.0 and python-jose[cryptography]>=3.3.0 to
  requirements/server.txt and pyproject.toml extras

Frontend
- web/middleware.ts: route protection; /login and /register are public
- web/lib/api.ts: apiFetch wrapper — credentials:include + 401→login
- web/lib/auth.ts: login/logout/fetchAuthStatus + register() +
  checkIsFirstUser()
- web/lib/admin-api.ts: listUsers / deleteUser / setUserRole
- web/lib/session-api.ts: credentials:include on all fetches;
  expectJson redirects to /login on 401 instead of throwing
- web/app/(auth)/login/page.tsx: auto-redirects to /register when no
  users exist; shows success banner after registration
- web/app/(auth)/register/page.tsx: new registration page with
  first-user admin notice and password confirmation
- web/app/(admin)/admin/users/page.tsx: admin dashboard — user table
  with role toggle and delete; guards against self-demotion/deletion
- AdminLink and LogoutButton hidden when AUTH_ENABLED=false
- .env.example and README.md updated with auth vars and setup guide

Made-with: Cursor
Introduces PocketBase as an optional sidecar for authentication and
session/KB storage, activated only when POCKETBASE_URL is set in .env.
Falls back to the existing SQLite/JSON backend when not configured.

Backend:
- SessionStoreProtocol (typing.Protocol) + get_session_store() factory
- PocketBaseSessionStore: JSONL write-ahead buffer, batch-flush on turn end
- pocketbase_client.py: singleton admin client, 60s in-memory token cache
- auth.py: additive PocketBase path (email-based login/register)
- CORS: explicit origins instead of wildcard (wildcard+credentials fails)
- WebSocket: validate token at connect time when AUTH_ENABLED

Infrastructure:
- docker-compose: pocketbase service with healthcheck + depends_on
- scripts/pb_setup.py: idempotent PocketBase collection bootstrap
- requirements/server.txt: pocketbase>=0.12.0

Frontend:
- Register/login: username field -> email field for PocketBase mode
- auth.ts: normalise FastAPI 422 errors to plain strings
- agents page: guard bots.map() against non-array responses
- settings page: graceful fetch error handling
- *.env.local added to .gitignore

Docs: README PocketBase sidecar setup section added
@xor-xe xor-xe force-pushed the feature/simple-login-page branch from 6814332 to 859f560 Compare April 9, 2026 17:03
@xor-xe
Copy link
Copy Markdown
Author

xor-xe commented Apr 9, 2026

In case you choose to commence with this feature,

it will be optional for users to run PocketBase. If a user chooses not to, they will still have the option to use simple Auth (the one I provided before) or no Auth at all. Should a user choose to run PocketBase, however, they will benefit from highly scalable multi-user concurrency, despite a slight increase in non-streaming transactions. This also opens up future possibility easier OAuth integration and more sophisticated user management through PocketBase. Additionally, data could potentially be shared across multiple DeepTutor instances via a shared PocketBase instance.

It still needs testing in terms of AI features. If you have any questions, please let me know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants