From 4819247e39588129a03daa7415a1bbd8ceb25102 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 9 Jul 2026 00:02:18 +0000 Subject: [PATCH] Add zulip-oidc-redirect-fix prose contract Fixes Zulip OIDC authentication where redirect_uri used internal IP (192.168.68.19) instead of public domain (chat.sysloggh.net) after server restart. Monkey-patches social_core.strategy.BaseStrategy to use ROOT_DOMAIN_URI for all redirect URI construction. --- zulip-oidc-redirect-fix.prose.md | 92 ++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 zulip-oidc-redirect-fix.prose.md diff --git a/zulip-oidc-redirect-fix.prose.md b/zulip-oidc-redirect-fix.prose.md new file mode 100644 index 0000000..acabf11 --- /dev/null +++ b/zulip-oidc-redirect-fix.prose.md @@ -0,0 +1,92 @@ +--- +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)