@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.
Files changed (42) hide show
  1. package/dist/cjs/create.cjs +139 -0
  2. package/dist/cjs/create.js.map +1 -0
  3. package/dist/esm/create.js +134 -0
  4. package/dist/esm/create.js.map +1 -0
  5. package/dist/types/create.d.ts +3 -0
  6. package/dist/types/create.d.ts.map +1 -0
  7. package/init/.env.example +8 -0
  8. package/init/realm.json +162 -0
  9. package/init/tcinit.sh +261 -0
  10. package/package.json +40 -0
  11. package/template-js-app/.env.example +0 -0
  12. package/template-js-app/app/api/protected/route.js +40 -0
  13. package/template-js-app/app/auth/redirect/page.jsx +46 -0
  14. package/template-js-app/app/home/page.jsx +101 -0
  15. package/template-js-app/app/layout.jsx +18 -0
  16. package/template-js-app/app/page.jsx +64 -0
  17. package/template-js-app/app/provider.jsx +11 -0
  18. package/template-js-app/init/.env.example +8 -0
  19. package/template-js-app/init/realm.json +162 -0
  20. package/template-js-app/init/tcinit.sh +261 -0
  21. package/template-js-app/jsconfig.json +9 -0
  22. package/template-js-app/middleware.js +31 -0
  23. package/template-js-app/next.config.js +5 -0
  24. package/template-js-app/package.json +17 -0
  25. package/template-js-app/public/silent-check-sso.html +1 -0
  26. package/template-js-app/tidecloak.json +1 -0
  27. package/template-ts-app/.env.example +0 -0
  28. package/template-ts-app/app/api/protected/route.ts +38 -0
  29. package/template-ts-app/app/auth/redirect/page.tsx +46 -0
  30. package/template-ts-app/app/home/page.tsx +101 -0
  31. package/template-ts-app/app/layout.tsx +24 -0
  32. package/template-ts-app/app/page.tsx +65 -0
  33. package/template-ts-app/app/provider.tsx +23 -0
  34. package/template-ts-app/init/.env.example +8 -0
  35. package/template-ts-app/init/realm.json +162 -0
  36. package/template-ts-app/init/tcinit.sh +261 -0
  37. package/template-ts-app/middleware.ts +44 -0
  38. package/template-ts-app/next.config.js +0 -0
  39. package/template-ts-app/package.json +22 -0
  40. package/template-ts-app/public/silent-check-sso.html +1 -0
  41. package/template-ts-app/tidecloak.json +1 -0
  42. 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,9 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".",
4
+ "paths": {
5
+ "@/*": ["*"]
6
+ }
7
+ },
8
+ "include": ["next-env.d.ts", "**/*.js", "**/*.jsx"]
9
+ }
@@ -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,5 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+
4
+ }
5
+ module.exports = nextConfig
@@ -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
+ }