--- kind: responsibility name: zulip-oidc-redirect-fix status: active description: > Fixes Zulip OIDC authentication when the redirect_uri sent to Authentik uses the internal IP (192.168.68.19) instead of the public domain (chat.sysloggh.net). Applied via monkey-patch in ZULIP_CUSTOM_SETTINGS. Survives container restarts through compose.override.yaml. agent: abiba triggers: - Zulip OIDC login returns "Redirect URI Error" from Authentik - redirect_uri in OAuth URL shows 192.168.68.19 instead of chat.sysloggh.net - After Zulip server restart, Authentik SSO login broken --- ## Maintains - zulip-oidc: { redirect_uri: "https://chat.sysloggh.net/complete/oidc/", scheme: "https", host: "chat.sysloggh.net" } - authentik-acceptance: { status: "accepted" | "rejected" } - patched-strategy: { module: "social_core.strategy.BaseStrategy", method: "absolute_uri", root: "ROOT_DOMAIN_URI" } ## Detection ### Rule 1: Wrong redirect_uri host - **Detect**: `curl -sk -L "https://chat.sysloggh.net/accounts/login/social/oidc/authentik" 2>&1 | grep "redirect_uri=https://192.168.68.19"` - **Status**: CRITICAL — Authentik will reject - **Trigger fix** → Execute self-heal ### Rule 2: OIDC flow broken - **Detect**: `curl -sk -o /dev/null -w "%{http_code}" "https://chat.sysloggh.net/accounts/login/social/oidc/authentik"` → chain ends at Authentik 400 - **Diagnose**: Check redirect_uri in the 302 Location header chain ## Fix (Self-Heal) Two layers applied: ### Layer 1: Live patch (inside container, immediate) ```bash # Add to /home/zulip/deployments/current/zproject/computed_settings.py: SOCIAL_AUTH_REDIRECT_IS_HTTPS = True import urllib.parse from social_core.strategy import BaseStrategy _original_absolute_uri = BaseStrategy.absolute_uri def _patched_absolute_uri(self, path=None): from django.conf import settings root = getattr(settings, "ROOT_DOMAIN_URI", "https://chat.sysloggh.net") if path is not None: return urllib.parse.urljoin(root, path) return root BaseStrategy.absolute_uri = _patched_absolute_uri # Restart Django supervisorctl restart zulip-django ``` ### Layer 2: Persistent fix (compose.override.yaml) The patch is baked into the `ZULIP_CUSTOM_SETTINGS` env var in `/opt/zulip/compose.override.yaml`. Survives Docker container restarts. ### Verification ```bash STEP1=$(curl -sk -w "%{redirect_url}" \ "https://chat.sysloggh.net/accounts/login/social/oidc/authentik" -o /dev/null) curl -sk -D- "$STEP1" -o /dev/null 2>&1 | grep "redirect_uri=" # Expected: redirect_uri=https://chat.sysloggh.net/complete/oidc/ # Wrong: redirect_uri=https://192.168.68.19/complete/oidc/ ``` ### Rollback Remove the patch block from `compose.override.yaml` and restart the container: ```bash docker compose -f /opt/zulip/compose.yaml -f /opt/zulip/compose.override.yaml up -d zulip ``` ## Root Cause After Zulip restart, `social-auth-core` computes the OIDC `redirect_uri` via Django's `request.build_absolute_uri()` → `request.get_host()`. The upstream Netbird/Traefik proxy (72.61.0.17) forwards `Host: 192.168.68.19` instead of `Host: chat.sysloggh.net`, and without `HTTP_HOST` in nginx's `uwsgi_params`, Django falls back to the server's IP. The monkey-patch overrides `BaseStrategy.absolute_uri()` to always use `ROOT_DOMAIN_URI` (`https://chat.sysloggh.net`) regardless of the request's Host header. ## Related Contracts - `zulip-health.prose.md` — General Zulip health monitoring - `zulip-self-heal.prose.md` — RETIRED (pi extension removed)