Commit Graph

3 Commits

Author SHA1 Message Date
Scott Idem
8aec6aafcc feat: Google OAuth sign-in + per-user Gemini API key
Users with Google accounts can now sign in without a password.

Auth flow:
- GET /auth/google → Google consent page (CSRF state cookie)
- GET /auth/google/callback → exchange code, lookup user, set JWT
- auth.json gains google_sub + google_email fields
- set_password() no longer overwrites unrelated auth.json fields

Admin setup:
  python manage_passwords.py google-add <username> <email>
  # add GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET to .env

Per-user Gemini key:
- get_user_gemini_key() reads gemini_api_key from auth.json
- orchestrator_engine.run() accepts gemini_api_key param
- orchestrator router passes user's key, falls back to server key

login.html: "Sign in with Google" button above the password form.
manage_passwords.py list: now shows auth method columns (pw / google).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 21:01:52 -04:00
Scott Idem
46b65d087c feat: persona onboarding — invite tokens, self-service setup, persona creation, switcher
New user flow:
  1. Admin: python manage_passwords.py invite <username>  → generates URL
  2. User visits /setup/<token> → sets own password → logged in
  3. User redirected to /setup/persona → fills name/emoji/description
  4. persona_template.py generates all starter files → lands at /{user}/{persona}

Multiple personas:
  - Header persona name is now a clickable dropdown listing all personas
  - "New persona" link at bottom → /setup/persona (available to logged-in users)
  - /api/personas endpoint returns persona list for current session user

New files:
  - persona_template.py: generates IDENTITY/SOUL/PROTOCOLS/USER/HELP.md + data files
  - routers/onboarding.py: /setup/{token}, /setup/persona GET+POST
  - static/setup.html: two-step form (password → persona), emoji picker, mobile-friendly

Updated:
  - auth_utils.py: create_invite(), validate_invite(), consume_invite()
  - manage_passwords.py: invite command with URL output
  - auth_middleware.py: /setup/* prefix is public (invite tokens need no auth)
  - routers/ui.py: /api/personas endpoint; post-login redirect if no personas
  - static/app.js: persona switcher dropdown with navigation + Add persona link
  - static/style.css: .persona-switcher, .persona-dropdown, mobile adjustments

Mobile: login/setup pages are card-centered with responsive padding;
dropdown avoids edge-clipping on narrow screens; logout button stays visible.

All 80 tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 23:10:32 -04:00
Scott Idem
a9bbb668b5 feat: session auth + per-user/persona UI at /{user}/{persona}
Replaces nginx basic auth with a proper per-user session system:

- auth_utils.py: bcrypt password hashing, JWT cookie creation/decode
- auth_middleware.py: validates JWT cookie on all routes except /login,
  /health, /static/, and webhook endpoints (/channels/, /webhook/)
- routers/ui.py: GET /login, POST /login, POST /logout,
  GET /{username}/{persona} — serves index.html with CORTEX_CONFIG injected
- static/login.html: minimal login form (dark theme, matches UI)
- main.py: registers SessionAuthMiddleware + ui.router
- config.py: jwt_secret, jwt_expire_days settings
- manage_passwords.py: CLI tool to set/check/list user passwords
- app.js: reads window.CORTEX_CONFIG (user + persona), sends both on
  every /chat and /orchestrate request; persona name shown in header;
  logout button (⏏) added to header
- requirements.txt: bcrypt, PyJWT, python-multipart
- .env.default: JWT_SECRET, JWT_EXPIRE_DAYS documented
- tests: client fixture injects JWT cookie; security test assertions
  updated for URL-normalized path traversal paths (still secure, codes differ)

All 80 tests pass.

Setup for a new user:
  python manage_passwords.py set scott
  python manage_passwords.py set holly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-20 22:54:12 -04:00