feat(oauth): generische OAuth2-Pipeline ueber RVS-Callback (Spotify/Google/GitHub/Strava/MS)
Bisher musste Stefan bei OAuth-Flows manuell den Auth-Code aus der Browser-URL kopieren (redirect_uri war localhost). Jetzt: RVS hat einen HTTP-Listener auf demselben Port wie der WebSocket, Provider redirecten nach Auth zu https://{RVS_HOST}/oauth/callback/{service}, RVS broadcastet, aria-bridge forwarded, Brain matched state + tauscht code gegen Token. Token-Refresh laeuft automatisch. - rvs/server.js: hybrid http.createServer + WebSocketServer{noServer}. Route GET /oauth/callback/{service}, broadcast oauth_callback an alle Raeume, schoene Dark-Mode-HTML-Antwort an den Browser (Auto-Close 4s). - bridge/aria_bridge.py: empfaengt oauth_callback, POSTet an Brain /internal/oauth-callback. - aria-brain/oauth.py: neuer Manager. Pending-Store mit state+TTL, Token-Exchange (Basic-Auth oder Body je nach Provider), persistente Speicherung in /shared/config/oauth_tokens.json (mode 0600), Token-Refresh wenn <60s Restzeit. Vordefinierte Configs fuer Spotify, Google, GitHub, Strava, Microsoft. - aria-brain/agent.py: META-Tools oauth_authorize / oauth_get_token / oauth_revoke. - aria-brain/prompts.py: System-Prompt-Block zeigt ARIA die feste Callback-URL als Quelle der Wahrheit + aktuelle Service-States. - aria-brain/main.py: HTTP-Endpoints /oauth/services, /oauth/apps, /oauth/authorize, /oauth/{service}/revoke, /internal/oauth-callback. - diagnostic: neue Section "OAuth-Apps". Pro Service Karte mit Status, client_id + client_secret (Passwort-Toggle), Speichern + Autorisieren- Buttons. Authorize oeffnet Provider-Auth in neuem Tab. - docker-compose.yml: brain-env um RVS_HOST + RVS_PORT_PUBLIC + RVS_TLS ergaenzt (Brain braucht die Werte zum Bau der Callback-URL). - .env.example: RVS_PORT_PUBLIC + Brain-Timeout-Vars (PROXY_TIMEOUT_SEC + Connect/Write/Pool) dokumentiert. - README.md: OAuth-Pipeline + ARIA-Live-Mirror in Diagnostic-Section, OAuth-Apps in Einstellungen-Tab erwaehnt. - issue.md: OAuth-Pipeline + Brain-Timeout-Fix als erledigt dokumentiert. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+71
-1
@@ -240,6 +240,63 @@ def build_triggers_section(
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_oauth_section(oauth_services: list[dict] | None,
|
||||
callback_host: str = "",
|
||||
callback_port: str = "443",
|
||||
callback_tls: bool = True) -> str:
|
||||
"""Block fuer den System-Prompt: zeigt ARIA welche externen Services
|
||||
via OAuth verfuegbar sind, welche schon authentifiziert sind, und welche
|
||||
Callback-URL beim Provider eingetragen werden muss."""
|
||||
scheme = "https" if callback_tls else "http"
|
||||
if callback_host:
|
||||
if (callback_tls and callback_port == "443") or (not callback_tls and callback_port == "80"):
|
||||
base = f"{scheme}://{callback_host}/oauth/callback/<SERVICE>"
|
||||
else:
|
||||
base = f"{scheme}://{callback_host}:{callback_port}/oauth/callback/<SERVICE>"
|
||||
else:
|
||||
base = "<nicht konfiguriert — RVS_HOST in brain env fehlt>"
|
||||
|
||||
lines = [
|
||||
"## OAuth externe Services",
|
||||
"",
|
||||
"Du kannst Spotify, Google, GitHub, Strava, Microsoft (und custom-konfigurierte) "
|
||||
"Services via OAuth2 ansprechen. Workflow ist IMMER:",
|
||||
"1. `oauth_get_token(service)` versuchen — Token vorhanden? → benutzen.",
|
||||
"2. Wirft 'Kein Token gespeichert'? → `oauth_authorize(service)` aufrufen, URL an Stefan, warten, dann nochmal `oauth_get_token`.",
|
||||
"",
|
||||
f"**Callback-URL (fest, NICHT raten):** `{base}`",
|
||||
"Diese URL muss Stefan EINMAL pro Service im Provider-Dashboard als gueltige "
|
||||
"Redirect-URI eintragen. Sie ist hardcoded an die RVS-Infrastruktur gebunden "
|
||||
"und aendert sich nicht — auch nicht wenn Du als Brain neu aufgesetzt wirst.",
|
||||
"",
|
||||
"**NICHT** versuchen client_id / client_secret selbst zu generieren oder zu "
|
||||
"raten. Wenn nicht eingetragen → Stefan sagen er soll es in Diagnostic > "
|
||||
"OAuth-Apps machen.",
|
||||
]
|
||||
if oauth_services:
|
||||
lines.append("")
|
||||
lines.append("**Aktuelle Service-Status:**")
|
||||
for s in oauth_services:
|
||||
name = s.get("service", "?")
|
||||
configured = s.get("configured", False)
|
||||
auth = s.get("authenticated", False)
|
||||
remain = s.get("expiresInSec")
|
||||
parts = []
|
||||
if not configured:
|
||||
parts.append("Credentials fehlen")
|
||||
elif not auth:
|
||||
parts.append("nicht authentifiziert")
|
||||
else:
|
||||
if remain is None:
|
||||
parts.append("authentifiziert")
|
||||
elif remain > 0:
|
||||
parts.append(f"authentifiziert, Token gueltig noch {remain}s")
|
||||
else:
|
||||
parts.append("Token abgelaufen (wird automatisch refresht)")
|
||||
lines.append(f"- `{name}`: {' / '.join(parts)}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def build_flux_section(flux_config: dict) -> str:
|
||||
"""Block fuer den System-Prompt: aktuelle Diagnostic-Settings fuer
|
||||
Bildgenerierung (Default-Modell + User-konfigurierbare Keywords).
|
||||
@@ -279,8 +336,12 @@ def build_system_prompt(
|
||||
condition_vars: List[dict] | None = None,
|
||||
condition_funcs: List[dict] | None = None,
|
||||
flux_config: dict | None = None,
|
||||
oauth_services: list[dict] | None = None,
|
||||
oauth_callback_host: str = "",
|
||||
oauth_callback_port: str = "443",
|
||||
oauth_callback_tls: bool = True,
|
||||
) -> str:
|
||||
"""Kompletter System-Prompt: Hot + Cold + Skills + Triggers + FLUX."""
|
||||
"""Kompletter System-Prompt: Hot + Cold + Skills + Triggers + FLUX + OAuth."""
|
||||
parts = [build_hot_memory_section(pinned), "", build_time_section()]
|
||||
if skills:
|
||||
parts.append("")
|
||||
@@ -291,6 +352,15 @@ def build_system_prompt(
|
||||
if flux_config is not None:
|
||||
parts.append("")
|
||||
parts.append(build_flux_section(flux_config))
|
||||
# OAuth-Block bauen wir nur wenn RVS_HOST konfiguriert ist (sonst hat
|
||||
# die Callback-URL keinen Sinn). Sonst lassen wir den Block weg statt
|
||||
# ARIA eine "<nicht konfiguriert>"-URL zu zeigen.
|
||||
if oauth_callback_host:
|
||||
parts.append("")
|
||||
parts.append(build_oauth_section(oauth_services,
|
||||
callback_host=oauth_callback_host,
|
||||
callback_port=oauth_callback_port,
|
||||
callback_tls=oauth_callback_tls))
|
||||
if cold:
|
||||
parts.append("")
|
||||
parts.append(build_cold_memory_section(cold))
|
||||
|
||||
Reference in New Issue
Block a user