@tidecloak/create-nextjs 0.13.30 → 0.13.31

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/README.md CHANGED
@@ -38,23 +38,22 @@ npm init @tidecloak/nextjs@latest my-app
38
38
 
39
39
  ```
40
40
  my-app/
41
- ├── app
42
- | └── api/
41
+ ├── app/
42
+ | ├── api/
43
43
  | │ └── protected/
44
44
  | │ └── route.js <- A protected API on your NextJS server that verifies the user's access token
45
45
  | ├── auth/
46
46
  | │ └── redirect/
47
47
  | │ └── page.jsx <- A dedicated page to redirect the user back to once authentication is complete
48
48
  | ├── home/
49
- | | └── page.jsx <- Your home page the user goes to once autenticated
50
- | ├── public/
51
- │ | └── silent-check-sso.html
49
+ | | └── page.jsx <- Your home page the user goes to once authenticated
52
50
  | ├── layout.jsx <- Entry point of your app before the user sees any actual pages
53
51
  | └── page.jsx <- Your login page the user is brought to when they need to authenticate
54
- |
52
+ ├── public/
53
+ │ └── silent-check-sso.html <- Silent SSO check page served at the site root
55
54
  ├── tidecloak.json <- Where your Tidecloak configuration sits
56
- ├── next.config.json
57
- ├── middleware.js <- Run on each page navigation - this is where the Tideccloak token is verified
55
+ ├── next.config.js
56
+ ├── middleware.js <- Run on each page navigation - this is where the Tidecloak token is verified
58
57
  └── package.json
59
58
  ```
60
59
 
@@ -204,7 +203,8 @@ TideCloak provides server-side route protection for both the **Pages Router** an
204
203
  #### Options
205
204
 
206
205
  * **`config`** (`TidecloakConfig`): Your Tidecloak adapter JSON (downloaded from your TideCloak client settings).
207
- * **`protectedRoutes`** (`ProtectedRoutesMap`): Map of path patterns to arrays of required roles.
206
+ * **`protectedRoutes`** (`ProtectedRoutesMap`): Map of path patterns to arrays of required roles. A trailing `/*` glob (e.g. `"/admin/*"`) also matches the bare base path (`/admin`).
207
+ * **`cookieName`** (`string`, default `"kcToken"`): Name of the cookie that holds the access token.
208
208
  * **`onRequest`**<br>`(ctx: { token: string | null }, req: NextRequest) => NextResponse | void`<br>Hook before auth logic; can short-circuit by returning a `NextResponse`.
209
209
  * **`onSuccess`**<br>`(ctx: { payload: Record<string, any> }, req: NextRequest) => NextResponse | void`<br>Hook after successful auth & role checks; override the response by returning one.
210
210
  * **`onFailure`**<br>`(ctx: { token: string | null }, req: NextRequest) => NextResponse | void`<br>Hook when auth or role check fails; return a `NextResponse` to override.
package/init/realm.json CHANGED
@@ -12,12 +12,12 @@
12
12
  "description": "Standard application user"
13
13
  },
14
14
  {
15
- "name": "_tide_dob.selfencrypt",
16
- "description": "Tide E2EE self-encrypt DoB data"
15
+ "name": "_tide_message.selfencrypt",
16
+ "description": "Tide E2EE self-encrypt message data"
17
17
  },
18
18
  {
19
- "name": "_tide_dob.selfdecrypt",
20
- "description": "Tide E2EE self-decrypt DoB data"
19
+ "name": "_tide_message.selfdecrypt",
20
+ "description": "Tide E2EE self-decrypt message data"
21
21
  },
22
22
  {
23
23
  "name": "default-roles-nextjs-test",
@@ -25,8 +25,8 @@
25
25
  "composite": true,
26
26
  "composites": {
27
27
  "realm": [
28
- "_tide_dob.selfencrypt",
29
- "_tide_dob.selfdecrypt",
28
+ "_tide_message.selfencrypt",
29
+ "_tide_message.selfdecrypt",
30
30
  "appUser"
31
31
  ]
32
32
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tidecloak/create-nextjs",
3
- "version": "0.13.30",
3
+ "version": "0.13.31",
4
4
  "type": "module",
5
5
  "description": "Scaffold a TideCloak-ready Next.js app with optional IAM setup and working auth - start building instantly with a live example",
6
6
  "bin": {
@@ -5,19 +5,40 @@ import { useTideCloak } from '@tidecloak/nextjs'
5
5
  import tcConfig from "../../tidecloak.json"
6
6
 
7
7
  export default function HomePage() {
8
- const { logout, getValueFromIdToken, hasRealmRole, token } = useTideCloak()
8
+ const { logout, getValueFromIdToken, hasRealmRole, token, doEncrypt, doDecrypt } = useTideCloak()
9
9
 
10
10
  const [username, setUsername] = useState("")
11
11
  const [hasDefaultRole, setHasDefaultRole] = useState(false)
12
12
  const [verifyResult, setVerifyResult] = useState(null)
13
13
  const [verifying, setVerifying] = useState(false)
14
14
 
15
+ // Self encrypt/decrypt: data is bound to THIS user's identity — only they can
16
+ // decrypt it. The "message" tag matches the _tide_message.selfencrypt/.selfdecrypt
17
+ // roles granted to every user in init/realm.json.
18
+ const TAG = "message"
19
+ const [text, setText] = useState("") // always the decrypted value, editable
20
+ const [busy, setBusy] = useState(false)
21
+ const [status, setStatus] = useState("")
22
+ const [cryptoErr, setCryptoErr] = useState("")
23
+
24
+ // localStorage key for the saved note, namespaced per user (vuid).
25
+ const storageKey = () => `tide-note:${getValueFromIdToken("vuid")}`
26
+
15
27
  useEffect(() => {
16
28
  if (token) {
17
29
  const name = getValueFromIdToken("preferred_username")
18
30
  const defaultRole = hasRealmRole(`default-roles-${tcConfig["realm"]}`)
19
31
  setUsername(name);
20
32
  setHasDefaultRole(defaultRole)
33
+
34
+ // Restore the saved note. Only the CIPHERTEXT is persisted; we decrypt it
35
+ // client-side here so the field shows plaintext when you log back in.
36
+ const stored = typeof window !== "undefined" ? localStorage.getItem(storageKey()) : null
37
+ if (stored) {
38
+ doDecrypt([{ encrypted: stored, tags: [TAG] }])
39
+ .then((res) => setText(String(res[0])))
40
+ .catch(() => {})
41
+ }
21
42
  }
22
43
 
23
44
  }, [token])
@@ -48,6 +69,25 @@ export default function HomePage() {
48
69
  }
49
70
  }, [token])
50
71
 
72
+ // Submit = encrypt the current value, persist the ciphertext, then decrypt it
73
+ // straight back so the field keeps showing plaintext. We store only the
74
+ // ciphertext (here in localStorage; in a real app, on your server) — it's
75
+ // decrypted again when you log back in.
76
+ const onSubmit = useCallback(async () => {
77
+ setBusy(true); setCryptoErr(""); setStatus("")
78
+ try {
79
+ const [ct] = await doEncrypt([{ data: text, tags: [TAG] }])
80
+ if (typeof window !== "undefined") localStorage.setItem(storageKey(), ct)
81
+ const [pt] = await doDecrypt([{ encrypted: ct, tags: [TAG] }])
82
+ setText(String(pt))
83
+ setStatus("Message successfully stored")
84
+ } catch (err) {
85
+ setCryptoErr(err.message || "Failed")
86
+ } finally {
87
+ setBusy(false)
88
+ }
89
+ }, [text, doEncrypt, doDecrypt])
90
+
51
91
  return (
52
92
  <div style={containerStyle}>
53
93
  <div style={cardStyle}>
@@ -75,11 +115,45 @@ export default function HomePage() {
75
115
  {verifyResult}
76
116
  </p>
77
117
  )}
118
+
119
+ {/* ── Encrypted note: always shown decrypted; Submit re-encrypts then decrypts ── */}
120
+ <div style={{ marginTop: '1.5rem', borderTop: '1px solid #eee', paddingTop: '1rem', textAlign: 'left' }}>
121
+ <h2 style={{ fontSize: '1.1rem', margin: '0 0 0.25rem' }}>Your encrypted note</h2>
122
+ <p style={{ margin: '0 0 0.5rem', color: '#777', fontSize: '0.85rem' }}>
123
+ This is an encrypted textbox under your own identity — only you can decrypt it.
124
+ </p>
125
+
126
+ <textarea
127
+ value={text}
128
+ onChange={(e) => setText(e.target.value)}
129
+ placeholder="Type your note…"
130
+ style={textareaStyle}
131
+ />
132
+ <button onClick={onSubmit} style={buttonStyle} disabled={busy}>
133
+ {busy ? 'Submitting…' : 'Submit'}
134
+ </button>
135
+
136
+ {status && <p style={{ color: 'green', marginTop: '0.5rem', fontSize: '0.85rem' }}>{status}</p>}
137
+
138
+ {cryptoErr && <p style={{ color: 'red', marginTop: '0.5rem' }}>{cryptoErr}</p>}
139
+ </div>
78
140
  </div>
79
141
  </div>
80
142
  )
81
143
  }
82
144
 
145
+ const textareaStyle = {
146
+ width: '100%',
147
+ minHeight: '64px',
148
+ padding: '0.5rem',
149
+ borderRadius: '4px',
150
+ border: '1px solid #ccc',
151
+ boxSizing: 'border-box',
152
+ fontFamily: 'inherit',
153
+ fontSize: '0.9rem',
154
+ }
155
+
156
+
83
157
  const containerStyle = {
84
158
  minHeight: '100vh',
85
159
  display: 'flex',
@@ -12,12 +12,12 @@
12
12
  "description": "Standard application user"
13
13
  },
14
14
  {
15
- "name": "_tide_dob.selfencrypt",
16
- "description": "Tide E2EE self-encrypt DoB data"
15
+ "name": "_tide_message.selfencrypt",
16
+ "description": "Tide E2EE self-encrypt message data"
17
17
  },
18
18
  {
19
- "name": "_tide_dob.selfdecrypt",
20
- "description": "Tide E2EE self-decrypt DoB data"
19
+ "name": "_tide_message.selfdecrypt",
20
+ "description": "Tide E2EE self-decrypt message data"
21
21
  },
22
22
  {
23
23
  "name": "default-roles-nextjs-test",
@@ -25,8 +25,8 @@
25
25
  "composite": true,
26
26
  "composites": {
27
27
  "realm": [
28
- "_tide_dob.selfencrypt",
29
- "_tide_dob.selfdecrypt",
28
+ "_tide_message.selfencrypt",
29
+ "_tide_message.selfdecrypt",
30
30
  "appUser"
31
31
  ]
32
32
  }
@@ -135,7 +135,7 @@ echo "🔐 Initializing Tide realm + IGA..."
135
135
  curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
136
136
  -H "Authorization: Bearer ${TOKEN}" \
137
137
  -H "Content-Type: application/x-www-form-urlencoded" \
138
- --data-urlencode "email=email@tide.org" >/dev/null
138
+ --data-urlencode "email=${SUBSCRIPTION_EMAIL:-test@demo.org}" >/dev/null
139
139
 
140
140
  curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/toggle-iga" \
141
141
  -H "Authorization: Bearer ${TOKEN}" \
@@ -7,6 +7,9 @@ import tcConfig from "./tidecloak.json";
7
7
  export default createTideCloakMiddleware({
8
8
  config: tcConfig,
9
9
  protectedRoutes:{
10
+ // "offline_access" is granted to every authenticated user, so this protects
11
+ // the route for "any logged-in user". Swap it for a real realm/client role
12
+ // (e.g. "appUser") to demonstrate role-based access control.
10
13
  "/protected": ["offline_access"]
11
14
  },
12
15
  onFailure: (ctx, req) => {
@@ -19,13 +22,14 @@ export default createTideCloakMiddleware({
19
22
  onSuccess: (ctx, req) => {
20
23
  return NextResponse.next();
21
24
  },
22
- onError: (ctx, req) => {
25
+ // Note: onError receives (err, req) - the error is the first argument.
26
+ onError: (err, req) => {
23
27
  console.error("[Middleware] ", err);
24
28
  return NextResponse.redirect(new URL("/auth/redirect", req.url));
25
29
  }
26
30
  })
27
31
 
28
- //Which routes the middleware should run on:
32
+ //Which routes the middleware should run on (include the bare path and subpaths):
29
33
  export const config = {
30
- matcher: ["/protected/:path*"],
34
+ matcher: ["/protected", "/protected/:path*"],
31
35
  };
@@ -12,6 +12,6 @@
12
12
  "next": "16.x",
13
13
  "react": "19.x",
14
14
  "react-dom": "19.x",
15
- "@tidecloak/nextjs": "^0.13.30"
15
+ "@tidecloak/nextjs": "^0.13.31"
16
16
  }
17
17
  }
@@ -16,7 +16,7 @@ export async function GET(request: NextRequest): Promise<NextResponse> {
16
16
  const token = authHeader.split(' ')[1]
17
17
 
18
18
  try {
19
- const user = await verifyTideCloakToken(tcConfig, token, [ALLOWED_ROLE])
19
+ const user = await verifyTideCloakToken(tcConfig, token, [ALLOWED_ROLE]) as Record<string, any>
20
20
 
21
21
  if (!user) {
22
22
  return NextResponse.json(
@@ -6,19 +6,40 @@ import tcConfig from "../../tidecloak.json"
6
6
 
7
7
 
8
8
  export default function HomePage() {
9
- const { logout, getValueFromIdToken, hasRealmRole, token } = useTideCloak()
9
+ const { logout, getValueFromIdToken, hasRealmRole, token, doEncrypt, doDecrypt } = useTideCloak()
10
10
 
11
11
  const [username, setUsername] = useState("")
12
12
  const [hasDefaultRole, setHasDefaultRole] = useState(false)
13
13
  const [verifyResult, setVerifyResult] = useState<string | null>(null)
14
14
  const [verifying, setVerifying] = useState(false)
15
15
 
16
- useEffect(() => {
16
+ // Self encrypt/decrypt: data is bound to THIS user's identity — only they can
17
+ // decrypt it. The "message" tag matches the _tide_message.selfencrypt/.selfdecrypt
18
+ // roles granted to every user in init/realm.json.
19
+ const TAG = "message"
20
+ const [text, setText] = useState("") // always the decrypted value, editable
21
+ const [busy, setBusy] = useState(false)
22
+ const [status, setStatus] = useState("")
23
+ const [cryptoErr, setCryptoErr] = useState("")
24
+
25
+ // localStorage key for the saved note, namespaced per user (vuid).
26
+ const storageKey = () => `tide-note:${getValueFromIdToken("vuid")}`
27
+
28
+ useEffect(() => {
17
29
  if (token) {
18
30
  const name = getValueFromIdToken("preferred_username")
19
31
  const defaultRole = hasRealmRole(`default-roles-${tcConfig["realm"]}`)
20
32
  setUsername(name);
21
33
  setHasDefaultRole(defaultRole)
34
+
35
+ // Restore the saved note. Only the CIPHERTEXT is persisted; we decrypt it
36
+ // client-side here so the field shows plaintext when you log back in.
37
+ const stored = typeof window !== "undefined" ? localStorage.getItem(storageKey()) : null
38
+ if (stored) {
39
+ doDecrypt([{ encrypted: stored, tags: [TAG] }])
40
+ .then((res) => setText(String(res[0])))
41
+ .catch(() => {})
42
+ }
22
43
  }
23
44
 
24
45
  }, [token])
@@ -50,6 +71,25 @@ export default function HomePage() {
50
71
  }
51
72
  }, [token])
52
73
 
74
+ // Submit = encrypt the current value, persist the ciphertext, then decrypt it
75
+ // straight back so the field keeps showing plaintext. We store only the
76
+ // ciphertext (here in localStorage; in a real app, on your server) — it's
77
+ // decrypted again when you log back in.
78
+ const onSubmit = useCallback(async () => {
79
+ setBusy(true); setCryptoErr(""); setStatus("")
80
+ try {
81
+ const [ct] = await doEncrypt([{ data: text, tags: [TAG] }])
82
+ if (typeof window !== "undefined") localStorage.setItem(storageKey(), ct)
83
+ const [pt] = await doDecrypt([{ encrypted: ct, tags: [TAG] }])
84
+ setText(String(pt))
85
+ setStatus("Message successfully stored")
86
+ } catch (err: any) {
87
+ setCryptoErr(err.message || "Failed")
88
+ } finally {
89
+ setBusy(false)
90
+ }
91
+ }, [text, doEncrypt, doDecrypt])
92
+
53
93
  return (
54
94
  <div style={containerStyle}>
55
95
  <div style={cardStyle}>
@@ -75,11 +115,45 @@ export default function HomePage() {
75
115
  {verifyResult}
76
116
  </p>
77
117
  )}
118
+
119
+ {/* ── Encrypted note: always shown decrypted; Submit re-encrypts then decrypts ── */}
120
+ <div style={{ marginTop: '1.5rem', borderTop: '1px solid #eee', paddingTop: '1rem', textAlign: 'left' }}>
121
+ <h2 style={{ fontSize: '1.1rem', margin: '0 0 0.25rem' }}>Your encrypted note</h2>
122
+ <p style={{ margin: '0 0 0.5rem', color: '#777', fontSize: '0.85rem' }}>
123
+ This is an encrypted textbox under your own identity — only you can decrypt it.
124
+ </p>
125
+
126
+ <textarea
127
+ value={text}
128
+ onChange={(e) => setText(e.target.value)}
129
+ placeholder="Type your note…"
130
+ style={textareaStyle}
131
+ />
132
+ <button onClick={onSubmit} style={buttonStyle} disabled={busy}>
133
+ {busy ? 'Submitting…' : 'Submit'}
134
+ </button>
135
+
136
+ {status && <p style={{ color: 'green', marginTop: '0.5rem', fontSize: '0.85rem' }}>{status}</p>}
137
+
138
+ {cryptoErr && <p style={{ color: 'red', marginTop: '0.5rem' }}>{cryptoErr}</p>}
139
+ </div>
78
140
  </div>
79
141
  </div>
80
142
  )
81
143
  }
82
144
 
145
+ const textareaStyle: React.CSSProperties = {
146
+ width: '100%',
147
+ minHeight: '64px',
148
+ padding: '0.5rem',
149
+ borderRadius: '4px',
150
+ border: '1px solid #ccc',
151
+ boxSizing: 'border-box',
152
+ fontFamily: 'inherit',
153
+ fontSize: '0.9rem',
154
+ }
155
+
156
+
83
157
  const containerStyle: React.CSSProperties = {
84
158
  minHeight: '100vh',
85
159
  display: 'flex',
@@ -35,7 +35,7 @@ const buttonStyle: CSSProperties = {
35
35
  cursor: 'pointer',
36
36
  }
37
37
 
38
- export default function LoginPage(): JSX.Element {
38
+ export default function LoginPage() {
39
39
  const { login, authenticated } = useTideCloak()
40
40
  const router = useRouter()
41
41
 
@@ -12,12 +12,12 @@
12
12
  "description": "Standard application user"
13
13
  },
14
14
  {
15
- "name": "_tide_dob.selfencrypt",
16
- "description": "Tide E2EE self-encrypt DoB data"
15
+ "name": "_tide_message.selfencrypt",
16
+ "description": "Tide E2EE self-encrypt message data"
17
17
  },
18
18
  {
19
- "name": "_tide_dob.selfdecrypt",
20
- "description": "Tide E2EE self-decrypt DoB data"
19
+ "name": "_tide_message.selfdecrypt",
20
+ "description": "Tide E2EE self-decrypt message data"
21
21
  },
22
22
  {
23
23
  "name": "default-roles-nextjs-test",
@@ -25,8 +25,8 @@
25
25
  "composite": true,
26
26
  "composites": {
27
27
  "realm": [
28
- "_tide_dob.selfencrypt",
29
- "_tide_dob.selfdecrypt",
28
+ "_tide_message.selfencrypt",
29
+ "_tide_message.selfdecrypt",
30
30
  "appUser"
31
31
  ]
32
32
  }
@@ -135,7 +135,7 @@ echo "🔐 Initializing Tide realm + IGA..."
135
135
  curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/vendorResources/setUpTideRealm" \
136
136
  -H "Authorization: Bearer ${TOKEN}" \
137
137
  -H "Content-Type: application/x-www-form-urlencoded" \
138
- --data-urlencode "email=email@tide.org" >/dev/null
138
+ --data-urlencode "email=${SUBSCRIPTION_EMAIL:-test@demo.org}" >/dev/null
139
139
 
140
140
  curl -s -X POST "${TIDECLOAK_LOCAL_URL}/admin/realms/${REALM_NAME}/tide-admin/toggle-iga" \
141
141
  -H "Authorization: Bearer ${TOKEN}" \
@@ -1,44 +1,40 @@
1
- // an example nextJS middleware router that does server-side validation on all traffic to secure pages
2
- import type { NextRequest } from "next/server";
3
- import { NextResponse } from "next/server";
4
- import {
5
- createTideCloakMiddleware,
6
- type TideCloakContext,
7
- } from "@tidecloak/nextjs/server";
8
- import tcConfig from "./tidecloak.json";
9
-
10
- export default createTideCloakMiddleware({
11
- config: tcConfig,
12
- protectedRoutes: {
13
- // list each protected route and the roles allowed to access it
14
- "/protected": ["offline_access"],
15
- },
16
- onFailure: (ctx: TideCloakContext, req: NextRequest) => {
17
- console.debug("Token verification failed", {
18
- path: req.nextUrl.pathname,
19
- ctx,
20
- });
21
- return NextResponse.json(
22
- { error: "Access forbidden: invalid token" },
23
- { status: 403 }
24
- );
25
- },
26
- onSuccess: (ctx: TideCloakContext, req: NextRequest) => {
27
- return NextResponse.next();
28
- },
29
- onError: (
30
- ctx: TideCloakContext,
31
- req: NextRequest,
32
- err: unknown
33
- ) => {
34
- console.error("[Middleware] error verifying token for", req.nextUrl.pathname, err);
35
- // if something unexpected happens, redirect to your auth flow
36
- const redirectUrl = new URL("/auth/redirect", req.url);
37
- return NextResponse.redirect(redirectUrl);
38
- },
39
- });
40
-
41
- // Tell Next.js which paths to apply this middleware to
42
- export const config = {
43
- matcher: ["/protected/:path*"],
44
- };
1
+ // an example nextJS middleware router that does server-side validation on all traffic to secure pages
2
+ import type { NextRequest } from "next/server";
3
+ import { NextResponse } from "next/server";
4
+ import { createTideCloakMiddleware } from "@tidecloak/nextjs/server";
5
+ import tcConfig from "./tidecloak.json";
6
+
7
+ export default createTideCloakMiddleware({
8
+ config: tcConfig,
9
+ protectedRoutes: {
10
+ // "offline_access" is granted to every authenticated user, so this protects
11
+ // the route for "any logged-in user". Swap it for a real realm/client role
12
+ // (e.g. "appUser") to demonstrate role-based access control.
13
+ "/protected": ["offline_access"],
14
+ },
15
+ onFailure: (ctx: { token: string | null }, req: NextRequest) => {
16
+ console.debug("Token verification failed", {
17
+ path: req.nextUrl.pathname,
18
+ ctx,
19
+ });
20
+ return NextResponse.json(
21
+ { error: "Access forbidden: invalid token" },
22
+ { status: 403 }
23
+ );
24
+ },
25
+ onSuccess: (ctx: { payload: Record<string, any> }, req: NextRequest) => {
26
+ return NextResponse.next();
27
+ },
28
+ // Note: onError receives (err, req) - the error is the first argument.
29
+ onError: (err: unknown, req: NextRequest) => {
30
+ console.error("[Middleware] error verifying token for", req.nextUrl.pathname, err);
31
+ // if something unexpected happens, redirect to your auth flow
32
+ const redirectUrl = new URL("/auth/redirect", req.url);
33
+ return NextResponse.redirect(redirectUrl);
34
+ },
35
+ });
36
+
37
+ // Tell Next.js which paths to apply this middleware to (bare path and subpaths)
38
+ export const config = {
39
+ matcher: ["/protected", "/protected/:path*"],
40
+ };
@@ -0,0 +1,5 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+
4
+ }
5
+ module.exports = nextConfig
@@ -12,7 +12,7 @@
12
12
  "next": "16.x",
13
13
  "react": "19.x",
14
14
  "react-dom": "19.x",
15
- "@tidecloak/nextjs": "^0.13.30"
15
+ "@tidecloak/nextjs": "^0.13.31"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "24.0.13",