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>
This commit is contained in:
Scott Idem
2026-03-27 21:01:52 -04:00
parent 62fde62653
commit 8aec6aafcc
10 changed files with 376 additions and 21 deletions

View File

@@ -56,6 +56,7 @@ async def run(
system_prompt: str = "",
session_messages: list[dict] | None = None,
respond_with_claude: bool = True,
gemini_api_key: str | None = None,
) -> OrchestratorResult:
"""
Run the full orchestration loop for a task.
@@ -66,17 +67,19 @@ async def run(
session_messages: Prior conversation history for session continuity
respond_with_claude: If False, return Gemini's summary as the response (useful for
background/cron tasks where a polished reply isn't needed)
gemini_api_key: Per-user Gemini API key (falls back to GEMINI_API_KEY in .env)
Returns:
OrchestratorResult with response, tool call log, backend used, and Gemini summary
"""
if not settings.gemini_api_key:
api_key = gemini_api_key or settings.gemini_api_key
if not api_key:
raise RuntimeError(
"GEMINI_API_KEY not set — orchestrator requires Gemini API. "
"Get a free key at https://aistudio.google.com/apikey and add it to .env"
"No Gemini API key available — set GEMINI_API_KEY in .env or add a personal key "
"via: manage_passwords.py gemini-key <username> <key>"
)
client = genai.Client(api_key=settings.gemini_api_key)
client = genai.Client(api_key=api_key)
# Seed Gemini with the task — include recent session context if available
task_with_context = _build_task_prompt(task, session_messages)