@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
package/init/tcinit.sh ADDED
@@ -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!"
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@tidecloak/create-nextjs",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+
6
+ "bin": {
7
+ "create-nextjs": "./dist/cjs/create.cjs"
8
+ },
9
+
10
+ "main": "./dist/cjs/create.cjs",
11
+ "module": "./dist/esm/create.js",
12
+ "exports": {
13
+ ".": {
14
+ "require": "./dist/cjs/create.cjs",
15
+ "import": "./dist/esm/create.js"
16
+ }
17
+ },
18
+
19
+ "files": [
20
+ "dist/",
21
+ "template-ts-app/",
22
+ "template-js-app/",
23
+ "init/"
24
+ ],
25
+
26
+ "scripts": {
27
+ "build:cjs": "tsc -p tsconfig.cjs.json && mv dist/cjs/create.js dist/cjs/create.cjs",
28
+ "build:esm": "tsc -p tsconfig.esm.json",
29
+ "build": "npm run build:cjs && npm run build:esm",
30
+ "prepare": "npm run build"
31
+ },
32
+
33
+ "dependencies": {
34
+ "enquirer": "^2"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^24.0.13",
38
+ "typescript": "^5.0.0"
39
+ }
40
+ }
File without changes
@@ -0,0 +1,40 @@
1
+ import { 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) {
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
+
28
+ // Return whatever protected data you need here
29
+ return NextResponse.json(
30
+ { vuid: user.vuid, userkey: user.tideuserkey },
31
+ { status: 200 }
32
+ )
33
+ } catch (err) {
34
+ console.error('Token verification failed:', err)
35
+ return NextResponse.json(
36
+ { error: 'Internal Server Error' },
37
+ { status: 500 }
38
+ )
39
+ }
40
+ }
@@ -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 = {
40
+ minHeight: '100vh',
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ justifyContent: 'center',
44
+ fontSize: '1rem',
45
+ color: '#555',
46
+ }
@@ -0,0 +1,101 @@
1
+ 'use client'
2
+
3
+ import { useState, useCallback } from 'react'
4
+ import { useTideCloak } from '@tidecloak/nextjs'
5
+ import tcConfig from "../../tidecloak.json"
6
+
7
+ export default function HomePage() {
8
+ const { logout, getValueFromIdToken, hasRealmRole, getAccessToken } = useTideCloak()
9
+ const username = getValueFromIdToken('preferred_username') || '…'
10
+ const hasDefaultRole = hasRealmRole(`default-roles-${tcConfig["realm"]}`)
11
+
12
+ const [verifyResult, setVerifyResult] = useState(null)
13
+ const [verifying, setVerifying] = useState(false)
14
+
15
+ const onLogout = useCallback(() => {
16
+ logout()
17
+ }, [logout])
18
+
19
+ const onVerify = useCallback(async () => {
20
+ setVerifying(true)
21
+ setVerifyResult(null)
22
+ try {
23
+ const token = await getAccessToken()
24
+ const res = await fetch('/api/protected', {
25
+ method: 'GET',
26
+ headers: { Authorization: `Bearer ${token}` },
27
+ })
28
+ const data = await res.json()
29
+ if (res.ok) {
30
+ setVerifyResult(`✅ Authorized: vuid=${data.vuid}, key=${data.userkey}`)
31
+ } else {
32
+ setVerifyResult(`❌ ${res.status} - ${data.error || res.statusText}`)
33
+ }
34
+ } catch (err) {
35
+ setVerifyResult(`❌ Network error: ${err.message}`)
36
+ } finally {
37
+ setVerifying(false)
38
+ }
39
+ }, [getAccessToken])
40
+
41
+ return (
42
+ <div style={containerStyle}>
43
+ <div style={cardStyle}>
44
+ <h1 style={{ margin: 0, fontSize: '1.5rem' }}>Hello, {username}!</h1>
45
+ <p style={{ margin: '0.5rem 0', color: '#555' }}>
46
+ Has default roles? <strong>{hasDefaultRole ? 'Yes' : 'No'}</strong>
47
+ </p>
48
+ <button onClick={onLogout} style={buttonStyle}>
49
+ Log out
50
+ </button>
51
+ <button
52
+ onClick={onVerify}
53
+ style={{ ...buttonStyle, marginTop: '0.5rem' }}
54
+ disabled={verifying}
55
+ >
56
+ {verifying ? 'Verifying…' : 'Verify Token'}
57
+ </button>
58
+ {verifyResult && (
59
+ <p
60
+ style={{
61
+ marginTop: '1rem',
62
+ color: verifyResult.startsWith('✅') ? 'green' : 'red',
63
+ }}
64
+ >
65
+ {verifyResult}
66
+ </p>
67
+ )}
68
+ </div>
69
+ </div>
70
+ )
71
+ }
72
+
73
+ const containerStyle = {
74
+ minHeight: '100vh',
75
+ display: 'flex',
76
+ alignItems: 'center',
77
+ justifyContent: 'center',
78
+ background: '#f5f5f5',
79
+ margin: 0,
80
+ }
81
+
82
+ const cardStyle = {
83
+ background: '#fff',
84
+ padding: '2rem',
85
+ borderRadius: '8px',
86
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
87
+ textAlign: 'center',
88
+ maxWidth: '360px',
89
+ width: '100%',
90
+ }
91
+
92
+ const buttonStyle = {
93
+ marginTop: '1rem',
94
+ padding: '0.75rem 1.5rem',
95
+ fontSize: '1rem',
96
+ borderRadius: '4px',
97
+ border: 'none',
98
+ background: '#0070f3',
99
+ color: '#fff',
100
+ cursor: 'pointer',
101
+ }
@@ -0,0 +1,18 @@
1
+ import { Provider } from './provider';
2
+
3
+ export const metadata = {
4
+ title: 'My Tidecloak App',
5
+ description: 'A Next.js starter with Tidecloak',
6
+ }
7
+
8
+ export default function RootLayout({ children }) {
9
+ return (
10
+ <html lang="en">
11
+ <body>
12
+ <Provider>
13
+ {children}
14
+ </Provider>
15
+ </body>
16
+ </html>
17
+ )
18
+ }
@@ -0,0 +1,64 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect } from 'react'
4
+ import { useTideCloak } from '@tidecloak/nextjs'
5
+ import { useRouter } from 'next/navigation'
6
+
7
+ const containerStyle = {
8
+ minHeight: '100vh',
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ justifyContent: 'center',
12
+ background: '#f5f5f5',
13
+ margin: 0,
14
+ }
15
+
16
+ const cardStyle = {
17
+ background: '#fff',
18
+ padding: '2rem',
19
+ borderRadius: '8px',
20
+ boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
21
+ textAlign: 'center',
22
+ maxWidth: '360px',
23
+ width: '100%',
24
+ }
25
+
26
+ const buttonStyle = {
27
+ marginTop: '1rem',
28
+ padding: '0.75rem 1.5rem',
29
+ fontSize: '1rem',
30
+ borderRadius: '4px',
31
+ border: 'none',
32
+ background: '#0070f3',
33
+ color: '#fff',
34
+ cursor: 'pointer',
35
+ }
36
+
37
+ export default function LoginPage() {
38
+ const { login, authenticated } = useTideCloak()
39
+ const router = useRouter()
40
+
41
+ const onLogin = useCallback(() => {
42
+ login()
43
+ }, [login])
44
+
45
+ useEffect(() => {
46
+ if (authenticated) {
47
+ router.push('/home')
48
+ }
49
+ }, [authenticated, router])
50
+
51
+ return (
52
+ <div style={containerStyle}>
53
+ <div style={cardStyle}>
54
+ <h1 style={{ margin: 0, fontSize: '1.75rem' }}>Welcome!</h1>
55
+ <p style={{ color: '#555', marginTop: '0.5rem' }}>
56
+ Please log in to continue.
57
+ </p>
58
+ <button onClick={onLogin} style={buttonStyle}>
59
+ Log In
60
+ </button>
61
+ </div>
62
+ </div>
63
+ )
64
+ }
@@ -0,0 +1,11 @@
1
+ 'use client'
2
+ import { TideCloakProvider } from "@tidecloak/nextjs";
3
+ import tcConfig from '../tidecloak.json';
4
+
5
+ export function Provider({ children }) {
6
+ return (
7
+ <TideCloakProvider config={tcConfig}>
8
+ {children}
9
+ </TideCloakProvider>
10
+ );
11
+ }
@@ -0,0 +1,8 @@
1
+ TIDECLOAK_LOCAL_URL="http://localhost:8080"
2
+ CLIENT_NAME="myclient"
3
+ CLIENT_APP_URL="http://localhost:3000"
4
+ REALM_JSON_PATH="./realm.json"
5
+ NEW_REALM_NAME="nextjs-test"
6
+ KC_USER="admin"
7
+ KC_PASSWORD="password"
8
+ # ADAPTER_OUTPUT_PATH=""