From 68cbd735890ee1d6edf03caf1f74cb817ff46360 Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Mon, 11 May 2026 21:26:29 +0200 Subject: [PATCH] google keys transferred to account poweron.center.ai --- TOPICS.md | 2 + c-work/_CHANGELOG.md | 1 + d-guides/deployment/poweron-sec.kdbx | Bin 23150 -> 23294 bytes d-guides/google-oauth-setup.md | 2 + d-guides/google-registration-checklist.md | 116 ++++++++++++++++++ .../microsoft-entra-registration-checklist.md | 113 +++++++++++++++++ 6 files changed, 234 insertions(+) create mode 100644 d-guides/google-registration-checklist.md create mode 100644 d-guides/microsoft-entra-registration-checklist.md diff --git a/TOPICS.md b/TOPICS.md index a1b5f03..eb4ad84 100644 --- a/TOPICS.md +++ b/TOPICS.md @@ -71,6 +71,8 @@ Lade immer zuerst diese Datei. Dann gezielt die passende(n) Referenz-Datei(en). | Dev-Setup | d-guides/dev-setup.md | Lokale Umgebung starten | | Secrets-Verschluesselung | d-guides/encrypt-env-secrets.md | Env-Dateien verschluesseln | | Google OAuth | d-guides/google-oauth-setup.md | OAuth Auth/Data Apps einrichten (inkl. Calendar/Contacts-Scopes + Reconnect-Hinweis) | +| Google Portal + Env-Checkliste | d-guides/google-registration-checklist.md | GCP-Projekt, APIs, Redirects, Env-Variablen, Code-Referenzen | +| Microsoft Entra + Env-Checkliste | d-guides/microsoft-entra-registration-checklist.md | App registration, Graph delegated permissions, Redirects, Env-Variablen | | Infomaniak Token-Setup | d-guides/infomaniak-token-setup.md | Personal Access Token im Infomaniak-Manager fuer kDrive/Calendar/Contacts erzeugen | | Security-Migration | d-guides/security-migration-guide.md | JWT Cookie Migration | | Doc-Sync Cursor-Rule | d-guides/cursor-doc-sync.md | Installation, Regel-Quelle `doc-sync.mdc`, Doku-Workflow | diff --git a/c-work/_CHANGELOG.md b/c-work/_CHANGELOG.md index e1c87fb..557d5b9 100644 --- a/c-work/_CHANGELOG.md +++ b/c-work/_CHANGELOG.md @@ -14,6 +14,7 @@ Skip: reine Refactors, Formatting, Lint, Dep-Bumps, Test-only, Wiki-Tippfehler. ## 2026-05-11 +- 2026-05-11 | docs | wiki | d-guides: `google-registration-checklist.md`, `microsoft-entra-registration-checklist.md` (Vendor-Registrierung + Gateway-Env/Code-Referenzen); TOPICS ergänzt. - 2026-05-11 | feat | gateway, frontend-nyla, wiki | TeamsBot: `GET /api/teamsbot/{instanceId}/dashboard/stream` (SSE dashboardState 3s/20s); dashboard consumes EventSource + reconnect; module tiles navigate to `modules?moduleId=` with expand + scroll highlight; canonical `b-reference/teams-bot/architecture.md`, TOPICS, `b-reference/frontend-nyla/architecture.md` (Teams Bot UI row). (c-work: 2-build/2026-04-teamsbot-greenfield-ia-and-live-update.md) ## 2026-05-10 diff --git a/d-guides/deployment/poweron-sec.kdbx b/d-guides/deployment/poweron-sec.kdbx index e514c2f76054c69b55cd36b3933674dc0441d1ed..68e5baef4795c2281bb29cdda77a633cab36cb03 100644 GIT binary patch literal 23294 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 Njd0o_oxBWW#u5@bP8a|H literal 23150 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 ZLfyWkC;g|p$gGN&u1R(4P2hTBnXQ*x3}yfT diff --git a/d-guides/google-oauth-setup.md b/d-guides/google-oauth-setup.md index 051ee5b..64531b9 100644 --- a/d-guides/google-oauth-setup.md +++ b/d-guides/google-oauth-setup.md @@ -3,6 +3,8 @@ ## Overview This guide explains how to set up Google OAuth 2.0 authentication for the Porta application. +**Checkliste (Google Cloud + Gateway-Env):** [google-registration-checklist.md](google-registration-checklist.md). + ## Prerequisites - A Google account - Access to Google Cloud Console (https://console.cloud.google.com/) diff --git a/d-guides/google-registration-checklist.md b/d-guides/google-registration-checklist.md new file mode 100644 index 0000000..bd9e182 --- /dev/null +++ b/d-guides/google-registration-checklist.md @@ -0,0 +1,116 @@ +# Checkliste: Google Cloud — PORTA (OAuth + Speech) + +Operative Liste: **was im Google Cloud Projekt** (z. B. Organisation `poweron-center-ai-org`, Projekt **PowerOn Porta**) **eingerichtet** werden muss und **welche Gateway-Variablen/Code-Stellen** dazu passen. + +Detaillierte OAuth-Schritte und Troubleshooting: [`google-oauth-setup.md`](google-oauth-setup.md). + +--- + +## 1) Google Cloud Console — APIs aktivieren + +Projekt auswählen → **APIs & Services** → **Library**. + +| API | Wofür im Gateway | +|-----|------------------| +| **Gmail API** | Mail-Connector (readonly-Scope) | +| **Google Drive API** | Drive-Connector (readonly) | +| **Google Calendar API** | Kalender-Connector (readonly) | +| **People API** | Kontakte (readonly) | +| **Cloud Speech-to-Text API** | STT (`/voice-google/stt/*`) | +| **Cloud Text-to-Speech API** | TTS über `ConnectorGoogleSpeech` | + +--- + +## 2) OAuth-Zustimmungsbildschirm + +**APIs & Services** → **OAuth consent screen**. + +- App-Name, Support-Mail, Developer Contact wie von Google gefordert. +- **Scopes** müssen zur Code-Basis passen (Single Source of Truth): + +`gateway/modules/auth/oauthProviderConfig.py` → `googleAuthScopes`, `googleDataScopes` + +Kurzreferenz: + +| Zweck | Scopes | +|-------|--------| +| Nur Login (**Auth-App**) | `openid`, `userinfo.email`, `userinfo.profile` | +| Datenverbindung (**Data-App**) | wie Auth **plus** `gmail.readonly`, `drive.readonly`, `calendar.readonly`, `contacts.readonly` | + +Hinweis: Nach **Scope-Erweiterung** müssen Nutzer Google-Verbindungen in der UI **Reconnect** auslösen (siehe `google-oauth-setup.md`). + +--- + +## 3) OAuth 2.0 Client(s) — „Web application“ + +**APIs & Services** → **Credentials** → **Create credentials** → **OAuth client ID**. + +Das Gateway unterstützt **getrennte Auth- und Data-Clients** (unterschiedliche Client-IDs) oder **einen gemeinsamen Client** (in den Envs sind ID/Secret für Auth und Data oft identisch). + +**Authorized redirect URIs** — pro öffentlich erreichbare Gateway-Basis-URL **zwei** Einträge (Pfad exakt, inkl. `http`/`https`): + +| Pfad | Verwendung | +|------|------------| +| `{origin}/api/google/auth/login/callback` | Login-Flow (`Service_GOOGLE_AUTH_REDIRECT_URI`) | +| `{origin}/api/google/auth/connect/callback` | Connect/Reconnect (`Service_GOOGLE_DATA_REDIRECT_URI`) | + +**In diesem Repo typische Origins** (jeweils beide Callbacks eintragen): + +- Lokal: `http://localhost:8000` +- INT: `https://gateway-int.poweron.swiss` +- PROD: `https://gateway-prod.poweron.swiss` +- Forgejo/ALT-Prod (falls genutzt): `https://api.poweron.swiss` — in `env-gateway-prod-forgejo.env` Google `*_REDIRECT_URI` ggf. noch setzen. + +Backend-Routing: `gateway/modules/routes/routeSecurityGoogle.py`. + +--- + +## 4) Speech / Voice (ohne User-OAuth) + +**Credentials** → **API key** (empfohlen:auf Speech + TTS beschränken) **oder** Service-Account nach Vorgabe des Connector (siehe `gateway/modules/connectors/connectorVoiceGoogle.py`: `Connector_GoogleSpeech_API_KEY_SECRET` kann API-Key oder SA-JSON sein). + +Doku STT/TTS: [`b-reference/gateway/voice-google.md`](../b-reference/gateway/voice-google.md). + +--- + +## 5) Gateway — Env-Dateien anpassen + +Alle Werte **ohne** echte Secrets im Wiki; in den Deploy-Envs mit eurem Verschlüsselungsworkflow pflegen ([`encrypt-env-secrets.md`](encrypt-env-secrets.md)). + +| Variable | Bedeutung | +|----------|-----------| +| `Service_GOOGLE_AUTH_CLIENT_ID` | OAuth Client (Login) | +| `Service_GOOGLE_AUTH_CLIENT_SECRET` | Geheimnis (verschlüsselt) | +| `Service_GOOGLE_AUTH_REDIRECT_URI` | Muss **1:1** mit Google Console Login-Callback übereinstimmen | +| `Service_GOOGLE_DATA_CLIENT_ID` | OAuth Client (Connector); darf = Auth sein | +| `Service_GOOGLE_DATA_CLIENT_SECRET` | Geheimnis (verschlüsselt) | +| `Service_GOOGLE_DATA_REDIRECT_URI` | Muss **1:1** mit Google Console Connect-Callback übereinstimmen | +| `Connector_GoogleSpeech_API_KEY_SECRET` | Speech/TTS API-Key oder SA-JSON (verschlüsselt) | + +**Dateien (Stand Repo):** `gateway/env-gateway-dev.env`, `env-gateway-int.env`, `env-gateway-prod.env`, `env-gateway-prod-forgejo.env`, ggf. `gateway/.env`. + +Zusätzlich — wenn neues **Frontend** oder neuer **API-Host**: + +- `APP_API_URL` muss die Basis sein, unter der `/api/google/...` erreichbar ist (Cookie/SameSite-Kontext). +- `APP_ALLOWED_ORIGINS` — CORS für das UI, falls neue Origins dazukommen. + +--- + +## 6) Gateway — Python (nur bei Scope-/API-Änderungen) + +| Datei | Wann anfassen | +|-------|----------------| +| `gateway/modules/auth/oauthProviderConfig.py` | Neue/entfernte Google-Scopes für Login oder Datenverbindung | +| `gateway/modules/routes/routeSecurityGoogle.py` | Nur bei Flow-/Route-Änderungen (unüblich) | +| `gateway/modules/auth/tokenManager.py` | Nutzt `Service_GOOGLE_DATA_*` für Refresh — keine manuelle Anpassung bei reiner Registrierung | + +Token-Refresh und Microsoft/Google Data: `gateway/modules/auth/tokenManager.py`. + +--- + +## 7) Smoke-Tests + +- [ ] Login mit Google: redirected zu Google, Rückkehr ohne `redirect_uri_mismatch` +- [ ] **Verbindungen** → Google connect/reconnect +- [ ] Drive/Mail/Kalender/Kontakte je nach Feature smoke-testen +- [ ] STT/TTS: kurzer Aufruf über Voice-Route (siehe `voice-google.md`) diff --git a/d-guides/microsoft-entra-registration-checklist.md b/d-guides/microsoft-entra-registration-checklist.md new file mode 100644 index 0000000..0f5b263 --- /dev/null +++ b/d-guides/microsoft-entra-registration-checklist.md @@ -0,0 +1,113 @@ +# Checkliste: Microsoft Entra ID — PORTA (OAuth) + +Operative Liste: **was in der App-Registrierung** im Ziel-Tenant (z. B. **poweron.swiss**) **konfiguriert** werden muss und **welche Gateway-Variablen/Code-Stellen** dazu passen. + +Hintergrund Konzept Auth- vs. Data-App: [`z-archive/concepts/OAuth-Auth-vs-Data-Connection-Konzept.md`](../z-archive/concepts/OAuth-Auth-vs-Data-Connection-Konzept.md). + +**Hinweis:** App-Registrierungen lassen sich nicht „in einen anderen Tenant verschieben“. Neu im Ziel-Tenant anlegen (oder bewusst Multi-Tenant), Secrets neu erzeugen, Nutzer müssen sich ggf. neu anmelden / Microsoft-Verbindungen neu verbinden. + +--- + +## 1) Entra Admin Center — App registration + +[Azure Portal](https://portal.azure.com) → **Microsoft Entra ID** → **App registrations** → **New registration**. + +- **Name:** z. B. PowerOn PORTA. +- **Supported account types:** + - Nur eigener Tenant (**Single tenant**), oder + - **Multitenant** — dann passt typisch `Service_MSFT_TENANT_ID = common` zum Authority `login.microsoftonline.com/common`. +- Redirects werden unter **Authentication** ergänzt (nächster Abschnitt). + +--- + +## 2) Authentication — Redirect URIs (Web) + +**App registration** → **Authentication** → **Platform** „Web“ → **Redirect URIs**. + +Pro öffentlich erreichbare Gateway-URL **zwei** URIs (exakt, inkl. Schema): + +| URI | Env-Variable | +|-----|----------------| +| `{origin}/api/msft/auth/login/callback` | `Service_MSFT_AUTH_REDIRECT_URI` | +| `{origin}/api/msft/auth/connect/callback` | `Service_MSFT_DATA_REDIRECT_URI` | + +**In diesem Repo typische Origins** (jeweils beide URIs): + +- Lokal: `http://localhost:8000` +- INT: `https://gateway-int.poweron.swiss` +- PROD: `https://gateway-prod.poweron.swiss` +- Forgejo/ALT-Prod (falls genutzt): `https://api.poweron.swiss` + +Implizite Flow / SPA: für dieses Gateway-Backend-Redirect-Muster **nicht** nötig (Confidential Client mit Secret). + +Backend-Routing: `gateway/modules/routes/routeSecurityMsft.py` (Prefix `/api/msft`). + +--- + +## 3) Certificates & secrets + +**Certificates & secrets** → **New client secret**. + +Wert in `Service_MSFT_AUTH_CLIENT_SECRET` und `Service_MSFT_DATA_CLIENT_SECRET` eintragen (bei **einer** App-Registrierung für beide Flows: **dieselben** Secrets — in den Envs oft identisch). + +Verschlüsselung siehe [`encrypt-env-secrets.md`](encrypt-env-secrets.md). + +--- + +## 4) API permissions (Microsoft Graph — Delegated) + +**API permissions** → **Add a permission** → **Microsoft Graph** → **Delegated permissions**. + +Single Source of Truth für die Scope-Liste: `gateway/modules/auth/oauthProviderConfig.py` → `msftAuthScopes`, `msftDataScopes` + +| Zweck | Graph permissions (delegated) | +|-------|-------------------------------| +| Login (`msftAuthScopes`) | `User.Read` | +| Datenverbindung (`msftDataScopes`) | `User.Read`, `Mail.ReadWrite`, `Mail.Send`, `Files.ReadWrite.All`, `Sites.ReadWrite.All`, `Team.ReadBasic.All`, `OnlineMeetings.Read`, `Chat.ReadWrite`, `ChatMessage.Send`, `Calendars.Read`, `Contacts.Read` | + +Viele dieser Permissions erfordern **Administratoreinwilligung** im Tenant: **Grant admin consent for {tenant}**. + +Zusätzlicher Flow im Gateway: Admin-Consent-URL unter **`/api/msft/adminconsent`** mit Callback **`…/api/msft/adminconsent/callback`** (Redirect wird aus `Service_MSFT_DATA_REDIRECT_URI` abgeleitet — siehe `routeSecurityMsft.py`). + +--- + +## 5) Gateway — Env-Dateien anpassen + +| Variable | Bedeutung | +|----------|-----------| +| `Service_MSFT_AUTH_CLIENT_ID` | Application (client) ID für Login-Flow | +| `Service_MSFT_AUTH_CLIENT_SECRET` | Client secret (verschlüsselt) | +| `Service_MSFT_AUTH_REDIRECT_URI` | Muss **1:1** mit Entra Login-Redirect übereinstimmen | +| `Service_MSFT_DATA_CLIENT_ID` | Application (client) ID für Connect; darf = Auth sein | +| `Service_MSFT_DATA_CLIENT_SECRET` | Client secret (verschlüsselt) | +| `Service_MSFT_DATA_REDIRECT_URI` | Muss **1:1** mit Entra Connect-Redirect übereinstimmen | +| `Service_MSFT_TENANT_ID` | `common` (Multi-Tenant-Anmeldung) **oder** Verzeichnis-ID (GUID) des Tenants **poweron.swiss** für Single-Tenant-Authority | + +**Dateien (Stand Repo):** `gateway/env-gateway-dev.env`, `env-gateway-int.env`, `env-gateway-prod.env`, `env-gateway-prod-forgejo.env`, ggf. `gateway/.env`. + +Wenn neues **Frontend** oder neuer **API-Host**: + +- `APP_API_URL` — öffentliche Basis des Gateways. +- `APP_ALLOWED_ORIGINS` — CORS für das UI. + +--- + +## 6) Gateway — Python / Sicherheit (Referenz) + +| Datei | Inhalt | +|-------|--------| +| `gateway/modules/auth/oauthProviderConfig.py` | `msftAuthScopes`, `msftDataScopes`, `msftDataScopesForRefresh` | +| `gateway/modules/routes/routeSecurityMsft.py` | Login, Connect, Admin-Consent; liest alle `Service_MSFT_*` | +| `gateway/modules/auth/tokenManager.py` | Refresh für Microsoft Data (`Service_MSFT_DATA_*`, `Service_MSFT_TENANT_ID`) | +| `gateway/modules/auth/csrf.py` | CSRF-Ausnahmen für `/api/msft/auth/*` und `/api/msft/adminconsent*` — bei **neuen** OAuth-Pfaden ggf. erweitern (selten) | + +Keine Code-Änderung nötig, solange **gleiche Routen** und **gleiche Scope-Menge** wie in `oauthProviderConfig.py` verwendet werden. + +--- + +## 7) Smoke-Tests + +- [ ] Login mit Microsoft: Rückkehr ohne AADSTS50011 (Redirect mismatch) +- [ ] **Verbindungen** → Microsoft connect/reconnect +- [ ] Nach Tenant-Wechsel: Admin Consent im neuen Tenant ausgeführt +- [ ] Je nach Feature: Mail, OneDrive/SharePoint, Teams/Chat, Kalender, Kontakte kurz testen