@tidecloak/create-nextjs 0.0.1
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/dist/cjs/create.cjs +139 -0
- package/dist/cjs/create.js.map +1 -0
- package/dist/esm/create.js +134 -0
- package/dist/esm/create.js.map +1 -0
- package/dist/types/create.d.ts +3 -0
- package/dist/types/create.d.ts.map +1 -0
- package/init/.env.example +8 -0
- package/init/realm.json +162 -0
- package/init/tcinit.sh +261 -0
- package/package.json +40 -0
- package/template-js-app/.env.example +0 -0
- package/template-js-app/app/api/protected/route.js +40 -0
- package/template-js-app/app/auth/redirect/page.jsx +46 -0
- package/template-js-app/app/home/page.jsx +101 -0
- package/template-js-app/app/layout.jsx +18 -0
- package/template-js-app/app/page.jsx +64 -0
- package/template-js-app/app/provider.jsx +11 -0
- package/template-js-app/init/.env.example +8 -0
- package/template-js-app/init/realm.json +162 -0
- package/template-js-app/init/tcinit.sh +261 -0
- package/template-js-app/jsconfig.json +9 -0
- package/template-js-app/middleware.js +31 -0
- package/template-js-app/next.config.js +5 -0
- package/template-js-app/package.json +17 -0
- package/template-js-app/public/silent-check-sso.html +1 -0
- package/template-js-app/tidecloak.json +1 -0
- package/template-ts-app/.env.example +0 -0
- package/template-ts-app/app/api/protected/route.ts +38 -0
- package/template-ts-app/app/auth/redirect/page.tsx +46 -0
- package/template-ts-app/app/home/page.tsx +101 -0
- package/template-ts-app/app/layout.tsx +24 -0
- package/template-ts-app/app/page.tsx +65 -0
- package/template-ts-app/app/provider.tsx +23 -0
- package/template-ts-app/init/.env.example +8 -0
- package/template-ts-app/init/realm.json +162 -0
- package/template-ts-app/init/tcinit.sh +261 -0
- package/template-ts-app/middleware.ts +44 -0
- package/template-ts-app/next.config.js +0 -0
- package/template-ts-app/package.json +22 -0
- package/template-ts-app/public/silent-check-sso.html +1 -0
- package/template-ts-app/tidecloak.json +1 -0
- package/template-ts-app/tsconfig.json +42 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
{
|
|
2
|
+
"realm": "nextjs-test",
|
|
3
|
+
"accessTokenLifespan": 600,
|
|
4
|
+
"enabled": true,
|
|
5
|
+
"sslRequired": "external",
|
|
6
|
+
"registrationAllowed": false,
|
|
7
|
+
"duplicateEmailsAllowed": true,
|
|
8
|
+
"roles": {
|
|
9
|
+
"realm": [
|
|
10
|
+
{
|
|
11
|
+
"name": "appUser",
|
|
12
|
+
"description": "Standard application user"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"name": "_tide_dob.selfencrypt",
|
|
16
|
+
"description": "Tide E2EE self-encrypt DoB data"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"name": "_tide_dob.selfdecrypt",
|
|
20
|
+
"description": "Tide E2EE self-decrypt DoB data"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "default-roles-nextjs-test",
|
|
24
|
+
"description": "${role_default-roles}",
|
|
25
|
+
"composite": true,
|
|
26
|
+
"composites": {
|
|
27
|
+
"realm": [
|
|
28
|
+
"_tide_dob.selfencrypt",
|
|
29
|
+
"_tide_dob.selfdecrypt",
|
|
30
|
+
"appUser"
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"client": {
|
|
36
|
+
"myclient": []
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"defaultRole": {
|
|
40
|
+
"name": "default-roles-nextjs-test",
|
|
41
|
+
"description": "${role_default-roles}",
|
|
42
|
+
"composite": true,
|
|
43
|
+
"clientRole": false
|
|
44
|
+
},
|
|
45
|
+
"clients": [
|
|
46
|
+
{
|
|
47
|
+
"clientId": "myclient",
|
|
48
|
+
"enabled": true,
|
|
49
|
+
"redirectUris": [
|
|
50
|
+
"http://localhost:3000",
|
|
51
|
+
"http://localhost:3000/*",
|
|
52
|
+
"http://localhost:3000/silent-check-sso.html",
|
|
53
|
+
"http://localhost:3000/auth/redirect"
|
|
54
|
+
],
|
|
55
|
+
"webOrigins": [
|
|
56
|
+
"http://localhost:3000"
|
|
57
|
+
],
|
|
58
|
+
"standardFlowEnabled": true,
|
|
59
|
+
"implicitFlowEnabled": false,
|
|
60
|
+
"publicClient": true,
|
|
61
|
+
"fullScopeAllowed": true,
|
|
62
|
+
"protocolMappers": [
|
|
63
|
+
{
|
|
64
|
+
"name": "Tide User Key",
|
|
65
|
+
"protocol": "openid-connect",
|
|
66
|
+
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
|
67
|
+
"consentRequired": false,
|
|
68
|
+
"config": {
|
|
69
|
+
"introspection.token.claim": "true",
|
|
70
|
+
"userinfo.token.claim": "true",
|
|
71
|
+
"user.attribute": "tideUserKey",
|
|
72
|
+
"lightweight.claim": "true",
|
|
73
|
+
"id.token.claim": "true",
|
|
74
|
+
"access.token.claim": "true",
|
|
75
|
+
"claim.name": "tideuserkey",
|
|
76
|
+
"jsonType.label": "String"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "Tide IGA Role Mapper",
|
|
81
|
+
"protocol": "openid-connect",
|
|
82
|
+
"protocolMapper": "tide-roles-mapper",
|
|
83
|
+
"consentRequired": false,
|
|
84
|
+
"config": {
|
|
85
|
+
"lightweight.claim": "true",
|
|
86
|
+
"access.token.claim": "true"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"name": "Tide vuid",
|
|
91
|
+
"protocol": "openid-connect",
|
|
92
|
+
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
|
93
|
+
"consentRequired": false,
|
|
94
|
+
"config": {
|
|
95
|
+
"introspection.token.claim": "true",
|
|
96
|
+
"userinfo.token.claim": "true",
|
|
97
|
+
"user.attribute": "vuid",
|
|
98
|
+
"lightweight.claim": "true",
|
|
99
|
+
"id.token.claim": "true",
|
|
100
|
+
"access.token.claim": "true",
|
|
101
|
+
"claim.name": "vuid",
|
|
102
|
+
"jsonType.label": "String"
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
}
|
|
107
|
+
],
|
|
108
|
+
"components": {
|
|
109
|
+
"org.keycloak.userprofile.UserProfileProvider": [
|
|
110
|
+
{
|
|
111
|
+
"providerId": "declarative-user-profile",
|
|
112
|
+
"config": {
|
|
113
|
+
"kc.user.profile.config": [
|
|
114
|
+
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
"authenticationFlows": [
|
|
121
|
+
{
|
|
122
|
+
"alias": "tidebrowser",
|
|
123
|
+
"providerId": "basic-flow",
|
|
124
|
+
"topLevel": true,
|
|
125
|
+
"authenticationExecutions": [
|
|
126
|
+
{
|
|
127
|
+
"authenticator": "auth-cookie",
|
|
128
|
+
"authenticatorFlow": false,
|
|
129
|
+
"requirement": "ALTERNATIVE",
|
|
130
|
+
"priority": 10,
|
|
131
|
+
"userSetupAllowed": false
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"authenticatorConfig": "tide browser",
|
|
135
|
+
"authenticator": "identity-provider-redirector",
|
|
136
|
+
"authenticatorFlow": false,
|
|
137
|
+
"requirement": "ALTERNATIVE",
|
|
138
|
+
"priority": 25,
|
|
139
|
+
"userSetupAllowed": false
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
],
|
|
144
|
+
"authenticatorConfig": [
|
|
145
|
+
{
|
|
146
|
+
"alias": "tide browser",
|
|
147
|
+
"config": {
|
|
148
|
+
"defaultProvider": "tide"
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
],
|
|
152
|
+
"browserFlow": "tidebrowser",
|
|
153
|
+
"requiredActions": [
|
|
154
|
+
{
|
|
155
|
+
"alias": "link-tide-account-action",
|
|
156
|
+
"name": "Link Tide Account",
|
|
157
|
+
"providerId": "link-tide-account-action",
|
|
158
|
+
"enabled": true
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"keycloakVersion": "26.1.4"
|
|
162
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
# Determine paths
|
|
6
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
8
|
+
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
9
|
+
|
|
10
|
+
# Load overrides from .env in the project root
|
|
11
|
+
if [ -f "${PROJECT_ROOT}/.env.example" ]; then
|
|
12
|
+
# shellcheck disable=SC1090
|
|
13
|
+
source "${PROJECT_ROOT}/.env.example"
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
17
|
+
# Defaults (override via env)
|
|
18
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
19
|
+
TIDECLOAK_LOCAL_URL="${TIDECLOAK_LOCAL_URL:-http://localhost:8080}"
|
|
20
|
+
CLIENT_APP_URL="${CLIENT_APP_URL:-http://localhost:3000}"
|
|
21
|
+
REALM_JSON_PATH="${REALM_JSON_PATH:-${SCRIPT_DIR}/realm.json}"
|
|
22
|
+
ADAPTER_OUTPUT_PATH="${ADAPTER_OUTPUT_PATH:-${PROJECT_ROOT}/tidecloak.json}"
|
|
23
|
+
NEW_REALM_NAME="${NEW_REALM_NAME:-nextjs-test}"
|
|
24
|
+
REALM_MGMT_CLIENT_ID="realm-management"
|
|
25
|
+
ADMIN_ROLE_NAME="tide-realm-admin"
|
|
26
|
+
KC_USER="${KC_USER:-admin}"
|
|
27
|
+
KC_PASSWORD="${KC_PASSWORD:-password}"
|
|
28
|
+
CLIENT_NAME="${CLIENT_NAME:-myclient}"
|
|
29
|
+
|
|
30
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
31
|
+
# sed -i portability
|
|
32
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
33
|
+
if sed --version >/dev/null 2>&1; then
|
|
34
|
+
SED_INPLACE=(-i)
|
|
35
|
+
else
|
|
36
|
+
SED_INPLACE=(-i '')
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
40
|
+
# Helper: grab an admin token
|
|
41
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
42
|
+
get_admin_token() {
|
|
43
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/realms/master/protocol/openid-connect/token" \
|
|
44
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
45
|
+
-d "username=${KC_USER}" \
|
|
46
|
+
-d "password=${KC_PASSWORD}" \
|
|
47
|
+
-d "grant_type=password" \
|
|
48
|
+
-d "client_id=admin-cli" \
|
|
49
|
+
| jq -r .access_token
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
53
|
+
# Step 1: prepare realm JSON
|
|
54
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
55
|
+
REALM_NAME="${NEW_REALM_NAME}"
|
|
56
|
+
echo "${REALM_NAME}" > "${PROJECT_ROOT}/.realm_name"
|
|
57
|
+
|
|
58
|
+
TMP_REALM_JSON="$(mktemp)"
|
|
59
|
+
cp "${REALM_JSON_PATH}" "${TMP_REALM_JSON}"
|
|
60
|
+
|
|
61
|
+
# replace placeholders
|
|
62
|
+
sed "${SED_INPLACE[@]}" "s|http://localhost:3000|${CLIENT_APP_URL}|g" "${TMP_REALM_JSON}"
|
|
63
|
+
sed "${SED_INPLACE[@]}" "s|nextjs-test|${REALM_NAME}|g" "${TMP_REALM_JSON}"
|
|
64
|
+
sed "${SED_INPLACE[@]}" "s|myclient|${CLIENT_NAME}|g" "${TMP_REALM_JSON}"
|
|
65
|
+
|
|
66
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
67
|
+
# Step 2: create realm (allow 409 if already exists)
|
|
68
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
69
|
+
TOKEN="$(get_admin_token)"
|
|
70
|
+
echo "🌍 Creating realm..."
|
|
71
|
+
status=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
72
|
+
-X POST "${TIDECLOAK_LOCAL_URL}/admin/realms" \
|
|
73
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
74
|
+
-H "Content-Type: application/json" \
|
|
75
|
+
--data-binary @"${TMP_REALM_JSON}")
|
|
76
|
+
|
|
77
|
+
if [[ ${status} == 2* || ${status} -eq 409 ]]; then
|
|
78
|
+
echo "✅ Realm created (or already exists)."
|
|
79
|
+
else
|
|
80
|
+
echo "❌ Realm creation failed (HTTP ${status})" >&2
|
|
81
|
+
exit 1
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
85
|
+
# Step 3: initialize Tide realm + IGA
|
|
86
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
TOKEN="$(get_admin_token)"
|
|
88
|
+
echo "🔐 Initializing Tide realm + IGA..."
|
|
89
|
+
|
|
90
|
+
response=$(curl -i -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
|
|
91
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
92
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
93
|
+
--data-urlencode "email=email@tide.org" 2>&1)
|
|
94
|
+
|
|
95
|
+
# parse status code from response
|
|
96
|
+
status=$(printf "%s" "${response}" | awk '/HTTP\/1\.[01]/ { code=$2 } END { print code }')
|
|
97
|
+
if [[ "${status}" != "200" && "${status}" != "201" && "${status}" != "204" ]]; then
|
|
98
|
+
echo "❌ setUpTideRealm failed with HTTP ${status}" >&2
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# toggle IGA
|
|
103
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/toggle-iga" \
|
|
104
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
105
|
+
-H "Content-Type: application/x-www-form-urlencoded" \
|
|
106
|
+
--data-urlencode "isIGAEnabled=true" \
|
|
107
|
+
> /dev/null
|
|
108
|
+
|
|
109
|
+
echo "✅ Tide realm + IGA done."
|
|
110
|
+
|
|
111
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
112
|
+
# Approve & commit change-sets
|
|
113
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
114
|
+
approve_and_commit() {
|
|
115
|
+
local TYPE=$1
|
|
116
|
+
echo "🔄 Processing ${TYPE} change-sets..."
|
|
117
|
+
TOKEN="$(get_admin_token)"
|
|
118
|
+
curl -s -X GET "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/change-set/${TYPE}/requests" \
|
|
119
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
120
|
+
| jq -c '.[]' | while read -r req; do
|
|
121
|
+
payload=$(jq -n \
|
|
122
|
+
--arg id "$(jq -r .draftRecordId <<< "${req}")" \
|
|
123
|
+
--arg cst "$(jq -r .changeSetType <<< "${req}")" \
|
|
124
|
+
--arg at "$(jq -r .actionType <<< "${req}")" \
|
|
125
|
+
'{changeSetId:$id,changeSetType:$cst,actionType:$at}')
|
|
126
|
+
|
|
127
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/change-set/sign" \
|
|
128
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
129
|
+
-H "Content-Type: application/json" \
|
|
130
|
+
-d "${payload}" \
|
|
131
|
+
> /dev/null
|
|
132
|
+
|
|
133
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/change-set/commit" \
|
|
134
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
135
|
+
-H "Content-Type: application/json" \
|
|
136
|
+
-d "${payload}" \
|
|
137
|
+
> /dev/null
|
|
138
|
+
done
|
|
139
|
+
echo "✅ ${TYPE^} change-sets done."
|
|
140
|
+
}
|
|
141
|
+
approve_and_commit clients
|
|
142
|
+
|
|
143
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
144
|
+
# Step 4: create admin user + assign role
|
|
145
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
146
|
+
TOKEN="$(get_admin_token)"
|
|
147
|
+
echo "👤 Creating admin user..."
|
|
148
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users" \
|
|
149
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
150
|
+
-H "Content-Type: application/json" \
|
|
151
|
+
-d '{"username":"admin","email":"admin@tidecloak.com","firstName":"admin","lastName":"user","enabled":true}' \
|
|
152
|
+
> /dev/null
|
|
153
|
+
|
|
154
|
+
USER_ID=$(curl -s -X GET \
|
|
155
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin" \
|
|
156
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
157
|
+
| jq -r '.[0].id')
|
|
158
|
+
|
|
159
|
+
CLIENT_UUID=$(curl -s -X GET \
|
|
160
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${REALM_MGMT_CLIENT_ID}" \
|
|
161
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
162
|
+
| jq -r '.[0].id')
|
|
163
|
+
|
|
164
|
+
ROLE_JSON=$(curl -s -X GET \
|
|
165
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients/${CLIENT_UUID}/roles/${ADMIN_ROLE_NAME}" \
|
|
166
|
+
-H "Authorization: Bearer ${TOKEN}")
|
|
167
|
+
|
|
168
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users/${USER_ID}/role-mappings/clients/${CLIENT_UUID}" \
|
|
169
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
170
|
+
-H "Content-Type: application/json" \
|
|
171
|
+
-d "[${ROLE_JSON}]" \
|
|
172
|
+
> /dev/null
|
|
173
|
+
|
|
174
|
+
echo "✅ Admin user & role done."
|
|
175
|
+
|
|
176
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
177
|
+
# Step 5: generate invite link + wait
|
|
178
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
179
|
+
TOKEN="$(get_admin_token)"
|
|
180
|
+
echo "🔗 Generating invite link..."
|
|
181
|
+
INVITE_LINK=$(curl -s -X POST \
|
|
182
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tideAdminResources/get-required-action-link?userId=${USER_ID}&lifespan=43200" \
|
|
183
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
184
|
+
-H "Content-Type: application/json" \
|
|
185
|
+
-d '["link-tide-account-action"]')
|
|
186
|
+
|
|
187
|
+
echo "🔗 Invite link: ${INVITE_LINK}"
|
|
188
|
+
echo "→ Send this link to the user so they can link their account."
|
|
189
|
+
|
|
190
|
+
MAX_TRIES=3
|
|
191
|
+
attempt=1
|
|
192
|
+
while true; do
|
|
193
|
+
echo -n "Checking link status (attempt ${attempt}/${MAX_TRIES})… "
|
|
194
|
+
ATTRS=$(curl -s -X GET \
|
|
195
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/users?username=admin" \
|
|
196
|
+
-H "Authorization: Bearer ${TOKEN}")
|
|
197
|
+
|
|
198
|
+
KEY=$(jq -r '.[0].attributes.tideUserKey[0] // empty' <<< "${ATTRS}")
|
|
199
|
+
VUID=$(jq -r '.[0].attributes.vuid[0] // empty' <<< "${ATTRS}")
|
|
200
|
+
|
|
201
|
+
if [[ -n "${KEY}" && -n "${VUID}" ]]; then
|
|
202
|
+
echo "✅ Linked!"
|
|
203
|
+
break
|
|
204
|
+
fi
|
|
205
|
+
|
|
206
|
+
if (( attempt >= MAX_TRIES )); then
|
|
207
|
+
echo "⚠️ Max retries reached (${MAX_TRIES}). Moving on."
|
|
208
|
+
break
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
read -t 30 -p "Not linked yet; press ENTER to retry or wait 30s…" || true
|
|
212
|
+
echo
|
|
213
|
+
((attempt++))
|
|
214
|
+
done
|
|
215
|
+
|
|
216
|
+
approve_and_commit users
|
|
217
|
+
|
|
218
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
219
|
+
# Step 6: update CustomAdminUIDomain
|
|
220
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
221
|
+
TOKEN="$(get_admin_token)"
|
|
222
|
+
echo "🌐 Updating CustomAdminUIDomain..."
|
|
223
|
+
|
|
224
|
+
INST_JSON=$(curl -s -X GET \
|
|
225
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide" \
|
|
226
|
+
-H "Authorization: Bearer ${TOKEN}")
|
|
227
|
+
|
|
228
|
+
UPDATED_JSON=$(jq --arg d "${CLIENT_APP_URL}" '.config.CustomAdminUIDomain = $d' <<< "${INST_JSON}")
|
|
229
|
+
|
|
230
|
+
curl -s -X PUT "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/identity-provider/instances/tide" \
|
|
231
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
232
|
+
-H "Content-Type: application/json" \
|
|
233
|
+
-d "${UPDATED_JSON}" \
|
|
234
|
+
> /dev/null
|
|
235
|
+
|
|
236
|
+
curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/sign-idp-settings" \
|
|
237
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
238
|
+
> /dev/null
|
|
239
|
+
|
|
240
|
+
echo "✅ CustomAdminUIDomain updated + signed."
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
244
|
+
# Step 7: fetch adapter config + cleanup
|
|
245
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
246
|
+
TOKEN="$(get_admin_token)"
|
|
247
|
+
echo "📥 Fetching adapter config…"
|
|
248
|
+
CLIENT_UUID=$(curl -s -X GET \
|
|
249
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/clients?clientId=${CLIENT_NAME}" \
|
|
250
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
251
|
+
| jq -r '.[0].id')
|
|
252
|
+
|
|
253
|
+
curl -s -X GET \
|
|
254
|
+
"${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/get-installations-provider?clientId=${CLIENT_UUID}&providerId=keycloak-oidc-keycloak-json" \
|
|
255
|
+
-H "Authorization: Bearer ${TOKEN}" \
|
|
256
|
+
> "${ADAPTER_OUTPUT_PATH}"
|
|
257
|
+
|
|
258
|
+
echo "✅ Adapter config saved to ${ADAPTER_OUTPUT_PATH}"
|
|
259
|
+
rm -f "${PROJECT_ROOT}/.realm_name" "${TMP_REALM_JSON}"
|
|
260
|
+
|
|
261
|
+
echo "🎉 All done!"
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// an example nextJS middleware router that does server-side validation on all traffic to secure pages
|
|
2
|
+
import { NextResponse } from "next/server";
|
|
3
|
+
import { createTideCloakMiddleware } from "@tidecloak/nextjs/server";
|
|
4
|
+
import tcConfig from "./tidecloak.json";
|
|
5
|
+
|
|
6
|
+
// Developer should list all secure pages and their respective allowed roles
|
|
7
|
+
export default createTideCloakMiddleware({
|
|
8
|
+
config: tcConfig,
|
|
9
|
+
protectedRoutes:{
|
|
10
|
+
"/protected": ["offline_access"]
|
|
11
|
+
},
|
|
12
|
+
onFailure: (ctx, req) => {
|
|
13
|
+
console.debug("Token verification failed");
|
|
14
|
+
return NextResponse.json(
|
|
15
|
+
{ error: 'Access forbidden: invalid token' },
|
|
16
|
+
{ status: 403 }
|
|
17
|
+
)
|
|
18
|
+
},
|
|
19
|
+
onSuccess: (ctx, req) => {
|
|
20
|
+
return NextResponse.next();
|
|
21
|
+
},
|
|
22
|
+
onError: (ctx, req) => {
|
|
23
|
+
console.error("[Middleware] ", err);
|
|
24
|
+
return NextResponse.redirect(new URL("/auth/redirect", req.url));
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
//Which routes the middleware should run on:
|
|
29
|
+
export const config = {
|
|
30
|
+
matcher: ["/protected/:path*"],
|
|
31
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"init": "bash init/tcinit.sh"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"next": "14.x",
|
|
13
|
+
"react": "18.x",
|
|
14
|
+
"react-dom": "18.x",
|
|
15
|
+
"@tidecloak/nextjs": "^0.9.11"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<html><body><script>parent.postMessage(location.href, location.origin)</script></body></html>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
File without changes
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from 'next/server'
|
|
2
|
+
import { verifyTideCloakToken } from '@tidecloak/nextjs/server'
|
|
3
|
+
import tcConfig from '../../../tidecloak.json'
|
|
4
|
+
|
|
5
|
+
const ALLOWED_ROLE = 'offline_access'
|
|
6
|
+
|
|
7
|
+
export async function GET(request: NextRequest): Promise<NextResponse> {
|
|
8
|
+
const authHeader = request.headers.get('authorization')
|
|
9
|
+
if (!authHeader?.startsWith('Bearer ')) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'Unauthorized: Missing or invalid token' },
|
|
12
|
+
{ status: 401 }
|
|
13
|
+
)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const token = authHeader.split(' ')[1]
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
const user = await verifyTideCloakToken(tcConfig, token, [ALLOWED_ROLE])
|
|
20
|
+
|
|
21
|
+
if (!user) {
|
|
22
|
+
return NextResponse.json(
|
|
23
|
+
{ error: 'Forbidden: Invalid token or insufficient role' },
|
|
24
|
+
{ status: 403 }
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
return NextResponse.json(
|
|
28
|
+
{ vuid: user.vuid, userkey: user.tideuserkey },
|
|
29
|
+
{ status: 200 }
|
|
30
|
+
)
|
|
31
|
+
} catch (err) {
|
|
32
|
+
console.error('Token verification failed:', err)
|
|
33
|
+
return NextResponse.json(
|
|
34
|
+
{ error: 'Internal Server Error' },
|
|
35
|
+
{ status: 500 }
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import { useTideCloak } from '@tidecloak/nextjs';
|
|
6
|
+
|
|
7
|
+
export default function RedirectPage() {
|
|
8
|
+
const { authenticated, isInitializing, logout } = useTideCloak()
|
|
9
|
+
const router = useRouter()
|
|
10
|
+
|
|
11
|
+
// Handles redirect when middleware detects token expiry
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const doLogOut = async () => {
|
|
14
|
+
logout();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const params = new URLSearchParams(window.location.search);
|
|
18
|
+
const auth = params.get("auth");
|
|
19
|
+
|
|
20
|
+
if (auth === "failed") {
|
|
21
|
+
sessionStorage.setItem("tokenExpired", "true");
|
|
22
|
+
doLogOut();
|
|
23
|
+
}
|
|
24
|
+
}, [])
|
|
25
|
+
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (!isInitializing) {
|
|
28
|
+
router.push(authenticated ? '/home' : '/')
|
|
29
|
+
}
|
|
30
|
+
}, [authenticated, isInitializing, router])
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div style={containerStyle}>
|
|
34
|
+
<p>Waiting for authentication...</p>
|
|
35
|
+
</div>
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const containerStyle: React.CSSProperties = {
|
|
40
|
+
minHeight: '100vh',
|
|
41
|
+
display: 'flex',
|
|
42
|
+
alignItems: 'center',
|
|
43
|
+
justifyContent: 'center',
|
|
44
|
+
fontSize: '1rem',
|
|
45
|
+
color: '#555',
|
|
46
|
+
}
|