Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ OSAC UI is the web console for the [Open Sovereign AI Cloud (OSAC)](https://gith
| `libs/ui-components/` | Shared PatternFly 6 component library |
| `proxy/` | Go chi reverse proxy — OIDC auth + API forwarding |
| `deploy/chart/` | Helm chart for Kubernetes/OpenShift deployment |
| `scripts/` | Developer helper scripts |
| `docs/` | Architecture and deployment documentation |

## Quick start
Expand All @@ -28,6 +29,24 @@ Start the Go proxy (requires a running fulfillment API) and Vite dev server:
FULFILLMENT_API_URL=https://fulfillment.your-env.example.com pnpm dev
```

## Local development with Keycloak (dev mode)

When running the UI locally with `pnpm dev`, OIDC login will fail unless the Keycloak `osac-ui` client is configured to accept `localhost` redirect URIs. Run the following script against your target cluster to add the necessary redirect URIs:

```bash
export KUBECONFIG=~/envs/<your-env>/kubeconfig
./scripts/enable-local-ui-redirect-uri.sh --namespace <osac-namespace>
```

The script auto-detects the Keycloak route and patches the `osac-ui` client to allow `http://localhost:5173` and `http://127.0.0.1:5173` callbacks. It tries `admin/admin` credentials first, then falls back to the `keycloak-initial-admin` Kubernetes secret. You can also pass credentials explicitly:

```bash
./scripts/enable-local-ui-redirect-uri.sh --namespace osac-devel \
--keycloak-username admin --keycloak-password <password>
```

Use `--dry-run` to preview changes or `--verify-only` to check if the redirect URIs are already configured. Run `./scripts/enable-local-ui-redirect-uri.sh --help` for all options.

## Documentation

| Document | Description |
Expand Down
323 changes: 323 additions & 0 deletions scripts/enable-local-ui-redirect-uri.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,323 @@
#!/usr/bin/env bash
# Patch the Keycloak osac-ui client so local dev (pnpm dev on localhost:5173) can complete OIDC login.
#
# Usage:
# export KUBECONFIG=~/envs/asaf-sno2/kubeconfig
# ./scripts/enable-local-ui-redirect-uri.sh
# ./scripts/enable-local-ui-redirect-uri.sh --namespace osac-laptop-sno --dry-run
# ./scripts/enable-local-ui-redirect-uri.sh --verify-only
set -euo pipefail

KEYCLOAK_NS="${KEYCLOAK_NS:-keycloak}"
KEYCLOAK_REALM="${KEYCLOAK_REALM:-osac}"
CLIENT_ID="${CLIENT_ID:-osac-ui}"
LOCAL_ORIGIN="${LOCAL_ORIGIN:-http://localhost:5173}"
KEYCLOAK_USERNAME="${KEYCLOAK_USERNAME:-}"
KEYCLOAK_PASSWORD="${KEYCLOAK_PASSWORD:-}"
OSAC_NAMESPACE=""
DRY_RUN=false
VERIFY_ONLY=false

usage() {
cat <<'EOF'
Enable local osac-ui OIDC login by adding localhost redirect URIs to the Keycloak osac-ui client.

Options:
--namespace <ns> OSAC namespace (auto-detected from osac-ui route if omitted)
--keycloak-namespace <ns> Keycloak namespace (default: keycloak)
--local-origin <url> Local dev origin (default: http://localhost:5173)
--keycloak-username <u> Keycloak admin username (default: admin)
--keycloak-password <p> Keycloak admin password (tries this, then admin, then k8s secret)
--dry-run Print planned changes without applying
--verify-only Exit 0 if localhost redirect URIs are already configured
-h, --help Show this help

Requires: kubectl or oc, curl, jq
EOF
}

while [[ $# -gt 0 ]]; do
case "$1" in
--namespace)
OSAC_NAMESPACE="${2:-}"
shift 2
;;
--keycloak-namespace)
KEYCLOAK_NS="${2:-}"
shift 2
;;
--local-origin)
LOCAL_ORIGIN="${2:-}"
shift 2
;;
--keycloak-username)
KEYCLOAK_USERNAME="${2:-}"
shift 2
;;
--keycloak-password)
KEYCLOAK_PASSWORD="${2:-}"
shift 2
;;
--dry-run)
DRY_RUN=true
shift
;;
--verify-only)
VERIFY_ONLY=true
shift
;;
-h | --help)
usage
exit 0
;;
*)
echo "ERROR: Unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done

if ! command -v jq >/dev/null 2>&1; then
echo "ERROR: jq is required" >&2
exit 1
fi

if command -v kubectl >/dev/null 2>&1; then
KUBE_CMD=(kubectl)
elif command -v oc >/dev/null 2>&1; then
KUBE_CMD=(oc)
else
echo "ERROR: kubectl or oc is required" >&2
exit 1
fi

LOCAL_ORIGIN="${LOCAL_ORIGIN%/}"
LOCAL_CALLBACK="${LOCAL_ORIGIN}/callback"
LOCAL_REDIRECT_WILDCARD="${LOCAL_ORIGIN}/*"

# Also allow 127.0.0.1 when origin uses localhost (common alternate).
ALT_ORIGIN=""
ALT_CALLBACK=""
ALT_REDIRECT_WILDCARD=""
if [[ "${LOCAL_ORIGIN}" == http://localhost:* ]]; then
ALT_ORIGIN="http://127.0.0.1:${LOCAL_ORIGIN##http://localhost:}"
ALT_CALLBACK="${ALT_ORIGIN}/callback"
ALT_REDIRECT_WILDCARD="${ALT_ORIGIN}/*"
fi
Comment on lines +95 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the file and inspect the relevant sections.
git ls-files scripts/enable-local-ui-redirect-uri.sh
wc -l scripts/enable-local-ui-redirect-uri.sh
sed -n '1,320p' scripts/enable-local-ui-redirect-uri.sh

# Find every use of the wildcard redirect variables and the redirectUris construction.
rg -n "LOCAL_REDIRECT_WILDCARD|ALT_REDIRECT_WILDCARD|deployedWildcard|redirectUris|merge_redirect_uris|callback" scripts/enable-local-ui-redirect-uri.sh

# Look for any docs/comments about Keycloak redirect URI behavior in the repo.
rg -n "redirect URI|redirectUris|Keycloak|wildcard.*redirect|CVE-2023-6927" .

Repository: osac-project/osac-ui

Length of output: 21655


🌐 Web query:

Keycloak CVE-2023-6927 redirect URI wildcard asterisk exact redirect_uri callback documentation

💡 Result:

CVE-2023-6927 was a security vulnerability in Keycloak involving an open redirect via the "form_post.jwt" JARM response mode [1][2]. The flaw allowed an attacker to potentially steal authorization codes or tokens by exploiting clients configured with a wildcard in their redirect URIs, effectively bypassing a previous security patch [2][3]. This vulnerability was fixed in Keycloak version 23.0.4 [3]. Regarding Keycloak's handling of redirect URI wildcards: 1. Usage and Constraints: Keycloak supports using a single asterisk () as a wildcard at the end of a valid redirect URI (e.g., http://host.com/path/) [4][5]. The use of full wildcards (*) for all URIs is not recommended for production environments [5]. 2. Security Best Practices: OAuth 2.0 specifications generally recommend using exact string matching for redirect URIs to enhance security [6]. Keycloak documentation explicitly warns that using wildcards can introduce security risks and advises users to make registered redirect URIs as specific as possible [5][7]. 3. Interaction with Other Components: Wildcard expansion is not supported for query parameters [6]. Furthermore, to mitigate security risks, Keycloak performs standard, exact string matching instead of wildcard comparison if the redirect URI contains a userinfo component or a path that manages access to parent directories (e.g., /../) [5]. 4. Evolution of Validation: Keycloak has consistently tightened its redirect URI validation logic over time to address various vulnerabilities (such as CVE-2023-6927 and the more recent CVE-2026-7504, which involved URL validation bypasses) [3][8][9]. Developers are encouraged to avoid wildcard patterns in production whenever feasible [5][6].

Citations:


Drop the /* redirect URIs

Keycloak recommends exact redirect URI matches; .../callback is enough for both the deployed client and local localhost/127.0.0.1 flows. Keeping /* on the client widens the auth-code/token-theft surface. Remove the wildcard entries from both the create and merge paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/enable-local-ui-redirect-uri.sh` around lines 95 - 107, Remove the
LOCAL_REDIRECT_WILDCARD and ALT_REDIRECT_WILDCARD values and stop including
wildcard redirect URIs in both the client creation and merge paths. Retain the
exact LOCAL_CALLBACK and ALT_CALLBACK entries for deployed, localhost, and
127.0.0.1 flows.


discover_osac_namespace() {
if [[ -n "${OSAC_NAMESPACE}" ]]; then
echo "${OSAC_NAMESPACE}"
return
fi

local ns
ns="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)"
if [[ -z "${ns}" ]]; then
echo "ERROR: Could not find osac-ui route. Pass --namespace." >&2
exit 1
fi
echo "${ns}"
}
Comment on lines +109 to +122

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Ambiguous namespace auto-discovery could target the wrong (e.g. production) Keycloak client.

get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' blindly picks the first match if multiple osac-ui routes exist across namespaces (staging/prod/multiple dev envs on the same cluster). Since this script modifies an IdP client's redirect URIs, silently discovering the wrong namespace risks adding localhost redirect URIs to a client the operator didn't intend to touch.

🛡️ Proposed fix
   local ns
-  ns="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)"
+  local count
+  count="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[*].metadata.namespace}' 2>/dev/null | wc -w || true)"
+  if [[ "${count}" -gt 1 ]]; then
+    echo "ERROR: Found osac-ui route in multiple namespaces. Pass --namespace explicitly." >&2
+    exit 1
+  fi
+  ns="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
discover_osac_namespace() {
if [[ -n "${OSAC_NAMESPACE}" ]]; then
echo "${OSAC_NAMESPACE}"
return
fi
local ns
ns="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)"
if [[ -z "${ns}" ]]; then
echo "ERROR: Could not find osac-ui route. Pass --namespace." >&2
exit 1
fi
echo "${ns}"
}
discover_osac_namespace() {
if [[ -n "${OSAC_NAMESPACE}" ]]; then
echo "${OSAC_NAMESPACE}"
return
fi
local ns
local count
count="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[*].metadata.namespace}' 2>/dev/null | wc -w || true)"
if [[ "${count}" -gt 1 ]]; then
echo "ERROR: Found osac-ui route in multiple namespaces. Pass --namespace explicitly." >&2
exit 1
fi
ns="$("${KUBE_CMD[@]}" get route osac-ui -A -o jsonpath='{.items[0].metadata.namespace}' 2>/dev/null || true)"
if [[ -z "${ns}" ]]; then
echo "ERROR: Could not find osac-ui route. Pass --namespace." >&2
exit 1
fi
echo "${ns}"
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/enable-local-ui-redirect-uri.sh` around lines 109 - 122, Update
discover_osac_namespace so automatic discovery does not select an arbitrary
osac-ui route when multiple namespaces match. Require an unambiguous result, or
fail with an actionable error instructing the operator to provide --namespace,
while preserving the explicit OSAC_NAMESPACE path and preventing any IdP client
modification when discovery is ambiguous.


discover_ui_route_host() {
local ns="$1"
"${KUBE_CMD[@]}" get route osac-ui -n "${ns}" -o jsonpath='{.spec.host}' 2>/dev/null || true
}

discover_keycloak_url() {
local host
host="$("${KUBE_CMD[@]}" get route keycloak -n "${KEYCLOAK_NS}" -o jsonpath='{.spec.host}' 2>/dev/null || true)"
if [[ -z "${host}" ]]; then
echo "ERROR: Could not find Keycloak route in namespace ${KEYCLOAK_NS}" >&2
exit 1
fi
echo "https://${host}"
}

try_keycloak_token() {
local kc_url="$1" username="$2" password="$3"
curl -sk "${kc_url}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=${username}" \
-d "password=${password}" | jq -r '.access_token // empty'
}
Comment on lines +139 to +146

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

TLS certificate verification disabled (-k) on all Keycloak admin API calls, including credential exchange.

curl -sk disables certificate validation. This call sends the admin username/password in the request body — combined with disabled cert verification, this is vulnerable to MITM credential theft. Same -k pattern repeats at lines 186-187, 271-275, 280-281, and 316-320 (flagged by static analysis, CWE-295). Prefer letting curl validate against the system/cluster CA (--cacert <bundle> if the route uses a custom CA), or make insecure mode an explicit opt-in flag with a loud warning rather than the default.

🔐 Proposed fix (sketch)
+CURL_TLS_OPTS=()
+if [[ "${INSECURE_TLS:-false}" == true ]]; then
+  CURL_TLS_OPTS=(-k)
+  echo "WARNING: TLS certificate verification disabled" >&2
+fi
 try_keycloak_token() {
   local kc_url="$1" username="$2" password="$3"
-  curl -sk "${kc_url}/realms/master/protocol/openid-connect/token" \
+  curl -s "${CURL_TLS_OPTS[@]}" "${kc_url}/realms/master/protocol/openid-connect/token" \

Apply the same pattern to the other four curl -sk call sites.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try_keycloak_token() {
local kc_url="$1" username="$2" password="$3"
curl -sk "${kc_url}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=${username}" \
-d "password=${password}" | jq -r '.access_token // empty'
}
CURL_TLS_OPTS=()
if [[ "${INSECURE_TLS:-false}" == true ]]; then
CURL_TLS_OPTS=(-k)
echo "WARNING: TLS certificate verification disabled" >&2
fi
try_keycloak_token() {
local kc_url="$1" username="$2" password="$3"
curl -s "${CURL_TLS_OPTS[@]}" "${kc_url}/realms/master/protocol/openid-connect/token" \
-d "grant_type=password" \
-d "client_id=admin-cli" \
-d "username=${username}" \
-d "password=${password}" | jq -r '.access_token // empty'
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 140-144: curl is invoked with -k/--insecure, which disables TLS certificate verification and exposes the connection to man-in-the-middle attacks. Remove the insecure flag and let curl validate the server certificate; if you need to trust a private CA, pin it with --cacert instead.
Context: curl -sk "${kc_url}/realms/master/protocol/openid-connect/token"
-d "grant_type=password"
-d "client_id=admin-cli"
-d "username=${username}"
-d "password=${password}"
Note: [CWE-295] Improper Certificate Validation.

(curl-insecure-tls-bash)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/enable-local-ui-redirect-uri.sh` around lines 139 - 146, Remove the
default TLS verification bypass from try_keycloak_token and the other four curl
call sites in the script, replacing curl -sk with certificate-validating
requests. Support custom cluster certificates through an explicit --cacert
bundle where needed; if insecure mode must remain available, require an explicit
opt-in with a prominent warning rather than enabling it by default.

Source: Linters/SAST tools


get_keycloak_admin_token() {
local kc_url="$1"
local token password
local username="${KEYCLOAK_USERNAME:-admin}"

if [[ -n "${KEYCLOAK_PASSWORD}" ]]; then
token="$(try_keycloak_token "${kc_url}" "${username}" "${KEYCLOAK_PASSWORD}")"
if [[ -n "${token}" && "${token}" != "null" ]]; then
echo "${token}"
return
fi
echo "ERROR: Could not obtain Keycloak admin token with provided credentials" >&2
exit 1
fi

token="$(try_keycloak_token "${kc_url}" "${username}" "admin")"
if [[ -n "${token}" && "${token}" != "null" ]]; then
echo "${token}"
return
fi

password="$("${KUBE_CMD[@]}" get secret keycloak-initial-admin -n "${KEYCLOAK_NS}" \
-o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true)"
if [[ -z "${password}" ]]; then
echo "ERROR: Could not obtain Keycloak admin token (tried ${username}/admin and keycloak-initial-admin secret)" >&2
exit 1
fi

token="$(try_keycloak_token "${kc_url}" "${username}" "${password}")"
if [[ -z "${token}" || "${token}" == "null" ]]; then
echo "ERROR: Could not obtain Keycloak admin token" >&2
exit 1
fi
echo "${token}"
}

get_client_internal_id() {
local kc_url="$1" token="$2"
curl -sk -H "Authorization: Bearer ${token}" \
"${kc_url}/admin/realms/${KEYCLOAK_REALM}/clients?clientId=${CLIENT_ID}" | jq -r '.[0].id // empty'
}

has_local_redirect_uri() {
local redirect_uris_json="$1"
local uri
for uri in "${LOCAL_CALLBACK}" "${LOCAL_REDIRECT_WILDCARD}" "${ALT_CALLBACK}" "${ALT_REDIRECT_WILDCARD}"; do
[[ -z "${uri}" ]] && continue
if echo "${redirect_uris_json}" | jq -e --arg u "${uri}" 'index($u) != null' >/dev/null; then
return 0
fi
done
return 1
}

merge_redirect_uris() {
local current_json="$1"
local uris_json='[]'

uris_json="$(echo "${current_json}" | jq -c '.redirectUris // []')"
for uri in "${LOCAL_CALLBACK}" "${LOCAL_REDIRECT_WILDCARD}" "${ALT_CALLBACK}" "${ALT_REDIRECT_WILDCARD}"; do
[[ -z "${uri}" ]] && continue
uris_json="$(jq -cn --argjson existing "${uris_json}" --arg uri "${uri}" \
'$existing + [$uri] | unique')"
done
echo "${uris_json}"
}

OSAC_NAMESPACE="$(discover_osac_namespace)"
UI_HOST="$(discover_ui_route_host "${OSAC_NAMESPACE}")"
KC_URL="$(discover_keycloak_url)"

echo "OSAC namespace: ${OSAC_NAMESPACE}"
echo "UI route: ${UI_HOST:-<not found>}"
echo "Keycloak: ${KC_URL}"
echo "Realm / client: ${KEYCLOAK_REALM} / ${CLIENT_ID}"
echo "Local redirect: ${LOCAL_CALLBACK}"

KC_ADMIN_TOKEN="$(get_keycloak_admin_token "${KC_URL}")"
CLIENT_INTERNAL_ID="$(get_client_internal_id "${KC_URL}" "${KC_ADMIN_TOKEN}")"

if [[ -z "${CLIENT_INTERNAL_ID}" ]]; then
if [[ -z "${UI_HOST}" ]]; then
echo "ERROR: Keycloak client ${CLIENT_ID} not found and osac-ui route is missing — cannot create client" >&2
exit 1
fi

DEPLOYED_ORIGIN="https://${UI_HOST}"
CREATE_PAYLOAD="$(jq -cn \
--arg clientId "${CLIENT_ID}" \
--arg rootUrl "${DEPLOYED_ORIGIN}" \
--arg deployedCallback "${DEPLOYED_ORIGIN}/callback" \
--arg deployedWildcard "${DEPLOYED_ORIGIN}/*" \
--arg localCallback "${LOCAL_CALLBACK}" \
--arg localWildcard "${LOCAL_REDIRECT_WILDCARD}" \
--arg altCallback "${ALT_CALLBACK}" \
--arg altWildcard "${ALT_REDIRECT_WILDCARD}" \
'{
clientId: $clientId,
name: "OSAC UI",
publicClient: true,
directAccessGrantsEnabled: false,
standardFlowEnabled: true,
rootUrl: $rootUrl,
redirectUris: ([$deployedCallback, $deployedWildcard, $localCallback, $localWildcard, $altCallback, $altWildcard] | map(select(length > 0)) | unique),
webOrigins: ["+"],
protocol: "openid-connect",
enabled: true,
attributes: {
"pkce.code.challenge.method": "S256"
}
}')"

if [[ "${VERIFY_ONLY}" == true ]]; then
echo "VERIFY FAILED: ${CLIENT_ID} client does not exist"
exit 1
fi

if [[ "${DRY_RUN}" == true ]]; then
echo "DRY RUN: would create Keycloak client:"
echo "${CREATE_PAYLOAD}" | jq .
exit 0
fi

curl -sk -f -X POST \
-H "Authorization: Bearer ${KC_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"${KC_URL}/admin/realms/${KEYCLOAK_REALM}/clients" \
-d "${CREATE_PAYLOAD}" >/dev/null
echo "Created Keycloak client ${CLIENT_ID}"
exit 0
fi

CURRENT_CLIENT="$(curl -sk -H "Authorization: Bearer ${KC_ADMIN_TOKEN}" \
"${KC_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${CLIENT_INTERNAL_ID}")"

CURRENT_REDIRECTS="$(echo "${CURRENT_CLIENT}" | jq -c '.redirectUris // []')"
if has_local_redirect_uri "${CURRENT_REDIRECTS}"; then
echo "Local redirect URIs already configured:"
echo "${CURRENT_REDIRECTS}" | jq .
if [[ "${VERIFY_ONLY}" == true ]]; then
exit 0
fi
if [[ "${DRY_RUN}" == true ]]; then
echo "DRY RUN: no changes needed"
exit 0
fi
echo "Nothing to do."
exit 0
fi

NEW_REDIRECTS="$(merge_redirect_uris "${CURRENT_CLIENT}")"
UPDATE_PAYLOAD="$(echo "${CURRENT_CLIENT}" | jq --argjson redirects "${NEW_REDIRECTS}" '.redirectUris = $redirects | del(.id, .access)')"

if [[ "${VERIFY_ONLY}" == true ]]; then
echo "VERIFY FAILED: localhost redirect URIs missing"
echo "Current redirectUris:"
echo "${CURRENT_REDIRECTS}" | jq .
exit 1
fi

echo "Planned redirectUris:"
echo "${NEW_REDIRECTS}" | jq .

if [[ "${DRY_RUN}" == true ]]; then
echo "DRY RUN: no changes applied"
exit 0
fi

curl -sk -f -X PUT \
-H "Authorization: Bearer ${KC_ADMIN_TOKEN}" \
-H "Content-Type: application/json" \
"${KC_URL}/admin/realms/${KEYCLOAK_REALM}/clients/${CLIENT_INTERNAL_ID}" \
-d "${UPDATE_PAYLOAD}" >/dev/null

echo "Updated Keycloak client ${CLIENT_ID} (${CLIENT_INTERNAL_ID})"
echo "Retry login at ${LOCAL_ORIGIN}"
Loading