@close-by/clay 0.2.16 → 0.2.17

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.
@@ -220,6 +220,28 @@ Still redact before display when possible. Prefer VPN + auth for hubs that surfa
220
220
  Nav filtering is UX only — always enforce roles in the page builder.
221
221
 
222
222
  ```ts
223
+ import { ui } from '@close-by/clay';
224
+ import { getCurrentSession } from '@close-by/clay-core';
225
+ import { resolveUserIdFromAuthCookie } from '@close-by/clay-server';
226
+ import { createAuthGuards } from '@close-by/clay-auth';
227
+
228
+ type User = { role: 'admin' | 'member' };
229
+ const userById = new Map<string, User>(); // Your app's user store, keyed by user ID.
230
+ const authSecret = process.env.CLAY_AUTH_SECRET!;
231
+ const anonymousUserId = '__clay_anonymous__';
232
+ const resolveSignedUserId = resolveUserIdFromAuthCookie(authSecret);
233
+
234
+ function getSessionUser(): User | null {
235
+ const userId = getCurrentSession()?.userId;
236
+ if (!userId || userId === anonymousUserId) return null;
237
+ return userById.get(userId) ?? null;
238
+ }
239
+
240
+ const { requireRole } = createAuthGuards({
241
+ getUser: getSessionUser,
242
+ onUnauthenticated: () => ui.navigate('/login'),
243
+ });
244
+
223
245
  // pageMeta
224
246
  export const pageMeta = {
225
247
  label: 'Admin',
@@ -230,20 +252,26 @@ export const pageMeta = {
230
252
 
231
253
  // shell
232
254
  ui.run({
233
- authSecret: process.env.CLAY_AUTH_SECRET!,
255
+ authSecret,
256
+ resolveUserId: (context) => resolveSignedUserId(context) ?? anonymousUserId,
234
257
  app: {
235
258
  title: 'Hub',
236
- nav: () => ui.navFromPages({ role: currentUser.role }),
259
+ get nav() {
260
+ const user = getSessionUser();
261
+ return ui.navFromPages(user ? { role: user.role } : {});
262
+ },
237
263
  },
238
264
  });
239
265
 
240
266
  // page
241
267
  ui.page('/admin', () => {
242
- requireRole('admin'); // @close-by/clay-auth
268
+ if (!requireRole('admin')) return;
243
269
  // …
244
270
  });
245
271
  ```
246
272
 
273
+ `get nav()` is evaluated while each page shell is built, so it filters routes for the current session. Reserve `__clay_anonymous__` outside `userById`: the resolver substitutes it for missing or invalid cookies, and `getSessionUser` treats it as signed out. Replace `userById` with your app's user store.
274
+
247
275
  See `/examples/auth` and [API — auth](./api.md).
248
276
 
249
277
  ## `ui.ai.chat` and state
@@ -18,22 +18,47 @@ Clay ships hooks for real identity beyond anonymous `userId`:
18
18
 
19
19
  ```ts
20
20
  import { ui } from '@close-by/clay';
21
- import { requireRole } from '@close-by/clay-auth';
21
+ import { getCurrentSession } from '@close-by/clay-core';
22
+ import { resolveUserIdFromAuthCookie } from '@close-by/clay-server';
23
+ import { createAuthGuards } from '@close-by/clay-auth';
24
+
25
+ type User = { role: 'admin' | 'member' };
26
+ const userById = new Map<string, User>(); // Your app's user store, keyed by user ID.
27
+ const authSecret = process.env.CLAY_AUTH_SECRET!;
28
+ const anonymousUserId = '__clay_anonymous__';
29
+ const resolveSignedUserId = resolveUserIdFromAuthCookie(authSecret);
30
+
31
+ function getSessionUser(): User | null {
32
+ const userId = getCurrentSession()?.userId;
33
+ if (!userId || userId === anonymousUserId) return null;
34
+ return userById.get(userId) ?? null;
35
+ }
36
+
37
+ const { requireRole } = createAuthGuards({
38
+ getUser: getSessionUser,
39
+ onUnauthenticated: () => ui.navigate('/login'),
40
+ });
22
41
 
23
42
  ui.run({
24
- authSecret: process.env.CLAY_AUTH_SECRET!,
43
+ authSecret,
44
+ resolveUserId: (context) => resolveSignedUserId(context) ?? anonymousUserId,
25
45
  app: {
26
46
  title: 'Hub',
27
- nav: () => ui.navFromPages({ role: currentUser.role }),
47
+ get nav() {
48
+ const user = getSessionUser();
49
+ return ui.navFromPages(user ? { role: user.role } : {});
50
+ },
28
51
  },
29
52
  });
30
53
 
31
54
  ui.page('/admin', () => {
32
- requireRole('admin');
55
+ if (!requireRole('admin')) return;
33
56
  // …
34
57
  });
35
58
  ```
36
59
 
60
+ `get nav()` is evaluated while each page shell is built, so it filters routes for the current session. Reserve `__clay_anonymous__` outside `userById`: the resolver substitutes it for missing or invalid cookies, and `getSessionUser` treats it as signed out. Replace `userById` with your app's user store.
61
+
37
62
  - `pageMeta.roles` — hide nav items via `ui.navFromPages({ role })` (UX only)
38
63
  - `requireRole` / guards in the page builder — **enforce** access before building sensitive UI
39
64
  - See [API — auth](./api.md), `/examples/auth`, and [Ops patterns — auth nav](./ops-patterns.md#auth--role-filtered-nav)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@close-by/clay",
3
- "version": "0.2.16",
3
+ "version": "0.2.17",
4
4
  "description": "NiceGUI-like ui.* facade for Clay apps",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -37,10 +37,10 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@close-by/clay-compiler": "0.2.16",
41
- "@close-by/clay-core": "0.2.16",
42
- "@close-by/clay-components": "0.2.16",
43
- "@close-by/clay-server": "0.2.16",
40
+ "@close-by/clay-compiler": "0.2.17",
41
+ "@close-by/clay-core": "0.2.17",
42
+ "@close-by/clay-components": "0.2.17",
43
+ "@close-by/clay-server": "0.2.17",
44
44
  "@tailwindcss/cli": "^4.3.3",
45
45
  "tailwindcss": "^4.3.3"
46
46
  }