@tidecloak/create-nextjs 0.14.2 → 0.14.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/init/realm.json +13 -10
- package/init/tcinit.sh +491 -177
- package/package.json +1 -1
- package/template-js-app/init/realm.json +164 -161
- package/template-js-app/init/tcinit.sh +459 -179
- package/template-js-app/package.json +1 -1
- package/template-ts-app/init/realm.json +164 -161
- package/template-ts-app/init/tcinit.sh +459 -179
- package/template-ts-app/package.json +1 -1
- package/template-ts-app/tsconfig.json +1 -1
|
@@ -2,44 +2,73 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
-
#
|
|
5
|
+
# @tidecloak/create-nextjs - tcinit.sh (SHIPPED template copy)
|
|
6
|
+
#
|
|
7
|
+
# Provisions a firstAdmin / threshold-1 Tide realm for a Next.js app, driving
|
|
8
|
+
# the CURRENT iga-core native governance model (drain PENDING change-requests
|
|
9
|
+
# via /iga/change-requests/{id}/approve, which records AND auto-commits at
|
|
10
|
+
# threshold-1). The legacy tide-admin/change-set/{type}/sign|commit path has
|
|
11
|
+
# been removed.
|
|
12
|
+
#
|
|
13
|
+
# This copy ships inside the scaffolded app so the user can re-run:
|
|
14
|
+
# cd <app> && bash init/tcinit.sh
|
|
15
|
+
# It resolves everything relative to SCRIPT_DIR and is runnable from anywhere.
|
|
16
|
+
#
|
|
17
|
+
# The governance body below (from "Helper: grab a fresh master admin-cli
|
|
18
|
+
# token" onward) is IDENTICAL to the canonical init/tcinit.sh. Both are kept
|
|
19
|
+
# in sync via `npm run sync:init`. Only this path/bootstrap preamble differs.
|
|
6
20
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
# ─── Resolve script directory (run from anywhere) ────────────────────────────
|
|
7
23
|
SCRIPT_DIR="$(cd -- "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd -P)"
|
|
8
24
|
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
25
|
+
# ─── Load defaults from .env.example (CRLF-safe) ─────────────────────────────
|
|
26
|
+
# -f guard keeps it a no-op when absent.
|
|
12
27
|
ENV_FILE="${SCRIPT_DIR}/.env.example"
|
|
13
28
|
if [[ -f "$ENV_FILE" ]]; then
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
# Apply each KEY=VALUE from the defaults file with FALLBACK semantics: a
|
|
30
|
+
# variable already present in the caller's environment is preserved and the
|
|
31
|
+
# default is ignored. We deliberately do NOT `source` the file, because raw
|
|
32
|
+
# assignments would hard-override caller-supplied env (e.g. NEW_REALM_NAME).
|
|
33
|
+
# CRLF-safe: a trailing CR is stripped from every line.
|
|
34
|
+
while IFS= read -r _env_line || [[ -n "$_env_line" ]]; do
|
|
35
|
+
_env_line="${_env_line%$'\r'}"
|
|
36
|
+
if [[ "$_env_line" =~ ^[[:space:]]*(#|$) ]]; then
|
|
37
|
+
continue
|
|
38
|
+
fi
|
|
39
|
+
if [[ "$_env_line" =~ ^[[:space:]]*(export[[:space:]]+)?([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=(.*)$ ]]; then
|
|
40
|
+
_env_key="${BASH_REMATCH[2]}"
|
|
41
|
+
_env_val="${BASH_REMATCH[3]}"
|
|
42
|
+
_env_val="${_env_val#"${_env_val%%[![:space:]]*}"}"
|
|
43
|
+
_env_val="${_env_val%"${_env_val##*[![:space:]]}"}"
|
|
44
|
+
if [[ ${#_env_val} -ge 2 && "$_env_val" == \"*\" ]]; then
|
|
45
|
+
_env_val="${_env_val:1:${#_env_val}-2}"
|
|
46
|
+
elif [[ ${#_env_val} -ge 2 && "$_env_val" == \'*\' ]]; then
|
|
47
|
+
_env_val="${_env_val:1:${#_env_val}-2}"
|
|
48
|
+
fi
|
|
49
|
+
if [[ -z "${!_env_key:-}" ]]; then
|
|
50
|
+
printf -v "$_env_key" '%s' "$_env_val"
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
done < "$ENV_FILE"
|
|
54
|
+
unset _env_line _env_key _env_val
|
|
24
55
|
fi
|
|
25
56
|
|
|
26
|
-
#
|
|
27
|
-
# Defaults (override via env)
|
|
28
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
57
|
+
# ─── Defaults (override via env) ─────────────────────────────────────────────
|
|
29
58
|
TIDECLOAK_LOCAL_URL="${TIDECLOAK_LOCAL_URL:-http://localhost:8080}"
|
|
30
59
|
CLIENT_APP_URL="${CLIENT_APP_URL:-http://localhost:3000}"
|
|
31
|
-
ADAPTER_OUTPUT_PATH="${ADAPTER_OUTPUT_PATH:-${SCRIPT_DIR}/tidecloak.json}"
|
|
32
60
|
NEW_REALM_NAME="${NEW_REALM_NAME:-nextjs-test}"
|
|
33
61
|
REALM_MGMT_CLIENT_ID="${REALM_MGMT_CLIENT_ID:-realm-management}"
|
|
34
62
|
ADMIN_ROLE_NAME="${ADMIN_ROLE_NAME:-tide-realm-admin}"
|
|
35
63
|
KC_USER="${KC_USER:-admin}"
|
|
36
64
|
KC_PASSWORD="${KC_PASSWORD:-password}"
|
|
37
65
|
CLIENT_NAME="${CLIENT_NAME:-myclient}"
|
|
66
|
+
SUBSCRIPTION_EMAIL="${SUBSCRIPTION_EMAIL:-test@demo.org}"
|
|
67
|
+
ADAPTER_OUTPUT_PATH="${ADAPTER_OUTPUT_PATH:-${SCRIPT_DIR}/tidecloak.json}"
|
|
68
|
+
MARKER_DIR="${SCRIPT_DIR}"
|
|
38
69
|
|
|
39
|
-
#
|
|
40
|
-
# Find realm.json robustly
|
|
70
|
+
# ─── Find realm.json robustly ────────────────────────────────────────────────
|
|
41
71
|
# Priority: env → same dir → parent → current working dir
|
|
42
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
43
72
|
CANDIDATES=()
|
|
44
73
|
[[ "${REALM_JSON_PATH:-}" != "" ]] && CANDIDATES+=("${REALM_JSON_PATH}")
|
|
45
74
|
CANDIDATES+=("${SCRIPT_DIR}/realm.json" "${SCRIPT_DIR}/../realm.json" "$(pwd)/realm.json")
|
|
@@ -49,31 +78,64 @@ for p in "${CANDIDATES[@]}"; do
|
|
|
49
78
|
if [[ -f "$p" ]]; then REALM_JSON_PATH="$p"; break; fi
|
|
50
79
|
done
|
|
51
80
|
|
|
52
|
-
echo "🔍 realm.json search candidates:"
|
|
53
|
-
for p in "${CANDIDATES[@]}"; do echo " - $p"; done
|
|
54
|
-
|
|
55
81
|
if [[ -z "${REALM_JSON_PATH}" ]]; then
|
|
56
|
-
echo "
|
|
57
|
-
|
|
58
|
-
echo "
|
|
82
|
+
echo "ERROR: Could not find realm.json in:" >&2
|
|
83
|
+
for p in "${CANDIDATES[@]}"; do echo " - $p" >&2; done
|
|
84
|
+
echo " Put realm.json next to the script (${SCRIPT_DIR}/realm.json)" >&2
|
|
85
|
+
echo " OR run with: REALM_JSON_PATH=/abs/path/realm.json bash init/tcinit.sh" >&2
|
|
59
86
|
exit 1
|
|
60
87
|
fi
|
|
61
|
-
echo "
|
|
88
|
+
echo "Using realm.json: ${REALM_JSON_PATH}"
|
|
62
89
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
66
|
-
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "❌ Missing dependency: $1" >&2; exit 1; }; }
|
|
90
|
+
# ─── Dependency checks ───────────────────────────────────────────────────────
|
|
91
|
+
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing dependency: $1" >&2; exit 1; }; }
|
|
67
92
|
need_cmd curl
|
|
68
93
|
need_cmd jq
|
|
69
94
|
need_cmd sed
|
|
70
95
|
need_cmd mktemp
|
|
71
96
|
|
|
72
|
-
# sed -i portability
|
|
97
|
+
# ─── sed -i portability ──────────────────────────────────────────────────────
|
|
73
98
|
if sed --version >/dev/null 2>&1; then SED_INPLACE=(-i); else SED_INPLACE=(-i ''); fi
|
|
74
99
|
|
|
100
|
+
# ─── Cleanup handler ─────────────────────────────────────────────────────────
|
|
101
|
+
# INCOMPLETE_FIRSTADMIN is set to 1 once IGA is enabled (step 4) and cleared
|
|
102
|
+
# back to 0 after the final grant+flip (step 14) completes. If the script exits
|
|
103
|
+
# non-zero while this flag is set, the realm is half-provisioned (IGA on, admin
|
|
104
|
+
# not yet granted/flipped) and we warn loudly. We do NOT auto-delete the realm.
|
|
105
|
+
TMP_REALM_JSON=""
|
|
106
|
+
INCOMPLETE_FIRSTADMIN=0
|
|
107
|
+
cleanup() {
|
|
108
|
+
local rc=$?
|
|
109
|
+
[[ -n "${TMP_REALM_JSON}" && -f "${TMP_REALM_JSON}" ]] && rm -f "${TMP_REALM_JSON}" || true
|
|
110
|
+
[[ -f "${MARKER_DIR}/.realm_name" ]] && rm -f "${MARKER_DIR}/.realm_name" || true
|
|
111
|
+
if [[ "${rc}" != "0" && "${INCOMPLETE_FIRSTADMIN}" == "1" ]]; then
|
|
112
|
+
echo "" >&2
|
|
113
|
+
echo "──────────────────────────────────────────────────────────────────────" >&2
|
|
114
|
+
echo "WARNING: realm '${REALM_NAME:-?}' may be left in an incomplete firstAdmin" >&2
|
|
115
|
+
echo " state (IGA enabled, admin not yet granted/flipped)." >&2
|
|
116
|
+
echo " Complete provisioning or delete the realm before use." >&2
|
|
117
|
+
echo "──────────────────────────────────────────────────────────────────────" >&2
|
|
118
|
+
fi
|
|
119
|
+
}
|
|
120
|
+
trap cleanup EXIT
|
|
121
|
+
|
|
122
|
+
# ─── Plaintext-to-remote credential warning (non-fatal) ──────────────────────
|
|
123
|
+
# TIDECLOAK_LOCAL_URL is resolved in the preamble above. If we are about to send
|
|
124
|
+
# admin credentials and bearer tokens over cleartext http:// to a NON-loopback
|
|
125
|
+
# host, warn the user. Loopback http:// stays silent; https:// stays silent.
|
|
126
|
+
case "${TIDECLOAK_LOCAL_URL}" in
|
|
127
|
+
http://localhost|http://localhost:*|http://localhost/*) : ;;
|
|
128
|
+
http://127.0.0.1|http://127.0.0.1:*|http://127.0.0.1/*) : ;;
|
|
129
|
+
http://\[::1\]|http://\[::1\]:*|http://\[::1\]/*) : ;;
|
|
130
|
+
http://*)
|
|
131
|
+
echo "WARNING: TIDECLOAK_LOCAL_URL='${TIDECLOAK_LOCAL_URL}' uses cleartext http:// to a non-loopback host." >&2
|
|
132
|
+
echo " Admin credentials and bearer tokens will be sent UNENCRYPTED over the network." >&2
|
|
133
|
+
echo " Use an https:// URL when targeting a remote TideCloak." >&2
|
|
134
|
+
;;
|
|
135
|
+
esac
|
|
136
|
+
|
|
75
137
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
76
|
-
#
|
|
138
|
+
# Helper: grab a fresh master admin-cli token (master realm password grant)
|
|
77
139
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
78
140
|
get_admin_token() {
|
|
79
141
|
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/realms/master/protocol/openid-connect/token" \
|
|
@@ -86,20 +148,82 @@ get_admin_token() {
|
|
|
86
148
|
}
|
|
87
149
|
|
|
88
150
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
-
#
|
|
151
|
+
# Helper: status-capturing admin API call.
|
|
152
|
+
# Usage: api METHOD URL [extra curl args...]
|
|
153
|
+
# then read the results from the globals it sets:
|
|
154
|
+
# RESP_CODE - the HTTP status ("000" on hard network failure)
|
|
155
|
+
# RESP_BODY - the full response body
|
|
156
|
+
# IMPORTANT: call api WITHOUT command substitution. It sets globals in the
|
|
157
|
+
# CURRENT shell; running it as `code=$(api ...)` would execute it in a subshell
|
|
158
|
+
# and the RESP_BODY/RESP_CODE it sets would be discarded before the parent
|
|
159
|
+
# could read them.
|
|
160
|
+
# - Authorization: Bearer ${TOKEN} is added automatically (refresh TOKEN first).
|
|
161
|
+
# - Never aborts the script on a non-2xx HTTP response (curl -s exits 0);
|
|
162
|
+
# only a hard network failure yields RESP_CODE "000".
|
|
90
163
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
164
|
+
RESP_CODE=""
|
|
165
|
+
RESP_BODY=""
|
|
166
|
+
api() {
|
|
167
|
+
local method="$1" url="$2"; shift 2
|
|
168
|
+
local tmp
|
|
169
|
+
tmp="$(mktemp)"
|
|
170
|
+
RESP_CODE=$(curl -s -o "${tmp}" -w "%{http_code}" -X "${method}" "${url}" \
|
|
171
|
+
-H "Authorization: Bearer ${TOKEN}" "$@") || RESP_CODE="000"
|
|
172
|
+
RESP_BODY="$(cat "${tmp}")"
|
|
173
|
+
rm -f "${tmp}"
|
|
95
174
|
}
|
|
96
|
-
trap cleanup EXIT
|
|
97
175
|
|
|
98
176
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
-
#
|
|
177
|
+
# Drain PENDING IGA change-requests (current iga-core native governance).
|
|
178
|
+
# LIST returns a bare JSON array; per-item id = .id; /approve body = {}
|
|
179
|
+
# (application/json). At threshold-1 /approve records AND auto-commits, so
|
|
180
|
+
# there is no separate /commit. Loop-until-empty is required because approving
|
|
181
|
+
# one CR unblocks its dependents.
|
|
100
182
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
183
|
+
drain_change_requests() {
|
|
184
|
+
local label="${1:-}" rounds=0
|
|
185
|
+
echo "Draining PENDING change-requests ${label}..."
|
|
186
|
+
while (( rounds < 12 )); do
|
|
187
|
+
TOKEN="$(get_admin_token)"
|
|
188
|
+
local list_tmp list_code
|
|
189
|
+
list_tmp="$(mktemp)"
|
|
190
|
+
list_code=$(curl -s -o "${list_tmp}" -w "%{http_code}" \
|
|
191
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/iga/change-requests?status=PENDING" \
|
|
192
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Cache-Control: no-store") || list_code="000"
|
|
193
|
+
if [[ "${list_code}" == "401" || "${list_code}" == "403" ]]; then
|
|
194
|
+
rm -f "${list_tmp}"
|
|
195
|
+
echo "FATAL: change-request LIST returned HTTP ${list_code} ${label} - admin authentication/authorization failed." >&2
|
|
196
|
+
echo " Refusing to treat an auth failure as an empty inbox. Check KC_USER/KC_PASSWORD and admin privileges." >&2
|
|
197
|
+
exit 1
|
|
198
|
+
fi
|
|
199
|
+
ids=$(jq -r '.[].id // empty' < "${list_tmp}")
|
|
200
|
+
rm -f "${list_tmp}"
|
|
201
|
+
if [[ -z "${ids}" ]]; then echo " inbox empty ${label}"; return 0; fi
|
|
202
|
+
while IFS= read -r id; do
|
|
203
|
+
[[ -z "$id" ]] && continue
|
|
204
|
+
st=$(curl -s -o /dev/null -w "%{http_code}" -X POST \
|
|
205
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/iga/change-requests/${id}/approve" \
|
|
206
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Content-Type: application/json" -d '{}')
|
|
207
|
+
case "$st" in
|
|
208
|
+
2*) : ;;
|
|
209
|
+
401|403)
|
|
210
|
+
echo "FATAL: change-request approve ${id} returned HTTP ${st} ${label} - admin authentication/authorization failed." >&2
|
|
211
|
+
echo " Check KC_USER/KC_PASSWORD and admin privileges." >&2
|
|
212
|
+
exit 1 ;;
|
|
213
|
+
404|409|412) : ;;
|
|
214
|
+
*) echo " WARN approve ${id} -> ${st}" ;;
|
|
215
|
+
esac
|
|
216
|
+
done <<< "${ids}"
|
|
217
|
+
((rounds++)) || true
|
|
218
|
+
done
|
|
219
|
+
echo " WARN drain hit round cap ${label}"; return 0
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
223
|
+
# Step 1: prepare realm JSON (placeholder substitution) + create the realm
|
|
224
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
101
225
|
REALM_NAME="${NEW_REALM_NAME}"
|
|
102
|
-
echo "${REALM_NAME}" > "${
|
|
226
|
+
echo "${REALM_NAME}" > "${MARKER_DIR}/.realm_name"
|
|
103
227
|
|
|
104
228
|
TMP_REALM_JSON="$(mktemp)"
|
|
105
229
|
cp "${REALM_JSON_PATH}" "${TMP_REALM_JSON}"
|
|
@@ -108,177 +232,333 @@ sed "${SED_INPLACE[@]}" "s|http://localhost:3000|${CLIENT_APP_URL}|g" "${TMP_REA
|
|
|
108
232
|
sed "${SED_INPLACE[@]}" "s|nextjs-test|${REALM_NAME}|g" "${TMP_REALM_JSON}"
|
|
109
233
|
sed "${SED_INPLACE[@]}" "s|myclient|${CLIENT_NAME}|g" "${TMP_REALM_JSON}"
|
|
110
234
|
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
235
|
+
# TideCloak console origin for THIS realm (used by the signed IdP settings).
|
|
236
|
+
TIDE_CONSOLE_ORIGIN="${TIDECLOAK_LOCAL_URL}/realms/${REALM_NAME}/tide-console/"
|
|
237
|
+
|
|
238
|
+
echo "Creating realm '${REALM_NAME}'..."
|
|
114
239
|
TOKEN="$(get_admin_token)"
|
|
115
|
-
|
|
116
|
-
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
117
|
-
-X POST "${TIDECLOAK_LOCAL_URL}/admin/realms" \
|
|
118
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
240
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms" \
|
|
119
241
|
-H "Content-Type: application/json" \
|
|
120
|
-
--data-binary @"${TMP_REALM_JSON}"
|
|
121
|
-
|
|
122
|
-
if [[ ${
|
|
123
|
-
echo "
|
|
242
|
+
--data-binary @"${TMP_REALM_JSON}"
|
|
243
|
+
code="${RESP_CODE}"
|
|
244
|
+
if [[ "${code}" == 2* ]]; then
|
|
245
|
+
echo " realm create -> ${code} (created)"
|
|
246
|
+
elif [[ "${code}" == "409" ]]; then
|
|
247
|
+
if [[ "${ALLOW_EXISTING_REALM:-}" == "1" ]]; then
|
|
248
|
+
echo " WARNING: realm '${REALM_NAME}' already exists (HTTP 409); ALLOW_EXISTING_REALM=1 set, proceeding." >&2
|
|
249
|
+
echo " NOTE: the drain step approves ALL pending change-requests in this realm," >&2
|
|
250
|
+
echo " including any unrelated to this provisioning run." >&2
|
|
251
|
+
else
|
|
252
|
+
echo "ERROR: Realm '${REALM_NAME}' already exists; this script provisions FRESH realms and would" >&2
|
|
253
|
+
echo " approve all pending change-requests. Set ALLOW_EXISTING_REALM=1 to override." >&2
|
|
254
|
+
exit 1
|
|
255
|
+
fi
|
|
124
256
|
else
|
|
125
|
-
echo "
|
|
257
|
+
echo "ERROR: realm creation failed (HTTP ${code})" >&2
|
|
258
|
+
echo " ${RESP_BODY}" >&2
|
|
126
259
|
exit 1
|
|
127
260
|
fi
|
|
128
261
|
|
|
129
|
-
#
|
|
130
|
-
#
|
|
131
|
-
#
|
|
262
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
263
|
+
# Step 2: setUpTideRealm (mints the realm VRK on the ORK network)
|
|
264
|
+
# REQUIRES healthy ORKs. Non-2xx = fail loudly.
|
|
265
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
266
|
+
echo "Setting up Tide realm (VRK keygen on the ORK network)..."
|
|
132
267
|
TOKEN="$(get_admin_token)"
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
|
|
136
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
268
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
|
|
137
269
|
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
138
|
-
--data-urlencode "email=${SUBSCRIPTION_EMAIL
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
echo "
|
|
270
|
+
--data-urlencode "email=${SUBSCRIPTION_EMAIL}" \
|
|
271
|
+
--data-urlencode "isRagnarokEnabled=true" \
|
|
272
|
+
--data-urlencode "skipLicense=false"
|
|
273
|
+
code="${RESP_CODE}"
|
|
274
|
+
if [[ "${code}" != 2* ]]; then
|
|
275
|
+
echo "ERROR: setUpTideRealm failed (HTTP ${code})." >&2
|
|
276
|
+
echo " This step needs a healthy ORK network (VRK keygen)." >&2
|
|
277
|
+
echo " Check that your ORKs are reachable and the license email is valid." >&2
|
|
278
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
279
|
+
exit 1
|
|
280
|
+
fi
|
|
281
|
+
echo " setUpTideRealm -> ${code}"
|
|
146
282
|
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
echo "✅ ${TYPE^} change-sets done."
|
|
174
|
-
}
|
|
175
|
-
approve_and_commit clients
|
|
283
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
284
|
+
# Step 3: stamp iga.attestor=tide on the realm BEFORE enabling IGA
|
|
285
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
286
|
+
echo "Stamping iga.attestor=tide on the realm..."
|
|
287
|
+
TOKEN="$(get_admin_token)"
|
|
288
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}"
|
|
289
|
+
code="${RESP_CODE}"
|
|
290
|
+
if [[ "${code}" != 2* ]]; then
|
|
291
|
+
echo "ERROR: could not fetch realm representation (HTTP ${code})." >&2
|
|
292
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
293
|
+
exit 1
|
|
294
|
+
fi
|
|
295
|
+
REALM_REP_FILE="$(mktemp)"
|
|
296
|
+
jq '.attributes = ((.attributes // {}) + {"iga.attestor":"tide"})' <<< "${RESP_BODY}" > "${REALM_REP_FILE}"
|
|
297
|
+
TOKEN="$(get_admin_token)"
|
|
298
|
+
api PUT "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}" \
|
|
299
|
+
-H "Content-Type: application/json" \
|
|
300
|
+
--data-binary @"${REALM_REP_FILE}"
|
|
301
|
+
code="${RESP_CODE}"
|
|
302
|
+
rm -f "${REALM_REP_FILE}"
|
|
303
|
+
if [[ "${code}" != 2* ]]; then
|
|
304
|
+
echo "ERROR: failed to set iga.attestor=tide (HTTP ${code})." >&2
|
|
305
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
echo " iga.attestor=tide -> ${code}"
|
|
176
309
|
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
310
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
311
|
+
# Step 4: enable IGA (application/json body {"enabled":true})
|
|
312
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
313
|
+
echo "Enabling IGA governance..."
|
|
180
314
|
TOKEN="$(get_admin_token)"
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
"
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
ROLE_JSON=$(curl -s -X GET \
|
|
196
|
-
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients/${CLIENT_UUID}/roles/${ADMIN_ROLE_NAME}" \
|
|
197
|
-
-H "Authorization: Bearer ${TOKEN}")
|
|
315
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/toggle-iga" \
|
|
316
|
+
-H "Content-Type: application/json" \
|
|
317
|
+
-d '{"enabled":true}'
|
|
318
|
+
code="${RESP_CODE}"
|
|
319
|
+
if [[ "${code}" == 2* ]]; then
|
|
320
|
+
echo " toggle-iga -> ${code} (IGA enabled)"
|
|
321
|
+
elif [[ "${code}" == "409" ]]; then
|
|
322
|
+
echo " toggle-iga -> 409 (already enabled)"
|
|
323
|
+
else
|
|
324
|
+
echo "ERROR: toggle-iga failed (HTTP ${code})." >&2
|
|
325
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
326
|
+
exit 1
|
|
327
|
+
fi
|
|
198
328
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
329
|
+
# From here until the final grant+flip (step 14) the realm is half-provisioned:
|
|
330
|
+
# IGA is enabled but the first admin is not yet granted/flipped. A non-zero exit
|
|
331
|
+
# in this window triggers the incomplete-firstAdmin warning in cleanup().
|
|
332
|
+
INCOMPLETE_FIRSTADMIN=1
|
|
203
333
|
|
|
204
|
-
|
|
334
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
335
|
+
# Step 5: drain the ADOPT change-requests raised by enabling IGA
|
|
336
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
337
|
+
drain_change_requests "(after IGA enable)"
|
|
205
338
|
|
|
206
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
339
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
340
|
+
# Step 6: create the admin user (tideInvitable + emailVerified:false at create)
|
|
341
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
342
|
+
echo "Creating admin user..."
|
|
209
343
|
TOKEN="$(get_admin_token)"
|
|
210
|
-
|
|
211
|
-
INVITE_LINK=$(curl -s -X POST \
|
|
212
|
-
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=43200" \
|
|
213
|
-
-H "Authorization: Bearer ${TOKEN}" \
|
|
344
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users" \
|
|
214
345
|
-H "Content-Type: application/json" \
|
|
215
|
-
-d '["
|
|
346
|
+
-d '{"username":"admin","email":"admin@tidecloak.com","firstName":"Admin","lastName":"User","enabled":true,"emailVerified":false,"attributes":{"tideInvitable":["true"]}}'
|
|
347
|
+
code="${RESP_CODE}"
|
|
348
|
+
if [[ "${code}" == 2* || "${code}" == "201" ]]; then
|
|
349
|
+
echo " create user -> ${code}"
|
|
350
|
+
elif [[ "${code}" == "409" ]]; then
|
|
351
|
+
echo " create user -> 409 (already exists)"
|
|
352
|
+
elif [[ "${code}" == "202" ]]; then
|
|
353
|
+
echo " create user -> 202 (IGA change-request parked)"
|
|
354
|
+
else
|
|
355
|
+
echo "ERROR: admin user creation failed (HTTP ${code})." >&2
|
|
356
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
357
|
+
exit 1
|
|
358
|
+
fi
|
|
216
359
|
|
|
217
|
-
|
|
218
|
-
|
|
360
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
361
|
+
# Step 7: drain (commits the CREATE_USER change-request) BEFORE resolving id
|
|
362
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
363
|
+
drain_change_requests "(after user create)"
|
|
219
364
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
365
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
366
|
+
# Step 8: resolve the admin userId (non-empty proves the create committed)
|
|
367
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
368
|
+
echo "Resolving admin userId..."
|
|
369
|
+
TOKEN="$(get_admin_token)"
|
|
370
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin&exact=true"
|
|
371
|
+
code="${RESP_CODE}"
|
|
372
|
+
USER_ID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
373
|
+
if [[ -z "${USER_ID}" ]]; then
|
|
374
|
+
echo "ERROR: could not resolve admin userId (HTTP ${code}); the CREATE_USER change-request may not have committed." >&2
|
|
375
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
376
|
+
exit 1
|
|
377
|
+
fi
|
|
378
|
+
echo " admin userId = ${USER_ID}"
|
|
227
379
|
|
|
380
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
381
|
+
# Step 9: mint the Tide enrollment link (link-tide-account-action)
|
|
382
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
383
|
+
echo "Minting Tide enrollment link..."
|
|
384
|
+
TOKEN="$(get_admin_token)"
|
|
385
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=3600" \
|
|
386
|
+
-H "Content-Type: application/json" \
|
|
387
|
+
-H "Accept: text/plain" \
|
|
388
|
+
-d '["link-tide-account-action"]'
|
|
389
|
+
code="${RESP_CODE}"
|
|
390
|
+
if [[ "${code}" != 2* ]]; then
|
|
391
|
+
echo "ERROR: failed to mint enrollment link (HTTP ${code})." >&2
|
|
392
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
393
|
+
exit 1
|
|
394
|
+
fi
|
|
395
|
+
INVITE_LINK="${RESP_BODY}"
|
|
396
|
+
|
|
397
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
398
|
+
# Step 10: HUMAN GATE - complete Tide enrollment in the enclave, then poll
|
|
399
|
+
# until BOTH tideUserKey and vuid attributes appear on the user.
|
|
400
|
+
# Re-fetch the admin token every iteration (master token ~60s).
|
|
401
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
402
|
+
echo ""
|
|
403
|
+
echo "──────────────────────────────────────────────────────────────────────"
|
|
404
|
+
echo " ACTION REQUIRED: Open this URL and complete Tide enrollment:"
|
|
405
|
+
echo ""
|
|
406
|
+
echo " ${INVITE_LINK}"
|
|
407
|
+
echo ""
|
|
408
|
+
echo " Enrollment happens in the Tide enclave and can take a few minutes."
|
|
409
|
+
echo " This script will wait and continue automatically once you are enrolled."
|
|
410
|
+
echo "──────────────────────────────────────────────────────────────────────"
|
|
411
|
+
echo ""
|
|
412
|
+
|
|
413
|
+
poll_attempt=0
|
|
414
|
+
POLL_MAX=100000 # effectively unbounded (~4s * 100000)
|
|
415
|
+
while true; do
|
|
416
|
+
poll_attempt=$(( poll_attempt + 1 ))
|
|
417
|
+
TOKEN="$(get_admin_token)"
|
|
418
|
+
ATTRS=$(curl -s "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin&exact=true&briefRepresentation=false" \
|
|
419
|
+
-H "Authorization: Bearer ${TOKEN}" -H "Cache-Control: no-store")
|
|
228
420
|
KEY=$(jq -r '.[0].attributes.tideUserKey[0] // empty' <<< "${ATTRS}")
|
|
229
421
|
VUID=$(jq -r '.[0].attributes.vuid[0] // empty' <<< "${ATTRS}")
|
|
230
|
-
|
|
231
422
|
if [[ -n "${KEY}" && -n "${VUID}" ]]; then
|
|
232
|
-
echo "
|
|
423
|
+
echo " Tide enrollment detected (tideUserKey + vuid present)."
|
|
233
424
|
break
|
|
234
425
|
fi
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
break
|
|
426
|
+
if (( poll_attempt >= POLL_MAX )); then
|
|
427
|
+
echo "ERROR: gave up waiting for Tide enrollment after ${poll_attempt} polls." >&2
|
|
428
|
+
exit 1
|
|
239
429
|
fi
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
echo
|
|
243
|
-
((attempt++))
|
|
430
|
+
printf " waiting for enrollment... (poll %s)\r" "${poll_attempt}"
|
|
431
|
+
sleep 4
|
|
244
432
|
done
|
|
245
433
|
|
|
246
|
-
|
|
434
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
435
|
+
# Step 11: drain (commits the tideUserKey / vuid change-requests)
|
|
436
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
437
|
+
drain_change_requests "(after enrollment)"
|
|
247
438
|
|
|
248
|
-
#
|
|
249
|
-
#
|
|
250
|
-
#
|
|
439
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
440
|
+
# Step 12: point the tide IdP at THIS realm's console origin, then sign it
|
|
441
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
442
|
+
echo "Signing tide IdP settings (CustomAdminUIDomain -> console origin)..."
|
|
251
443
|
TOKEN="$(get_admin_token)"
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
"${
|
|
256
|
-
|
|
444
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide"
|
|
445
|
+
code="${RESP_CODE}"
|
|
446
|
+
if [[ "${code}" != 2* ]]; then
|
|
447
|
+
echo "ERROR: could not fetch tide IdP instance (HTTP ${code})." >&2
|
|
448
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
449
|
+
exit 1
|
|
450
|
+
fi
|
|
451
|
+
IDP_REP_FILE="$(mktemp)"
|
|
452
|
+
jq --arg d "${TIDE_CONSOLE_ORIGIN}" '.config.CustomAdminUIDomain = $d' <<< "${RESP_BODY}" > "${IDP_REP_FILE}"
|
|
453
|
+
TOKEN="$(get_admin_token)"
|
|
454
|
+
api PUT "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide" \
|
|
455
|
+
-H "Content-Type: application/json" \
|
|
456
|
+
--data-binary @"${IDP_REP_FILE}"
|
|
457
|
+
code="${RESP_CODE}"
|
|
458
|
+
rm -f "${IDP_REP_FILE}"
|
|
459
|
+
if [[ "${code}" != 2* ]]; then
|
|
460
|
+
echo "ERROR: failed to update tide IdP settings (HTTP ${code})." >&2
|
|
461
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
462
|
+
exit 1
|
|
463
|
+
fi
|
|
464
|
+
TOKEN="$(get_admin_token)"
|
|
465
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/sign-idp-settings" \
|
|
466
|
+
-H "Content-Type: text/plain" \
|
|
467
|
+
--data-binary ""
|
|
468
|
+
code="${RESP_CODE}"
|
|
469
|
+
if [[ "${code}" != 2* ]]; then
|
|
470
|
+
echo "ERROR: sign-idp-settings failed (HTTP ${code}). Needs healthy ORKs." >&2
|
|
471
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
472
|
+
exit 1
|
|
473
|
+
fi
|
|
474
|
+
echo " IdP settings signed -> ${code}"
|
|
257
475
|
|
|
258
|
-
|
|
476
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
477
|
+
# Step 13: grant tide-realm-admin to the enrolled admin (AFTER enrollment)
|
|
478
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
479
|
+
echo "Granting tide-realm-admin to the admin user..."
|
|
480
|
+
TOKEN="$(get_admin_token)"
|
|
481
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${REALM_MGMT_CLIENT_ID}"
|
|
482
|
+
code="${RESP_CODE}"
|
|
483
|
+
RM_UUID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
484
|
+
if [[ -z "${RM_UUID}" ]]; then
|
|
485
|
+
echo "ERROR: could not resolve realm-management client uuid (HTTP ${code})." >&2
|
|
486
|
+
exit 1
|
|
487
|
+
fi
|
|
488
|
+
TOKEN="$(get_admin_token)"
|
|
489
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients/${RM_UUID}/roles/${ADMIN_ROLE_NAME}"
|
|
490
|
+
code="${RESP_CODE}"
|
|
491
|
+
if [[ "${code}" != 2* ]]; then
|
|
492
|
+
echo "ERROR: could not fetch ${ADMIN_ROLE_NAME} role (HTTP ${code})." >&2
|
|
493
|
+
echo " Response: ${RESP_BODY}" >&2
|
|
494
|
+
exit 1
|
|
495
|
+
fi
|
|
496
|
+
ROLE_REP="${RESP_BODY}"
|
|
259
497
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
498
|
+
# idempotency: skip if the mapping already exists
|
|
499
|
+
TOKEN="$(get_admin_token)"
|
|
500
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}"
|
|
501
|
+
code="${RESP_CODE}"
|
|
502
|
+
ALREADY=$(jq -r --arg r "${ADMIN_ROLE_NAME}" '[.[]?.name] | index($r) // empty' <<< "${RESP_BODY}")
|
|
503
|
+
if [[ -n "${ALREADY}" ]]; then
|
|
504
|
+
echo " ${ADMIN_ROLE_NAME} already mapped; skipping grant."
|
|
505
|
+
else
|
|
506
|
+
TOKEN="$(get_admin_token)"
|
|
507
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}" \
|
|
508
|
+
-H "Content-Type: application/json" \
|
|
509
|
+
-d "[${ROLE_REP}]"
|
|
510
|
+
code="${RESP_CODE}"
|
|
511
|
+
case "${code}" in
|
|
512
|
+
2*) echo " grant -> ${code}" ;;
|
|
513
|
+
202) echo " grant -> 202 (change-request parked)" ;;
|
|
514
|
+
409)
|
|
515
|
+
echo " grant -> 409; draining users then retrying once..."
|
|
516
|
+
drain_change_requests "(grant 409 retry)"
|
|
517
|
+
TOKEN="$(get_admin_token)"
|
|
518
|
+
api POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${RM_UUID}" \
|
|
519
|
+
-H "Content-Type: application/json" \
|
|
520
|
+
-d "[${ROLE_REP}]"
|
|
521
|
+
code="${RESP_CODE}"
|
|
522
|
+
case "${code}" in
|
|
523
|
+
2*|202|409) echo " grant retry -> ${code}" ;;
|
|
524
|
+
*) echo "ERROR: grant retry failed (HTTP ${code})." >&2; echo " ${RESP_BODY}" >&2; exit 1 ;;
|
|
525
|
+
esac
|
|
526
|
+
;;
|
|
527
|
+
*) echo "ERROR: grant failed (HTTP ${code})." >&2; echo " ${RESP_BODY}" >&2; exit 1 ;;
|
|
528
|
+
esac
|
|
529
|
+
fi
|
|
264
530
|
|
|
265
|
-
|
|
266
|
-
|
|
531
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
532
|
+
# Step 14: FINAL drain - commits GRANT_ROLES and flips firstAdmin->multiAdmin.
|
|
533
|
+
# No drain or governed write may run after this point.
|
|
534
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
535
|
+
drain_change_requests "(final: grant + firstAdmin->multiAdmin flip)"
|
|
267
536
|
|
|
268
|
-
|
|
537
|
+
# Grant+flip committed: the realm is fully provisioned. Clear the incomplete
|
|
538
|
+
# state flag so a later non-zero exit does NOT emit the half-provisioned warning.
|
|
539
|
+
INCOMPLETE_FIRSTADMIN=0
|
|
269
540
|
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
#
|
|
541
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
542
|
+
# Step 15: fetch the client adapter config (tidecloak.json)
|
|
543
|
+
# ═════════════════════════════════════════════════════════════════════════════
|
|
544
|
+
echo "Fetching adapter config..."
|
|
273
545
|
TOKEN="$(get_admin_token)"
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
546
|
+
api GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_NAME}"
|
|
547
|
+
code="${RESP_CODE}"
|
|
548
|
+
CLIENT_UUID="$(jq -r '.[0].id // empty' <<< "${RESP_BODY}")"
|
|
549
|
+
if [[ -z "${CLIENT_UUID}" ]]; then
|
|
550
|
+
echo "ERROR: could not resolve client '${CLIENT_NAME}' uuid (HTTP ${code})." >&2
|
|
551
|
+
exit 1
|
|
552
|
+
fi
|
|
553
|
+
TOKEN="$(get_admin_token)"
|
|
554
|
+
adapter_code=$(curl -s -o "${ADAPTER_OUTPUT_PATH}" -w "%{http_code}" \
|
|
280
555
|
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/get-installations-provider?clientId=${CLIENT_UUID}&providerId=keycloak-oidc-keycloak-json" \
|
|
281
|
-
-H "Authorization: Bearer ${TOKEN}"
|
|
556
|
+
-H "Authorization: Bearer ${TOKEN}")
|
|
557
|
+
if [[ "${adapter_code}" != 2* ]]; then
|
|
558
|
+
echo "ERROR: failed to fetch adapter config (HTTP ${adapter_code})." >&2
|
|
559
|
+
exit 1
|
|
560
|
+
fi
|
|
561
|
+
echo " Adapter config saved to ${ADAPTER_OUTPUT_PATH}"
|
|
282
562
|
|
|
283
|
-
echo "
|
|
284
|
-
echo "
|
|
563
|
+
echo ""
|
|
564
|
+
echo "All done. Realm '${REALM_NAME}' is provisioned and the first admin is enrolled."
|