@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,101 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useTideCloak } from '@tidecloak/nextjs'
|
|
4
|
+
import { useState, useCallback } from 'react'
|
|
5
|
+
import tcConfig from "../../tidecloak.json"
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
export default function HomePage() {
|
|
9
|
+
const { logout, getValueFromIdToken, hasRealmRole, token } = useTideCloak()
|
|
10
|
+
const username = getValueFromIdToken('preferred_username') ?? '…'
|
|
11
|
+
const hasDefaultRole = hasRealmRole(`default-roles-${tcConfig["realm"]}`)
|
|
12
|
+
|
|
13
|
+
const [verifyResult, setVerifyResult] = useState<string | null>(null)
|
|
14
|
+
const [verifying, setVerifying] = useState(false)
|
|
15
|
+
|
|
16
|
+
const onLogout = useCallback(() => {
|
|
17
|
+
logout()
|
|
18
|
+
}, [logout])
|
|
19
|
+
|
|
20
|
+
const onVerify = useCallback(async () => {
|
|
21
|
+
setVerifying(true)
|
|
22
|
+
setVerifyResult(null)
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch('/api/protected', {
|
|
25
|
+
method: 'GET',
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: `Bearer ${token}`,
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
const data = await res.json()
|
|
31
|
+
if (res.ok) {
|
|
32
|
+
setVerifyResult(`✅ Authorized: vuid=${data.vuid}, key=${data.userkey}`)
|
|
33
|
+
} else {
|
|
34
|
+
setVerifyResult(`❌ ${res.status} - ${data.error || res.statusText}`)
|
|
35
|
+
}
|
|
36
|
+
} catch (err: any) {
|
|
37
|
+
setVerifyResult(`❌ Network error: ${err.message}`)
|
|
38
|
+
} finally {
|
|
39
|
+
setVerifying(false)
|
|
40
|
+
}
|
|
41
|
+
}, [])
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div style={containerStyle}>
|
|
45
|
+
<div style={cardStyle}>
|
|
46
|
+
<h1 style={{ margin: 0, fontSize: '1.5rem' }}>Hello, {username}!</h1>
|
|
47
|
+
<p style={{ margin: '0.5rem 0', color: '#555' }}>
|
|
48
|
+
Has default roles? <strong>{hasDefaultRole ? 'Yes' : 'No'}</strong>
|
|
49
|
+
</p>
|
|
50
|
+
|
|
51
|
+
<button onClick={onLogout} style={buttonStyle}>
|
|
52
|
+
Log out
|
|
53
|
+
</button>
|
|
54
|
+
|
|
55
|
+
<button
|
|
56
|
+
onClick={onVerify}
|
|
57
|
+
style={{ ...buttonStyle, marginTop: '0.5rem' }}
|
|
58
|
+
disabled={verifying}
|
|
59
|
+
>
|
|
60
|
+
{verifying ? 'Verifying…' : 'Verify Token'}
|
|
61
|
+
</button>
|
|
62
|
+
|
|
63
|
+
{verifyResult && (
|
|
64
|
+
<p style={{ marginTop: '1rem', color: verifyResult.startsWith('✅') ? 'green' : 'red' }}>
|
|
65
|
+
{verifyResult}
|
|
66
|
+
</p>
|
|
67
|
+
)}
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const containerStyle: React.CSSProperties = {
|
|
74
|
+
minHeight: '100vh',
|
|
75
|
+
display: 'flex',
|
|
76
|
+
alignItems: 'center',
|
|
77
|
+
justifyContent: 'center',
|
|
78
|
+
background: '#f5f5f5',
|
|
79
|
+
margin: 0,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const cardStyle: React.CSSProperties = {
|
|
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: React.CSSProperties = {
|
|
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,24 @@
|
|
|
1
|
+
import type { Metadata } from 'next'
|
|
2
|
+
import type { ReactNode } from 'react'
|
|
3
|
+
import { Provider } from './provider'
|
|
4
|
+
|
|
5
|
+
export const metadata: Metadata = {
|
|
6
|
+
title: 'My Tidecloak App',
|
|
7
|
+
description: 'A Next.js starter with Tidecloak',
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RootLayoutProps {
|
|
11
|
+
children: ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export default function RootLayout({ children }: RootLayoutProps): JSX.Element {
|
|
15
|
+
return (
|
|
16
|
+
<html lang="en">
|
|
17
|
+
<body>
|
|
18
|
+
<Provider>
|
|
19
|
+
{children}
|
|
20
|
+
</Provider>
|
|
21
|
+
</body>
|
|
22
|
+
</html>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, type CSSProperties } from 'react'
|
|
4
|
+
import { useTideCloak } from '@tidecloak/nextjs'
|
|
5
|
+
import { useRouter } from 'next/navigation'
|
|
6
|
+
import { useEffect } from 'react'
|
|
7
|
+
|
|
8
|
+
const containerStyle: CSSProperties = {
|
|
9
|
+
minHeight: '100vh',
|
|
10
|
+
display: 'flex',
|
|
11
|
+
alignItems: 'center',
|
|
12
|
+
justifyContent: 'center',
|
|
13
|
+
background: '#f5f5f5',
|
|
14
|
+
margin: 0,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const cardStyle: CSSProperties = {
|
|
18
|
+
background: '#fff',
|
|
19
|
+
padding: '2rem',
|
|
20
|
+
borderRadius: '8px',
|
|
21
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
|
|
22
|
+
textAlign: 'center',
|
|
23
|
+
maxWidth: '360px',
|
|
24
|
+
width: '100%',
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const buttonStyle: CSSProperties = {
|
|
28
|
+
marginTop: '1rem',
|
|
29
|
+
padding: '0.75rem 1.5rem',
|
|
30
|
+
fontSize: '1rem',
|
|
31
|
+
borderRadius: '4px',
|
|
32
|
+
border: 'none',
|
|
33
|
+
background: '#0070f3',
|
|
34
|
+
color: '#fff',
|
|
35
|
+
cursor: 'pointer',
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export default function LoginPage(): JSX.Element {
|
|
39
|
+
const { login, authenticated } = useTideCloak()
|
|
40
|
+
const router = useRouter()
|
|
41
|
+
|
|
42
|
+
const onLogin = useCallback(() => {
|
|
43
|
+
login()
|
|
44
|
+
}, [login])
|
|
45
|
+
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
if (authenticated) {
|
|
48
|
+
router.push('/home')
|
|
49
|
+
}
|
|
50
|
+
}, [authenticated])
|
|
51
|
+
|
|
52
|
+
return (
|
|
53
|
+
<div style={containerStyle}>
|
|
54
|
+
<div style={cardStyle}>
|
|
55
|
+
<h1 style={{ margin: 0, fontSize: '1.75rem' }}>Welcome!</h1>
|
|
56
|
+
<p style={{ color: '#555', marginTop: '0.5rem' }}>
|
|
57
|
+
Please log in to continue.
|
|
58
|
+
</p>
|
|
59
|
+
<button onClick={onLogin} style={buttonStyle}>
|
|
60
|
+
Log In
|
|
61
|
+
</button>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import React, { type ReactNode } from 'react'
|
|
4
|
+
import {
|
|
5
|
+
TideCloakProvider,
|
|
6
|
+
type TideCloakConfig,
|
|
7
|
+
} from '@tidecloak/nextjs'
|
|
8
|
+
import tcConfig from '../tidecloak.json'
|
|
9
|
+
|
|
10
|
+
interface ProviderProps {
|
|
11
|
+
children: ReactNode
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function Provider({ children }: ProviderProps): JSX.Element {
|
|
15
|
+
// If tidecloak.json isn’t already typed, you can cast it:
|
|
16
|
+
const config = tcConfig as unknown as TideCloakConfig
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<TideCloakProvider config={config}>
|
|
20
|
+
{children}
|
|
21
|
+
</TideCloakProvider>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -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!"
|