#!/bin/bash # Discourse SSO first-login via Cloudron OIDC (openid_connect). # usage: discourse-sso-login.sh [vault-item] # Verifies a logged-in session at /my/summary. set -uo pipefail U="${1:?usage: discourse-sso-login.sh [vault-item]}" ITEM="${2:-$U Cloudron}" BASE="https://community.turnsys.com" IDP="https://my.knownelement.com" JAR="/tmp/discourse-jar-$U.txt"; rm -f "$JAR" SM() { if [ "$(id -un)" = "TSGCOO" ]; then /data2/TSGCOO/.local/bin/sm "$@" &2; exit 1; } SEED=$(SM get "$ITEM" --field totp_seed 2>/dev/null || true) # Discourse gates omniauth behind CSRF + a confirm page CSRF=$(curl -sk -b "$JAR" -c "$JAR" -H 'X-Requested-With: XMLHttpRequest' "$BASE/session/csrf" | sed -n 's/.*"csrf":"\([^"]*\)".*/\1/p') [ -n "$CSRF" ] || { echo "FAIL: no csrf token" >&2; exit 2; } CONFIRM_HTML=$(curl -sk -b "$JAR" -c "$JAR" -H "X-CSRF-Token: $CSRF" -H 'X-Requested-With: XMLHttpRequest' "$BASE/auth/oidc") FORMTOKEN=$(printf '%s' "$CONFIRM_HTML" | grep -o 'name="authenticity_token"[^>]*value="[^"]*"' | head -1 | sed 's/.*value="//; s/"$//') [ -n "$FORMTOKEN" ] || { echo "FAIL: no confirm form token" >&2; exit 2; } INT=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$BASE/auth/oidc" \ --data-urlencode "authenticity_token=$FORMTOKEN" --data-urlencode "button=continue" \ -o /dev/null -w '%{redirect_url}') [ -n "$INT" ] || { echo "FAIL: no oidc redirect" >&2; exit 2; } # follow the provider auth URL down to the interaction INT=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$INT") case "$INT" in */openid/interaction/*) : ;; *) echo "FAIL: no interaction reached: $INT" >&2; exit 3 ;; esac UIDPATH=$(printf '%s' "$INT" | grep -o '/openid/interaction/[^?]*') BODY="{\"username\":\"$U\",\"password\":\"$PW\"" if [ -n "$SEED" ]; then TCODE=$(bash "$(dirname "$0")/totp.sh" "$SEED") BODY="$BODY,\"totpToken\":\"$TCODE\"" fi BODY="$BODY}" LOGIN_RESP=$(curl -sk -b "$JAR" -c "$JAR" -X POST "$IDP$UIDPATH/login" -H 'Content-Type: application/json' -d "$BODY") unset BODY case "$LOGIN_RESP" in *redirectTo*) : ;; *) echo "FAIL: login rejected: $LOGIN_RESP" >&2; exit 5 ;; esac RED=$(printf '%s' "$LOGIN_RESP" | sed -n 's/.*"redirectTo":"\([^"]*\)".*/\1/p') FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$RED") case "$FINAL" in */openid/interaction/*) CUIDPATH=$(printf '%s' "$FINAL" | grep -o '/openid/interaction/[^?]*') CLOC=$(curl -sk -b "$JAR" -c "$JAR" -D - -o /dev/null -X POST "$IDP$CUIDPATH/confirm" -d '' | grep -i '^location:' | head -1 | tr -d '\r' | sed 's/^[Ll]ocation: //') [ -n "$CLOC" ] || { echo "FAIL: consent confirm produced no redirect" >&2; exit 6; } CLOC=$(printf '%s' "$CLOC" | sed 's|^https//|https://|; s|^http//|http://|') case "$CLOC" in http://*|https://*) : ;; /*) CLOC="$IDP$CLOC" ;; *) CLOC="$IDP/$CLOC" ;; esac FINAL=$(curl -sk -b "$JAR" -c "$JAR" -o /dev/null -w '%{url_effective}' -L --max-redirs 8 "$CLOC") ;; esac WHO=$(curl -sk -b "$JAR" "$BASE/session/current.json" | sed -n 's/.*"username":"\([^"]*\)".*/\1/p' | head -1) if [ -n "$WHO" ]; then echo "OK: $WHO logged into discourse (session verified)" exit 0 fi echo "FAIL: no discourse session, landed: $FINAL" >&2 exit 7