create-lego-one 2.0.12 → 2.0.13
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/index.cjs +34 -0
- package/dist/index.cjs.map +1 -1
- package/package.json +1 -1
- package/template/.cursor/rules/rules.mdc +639 -0
- package/template/.dockerignore +58 -0
- package/template/.env.example +18 -0
- package/template/.eslintignore +5 -0
- package/template/.eslintrc.js +28 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc +11 -0
- package/template/CLAUDE.md +634 -0
- package/template/Dockerfile +67 -0
- package/template/PROMPT.md +457 -0
- package/template/README.md +325 -0
- package/template/docker-compose.yml +48 -0
- package/template/docker-entrypoint.sh +23 -0
- package/template/docs/checkpoints/.template.md +64 -0
- package/template/docs/checkpoints/framework/01-infrastructure-setup.md +132 -0
- package/template/docs/checkpoints/framework/02-pocketbase-setup.md +155 -0
- package/template/docs/checkpoints/framework/03-host-kernel.md +170 -0
- package/template/docs/checkpoints/framework/04-auth-system.md +163 -0
- package/template/docs/checkpoints/framework/phase-05-multitenancy-rbac.md +223 -0
- package/template/docs/checkpoints/framework/phase-06-ui-components.md +260 -0
- package/template/docs/checkpoints/framework/phase-07-communication-system.md +276 -0
- package/template/docs/checkpoints/framework/phase-08-plugin-system.md +91 -0
- package/template/docs/checkpoints/framework/phase-09-dashboard-plugin.md +111 -0
- package/template/docs/checkpoints/framework/phase-10-todo-plugin.md +169 -0
- package/template/docs/checkpoints/framework/phase-11-testing.md +264 -0
- package/template/docs/checkpoints/framework/phase-12-deployment.md +294 -0
- package/template/docs/checkpoints/framework/phase-13-documentation.md +312 -0
- package/template/docs/framework/plans/00-index.md +164 -0
- package/template/docs/framework/plans/01-infrastructure-setup.md +855 -0
- package/template/docs/framework/plans/02-pocketbase-setup.md +1374 -0
- package/template/docs/framework/plans/03-host-kernel.md +1518 -0
- package/template/docs/framework/plans/04-auth-system.md +1466 -0
- package/template/docs/framework/plans/05-multitenancy-rbac.md +1527 -0
- package/template/docs/framework/plans/06-ui-components.md +1478 -0
- package/template/docs/framework/plans/07-communication-system.md +1106 -0
- package/template/docs/framework/plans/08-plugin-system.md +1179 -0
- package/template/docs/framework/plans/09-dashboard-plugin.md +1137 -0
- package/template/docs/framework/plans/10-todo-plugin.md +1343 -0
- package/template/docs/framework/plans/11-testing.md +935 -0
- package/template/docs/framework/plans/12-deployment.md +896 -0
- package/template/docs/framework/prompts/0-boilerplate-modernjs.md +151 -0
- package/template/docs/framework/research/00-modernjs-audit.md +488 -0
- package/template/docs/framework/research/01-system-blueprint.md +721 -0
- package/template/docs/framework/research/02-data-migration-protocol.md +699 -0
- package/template/docs/framework/research/03-host-setup.md +714 -0
- package/template/docs/framework/research/04-plugin-architecture.md +645 -0
- package/template/docs/framework/research/05-slot-injection-pattern.md +671 -0
- package/template/docs/framework/research/06-cli-strategy.md +615 -0
- package/template/docs/framework/research/07-deployment.md +629 -0
- package/template/docs/framework/research/README.md +282 -0
- package/template/docs/framework/setup/00-index.md +210 -0
- package/template/docs/framework/setup/01-framework-structure.md +308 -0
- package/template/docs/framework/setup/02-development-workflow.md +405 -0
- package/template/docs/framework/setup/03-environment-setup.md +215 -0
- package/template/docs/framework/setup/04-kernel-architecture.md +499 -0
- package/template/docs/framework/setup/05-plugin-system.md +620 -0
- package/template/docs/framework/setup/06-communication-patterns.md +451 -0
- package/template/docs/framework/setup/07-plugin-development.md +582 -0
- package/template/docs/framework/setup/08-component-library.md +658 -0
- package/template/docs/framework/setup/09-data-integration.md +609 -0
- package/template/docs/framework/setup/10-auth-rbac.md +497 -0
- package/template/docs/framework/setup/11-hooks-api.md +393 -0
- package/template/docs/framework/setup/12-components-api.md +665 -0
- package/template/docs/framework/setup/13-deployment-guide.md +566 -0
- package/template/docs/framework/setup/README.md +548 -0
- package/template/host/package.json +1 -1
- package/template/nginx.conf +72 -0
- package/template/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-dashboard/package.json +1 -1
- package/template/packages/plugins/@lego/plugin-todo/package.json +1 -1
- package/template/pocketbase/CHANGELOG.md +911 -0
- package/template/pocketbase/LICENSE.md +17 -0
- package/template/scripts/create-plugin.js +221 -0
- package/template/scripts/deploy.sh +56 -0
- package/template/tsconfig.base.json +26 -0
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# Authentication & RBAC
|
|
2
|
+
|
|
3
|
+
**Authentication, Authorization, and Permission Management**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
Lego-One provides complete authentication via PocketBase and Role-Based Access Control (RBAC) with organizations, roles, and permissions. All features are multi-tenant aware.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Authentication System
|
|
14
|
+
|
|
15
|
+
### PocketBase Auth
|
|
16
|
+
|
|
17
|
+
**Collection:** `users`
|
|
18
|
+
|
|
19
|
+
**Auth Methods:**
|
|
20
|
+
- Email/password (via PocketBase)
|
|
21
|
+
- Token persistence (localStorage)
|
|
22
|
+
- Auto-refresh on app load
|
|
23
|
+
|
|
24
|
+
### Auth Hooks
|
|
25
|
+
|
|
26
|
+
#### useAuth()
|
|
27
|
+
|
|
28
|
+
**Import:** `import { useAuth } from '@lego/kernel/auth'`
|
|
29
|
+
|
|
30
|
+
**Returns:**
|
|
31
|
+
```typescript
|
|
32
|
+
{
|
|
33
|
+
user: User | null; // Current user
|
|
34
|
+
token: string | null; // Auth token
|
|
35
|
+
isAuthenticated: boolean; // Auth status
|
|
36
|
+
isLoading: boolean; // Loading state
|
|
37
|
+
login: (email, password) => Promise<void>;
|
|
38
|
+
register: (data) => Promise<void>;
|
|
39
|
+
logout: () => void;
|
|
40
|
+
updateProfile: (data) => Promise<void>;
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Usage:**
|
|
45
|
+
```typescript
|
|
46
|
+
function MyComponent() {
|
|
47
|
+
const { user, isAuthenticated, logout } = useAuth();
|
|
48
|
+
|
|
49
|
+
if (!isAuthenticated) {
|
|
50
|
+
return <div>Please log in</div>;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div>
|
|
55
|
+
Welcome, {user?.name}
|
|
56
|
+
<button onClick={logout}>Logout</button>
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
#### useRequireAuth()
|
|
65
|
+
|
|
66
|
+
**Purpose:** Automatically redirects to sign-in if not authenticated
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
function ProtectedPage() {
|
|
70
|
+
useRequireAuth(); // Redirects to /sign-in if not authenticated
|
|
71
|
+
|
|
72
|
+
return <div>Protected content</div>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
### Protected Routes
|
|
79
|
+
|
|
80
|
+
**In Host (modern.runtime.ts):**
|
|
81
|
+
Routes with `protected: true` require authentication.
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
routes: [
|
|
85
|
+
{
|
|
86
|
+
path: '/dashboard',
|
|
87
|
+
component: () => import('./pages/DashboardPage'),
|
|
88
|
+
protected: true, // Requires auth
|
|
89
|
+
},
|
|
90
|
+
]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**In Plugins:**
|
|
94
|
+
Plugins declare route protection in `plugin.config.ts`:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
routes: [
|
|
98
|
+
{
|
|
99
|
+
path: '/my-plugin',
|
|
100
|
+
component: () => import('./pages/MyPluginPage'),
|
|
101
|
+
protected: true, // Host will enforce this
|
|
102
|
+
},
|
|
103
|
+
]
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## RBAC System
|
|
109
|
+
|
|
110
|
+
### System Roles
|
|
111
|
+
|
|
112
|
+
| Role | Description | Permissions |
|
|
113
|
+
|------|-------------|-------------|
|
|
114
|
+
| `Owner` | Organization owner | Full access (all:all) |
|
|
115
|
+
| `Admin` | Organization admin | Users, roles, todos, settings |
|
|
116
|
+
| `Member` | Regular member | Todos, read-only access |
|
|
117
|
+
| `Guest` | Limited access | Read-only todos |
|
|
118
|
+
|
|
119
|
+
### Permission Format
|
|
120
|
+
|
|
121
|
+
**Resource:** What you're accessing (e.g., 'todos', 'users', 'settings')
|
|
122
|
+
|
|
123
|
+
**Action:** What you're doing (e.g., 'read', 'write', 'delete', 'manage')
|
|
124
|
+
|
|
125
|
+
**Examples:**
|
|
126
|
+
- `todos.read` - Can view todos
|
|
127
|
+
- `todos.write` - Can create/edit todos
|
|
128
|
+
- `todos.delete` - Can delete todos
|
|
129
|
+
- `users.manage` - Full user management
|
|
130
|
+
- `settings.read` - Can view settings
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
### RBAC Hooks
|
|
135
|
+
|
|
136
|
+
#### useCurrentOrganization()
|
|
137
|
+
|
|
138
|
+
**Purpose:** Get/set current organization context
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { useCurrentOrganization } from '@lego/kernel/rbac';
|
|
142
|
+
|
|
143
|
+
function OrgSelector() {
|
|
144
|
+
const { organization, setCurrentOrganization } = useCurrentOrganization();
|
|
145
|
+
|
|
146
|
+
return (
|
|
147
|
+
<select value={organization?.id} onChange={(e) => {
|
|
148
|
+
const org = organizations.find(o => o.id === e.target.value);
|
|
149
|
+
setCurrentOrganization(org);
|
|
150
|
+
}}>
|
|
151
|
+
{organizations.map(org => (
|
|
152
|
+
<option key={org.id} value={org.id}>{org.name}</option>
|
|
153
|
+
))}
|
|
154
|
+
</select>
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
#### useHasPermission()
|
|
162
|
+
|
|
163
|
+
**Purpose:** Check if user has specific permission
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { useHasPermission } from '@lego/kernel/rbac';
|
|
167
|
+
|
|
168
|
+
function DeleteButton({ todoId }: { todoId: string }) {
|
|
169
|
+
const checkPermission = useHasPermission();
|
|
170
|
+
const [hasPermission, setHasPermission] = useState(false);
|
|
171
|
+
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
checkPermission('todos', 'delete').then(setHasPermission);
|
|
174
|
+
}, [checkPermission]);
|
|
175
|
+
|
|
176
|
+
if (!hasPermission) return null;
|
|
177
|
+
|
|
178
|
+
return <button>Delete Todo</button>;
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
#### useRequirePermission()
|
|
185
|
+
|
|
186
|
+
**Purpose:** Check permission and return loading state
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { useRequirePermission } from '@lego/kernel/rbac';
|
|
190
|
+
|
|
191
|
+
function AdminPanel() {
|
|
192
|
+
const { hasPermission, isLoading } = useRequirePermission('users', 'write');
|
|
193
|
+
|
|
194
|
+
if (isLoading) return <div>Loading...</div>;
|
|
195
|
+
if (!hasPermission) return <div>Access denied</div>;
|
|
196
|
+
|
|
197
|
+
return <div>Admin content</div>;
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
#### useUserPermissions()
|
|
204
|
+
|
|
205
|
+
**Purpose:** Get all user permissions for current organization
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
import { useUserPermissions } from '@lego/kernel/rbac';
|
|
209
|
+
|
|
210
|
+
function PermissionDebug() {
|
|
211
|
+
const { data: permissions } = useUserPermissions();
|
|
212
|
+
|
|
213
|
+
return (
|
|
214
|
+
<div>
|
|
215
|
+
<h4>Your Permissions:</h4>
|
|
216
|
+
<ul>
|
|
217
|
+
{permissions?.map(p => (
|
|
218
|
+
<li key={`${p.resource}:${p.action}`}>
|
|
219
|
+
{p.resource}:{p.action}
|
|
220
|
+
</li>
|
|
221
|
+
))}
|
|
222
|
+
</ul>
|
|
223
|
+
</div>
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
#### useOrganizations()
|
|
231
|
+
|
|
232
|
+
**Purpose:** Get all user's organizations
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { useOrganizations } from '@lego/kernel/rbac';
|
|
236
|
+
|
|
237
|
+
function OrganizationSwitcher() {
|
|
238
|
+
const { data: organizations } = useOrganizations();
|
|
239
|
+
const { setCurrentOrganization } = useCurrentOrganization();
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<select onChange={(e) => {
|
|
243
|
+
const org = organizations?.find(o => o.id === e.target.value);
|
|
244
|
+
setCurrentOrganization(org);
|
|
245
|
+
}}>
|
|
246
|
+
{organizations?.map(org => (
|
|
247
|
+
<option key={org.id} value={org.id}>{org.name}</option>
|
|
248
|
+
))}
|
|
249
|
+
</select>
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
256
|
+
### Permission Gate Component
|
|
257
|
+
|
|
258
|
+
**Purpose:** Conditionally render based on permission
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
import { PermissionGate } from '@lego/kernel/rbac';
|
|
262
|
+
|
|
263
|
+
function MyComponent() {
|
|
264
|
+
return (
|
|
265
|
+
<PermissionGate resource="todos" action="write">
|
|
266
|
+
<Button>Create Todo</Button>
|
|
267
|
+
</PermissionGate>
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
**With Custom Fallback:**
|
|
273
|
+
```typescript
|
|
274
|
+
<PermissionGate
|
|
275
|
+
resource="todos"
|
|
276
|
+
action="delete"
|
|
277
|
+
fallback={<span className="text-muted-foreground">Insufficient permissions</span>}
|
|
278
|
+
>
|
|
279
|
+
<Button>Delete Todo</Button>
|
|
280
|
+
</PermissionGate>
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Organization Management
|
|
286
|
+
|
|
287
|
+
### Creating Organizations
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
import { useCreateOrganization } from '@lego/kernel/rbac';
|
|
291
|
+
|
|
292
|
+
function CreateOrganizationForm() {
|
|
293
|
+
const { mutate: createOrg, isPending } = useCreateOrganization();
|
|
294
|
+
|
|
295
|
+
const handleSubmit = (data: { name: string; slug: string }) => {
|
|
296
|
+
createOrg(data, {
|
|
297
|
+
onSuccess: (org) => {
|
|
298
|
+
console.log('Organization created:', org);
|
|
299
|
+
// Switch to new organization
|
|
300
|
+
setCurrentOrganization(org);
|
|
301
|
+
},
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return (
|
|
306
|
+
<form onSubmit={handleSubmit}>
|
|
307
|
+
<input name="name" placeholder="Organization Name" />
|
|
308
|
+
<input name="slug" placeholder="Slug" />
|
|
309
|
+
<button disabled={isPending}>
|
|
310
|
+
{isPending ? 'Creating...' : 'Create Organization'}
|
|
311
|
+
</button>
|
|
312
|
+
</form>
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Organization Selector Component
|
|
318
|
+
|
|
319
|
+
**Built-in component:** `OrganizationSelector`
|
|
320
|
+
|
|
321
|
+
**Import:** `import { OrganizationSelector } from '@lego/kernel/rbac'`
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
import { OrganizationSelector } from '@lego/kernel/rbac';
|
|
325
|
+
|
|
326
|
+
function Topbar() {
|
|
327
|
+
return (
|
|
328
|
+
<header>
|
|
329
|
+
<OrganizationSelector />
|
|
330
|
+
</header>
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
---
|
|
336
|
+
|
|
337
|
+
## User Management
|
|
338
|
+
|
|
339
|
+
### Getting Organization Users
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
import { useOrganizationUsers } from '@lego/kernel/rbac';
|
|
343
|
+
|
|
344
|
+
function UserList() {
|
|
345
|
+
const { data: users } = useOrganizationUsers(orgId);
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<table>
|
|
349
|
+
<thead>
|
|
350
|
+
<tr>
|
|
351
|
+
<th>Name</th>
|
|
352
|
+
<th>Email</th>
|
|
353
|
+
<th>Roles</th>
|
|
354
|
+
</tr>
|
|
355
|
+
</thead>
|
|
356
|
+
<tbody>
|
|
357
|
+
{users?.map(user => (
|
|
358
|
+
<tr key={user.id}>
|
|
359
|
+
<td>{user.name}</td>
|
|
360
|
+
<td>{user.email}</td>
|
|
361
|
+
<td>{user.roles.map(r => r.name).join(', ')}</td>
|
|
362
|
+
</tr>
|
|
363
|
+
))}
|
|
364
|
+
</tbody>
|
|
365
|
+
</table>
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
### Assigning Roles
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
import { useAssignRole } from '@lego/kernel/rbac';
|
|
374
|
+
|
|
375
|
+
function RoleSelector({ userId, organizationId }: Props) {
|
|
376
|
+
const { mutate: assignRole } = useAssignRole();
|
|
377
|
+
const { data: roles } = useOrganizationRoles(organizationId);
|
|
378
|
+
|
|
379
|
+
return (
|
|
380
|
+
<select onChange={(e) => {
|
|
381
|
+
assignRole({
|
|
382
|
+
userId,
|
|
383
|
+
roleId: e.target.value,
|
|
384
|
+
organizationId,
|
|
385
|
+
});
|
|
386
|
+
}}>
|
|
387
|
+
{roles?.map(role => (
|
|
388
|
+
<option key={role.id} value={role.id}>{role.name}</option>
|
|
389
|
+
))}
|
|
390
|
+
</select>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Plugin Permissions
|
|
398
|
+
|
|
399
|
+
### Declaring Required Permissions
|
|
400
|
+
|
|
401
|
+
In `plugin.config.ts`:
|
|
402
|
+
|
|
403
|
+
```typescript
|
|
404
|
+
export const pluginConfig: PluginConfig = {
|
|
405
|
+
manifest: {
|
|
406
|
+
name: '@lego/plugin-todo',
|
|
407
|
+
permissions: [
|
|
408
|
+
'todos.read', // Required to view todos
|
|
409
|
+
'todos.write', // Required to create/edit todos
|
|
410
|
+
'todos.delete', // Required to delete todos
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
// ...
|
|
414
|
+
};
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
### Checking Permissions in Plugins
|
|
418
|
+
|
|
419
|
+
```typescript
|
|
420
|
+
import { useRequirePermission } from '@lego/kernel/rbac';
|
|
421
|
+
|
|
422
|
+
function TodoPage() {
|
|
423
|
+
const { hasPermission: canRead } = useRequirePermission('todos', 'read');
|
|
424
|
+
const { hasPermission: canWrite } = useRequirePermission('todos', 'write');
|
|
425
|
+
const { hasPermission: canDelete } = useRequirePermission('todos', 'delete');
|
|
426
|
+
|
|
427
|
+
if (!canRead) {
|
|
428
|
+
return <div>You don't have permission to view todos</div>;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
return (
|
|
432
|
+
<div>
|
|
433
|
+
{canWrite && <Button>Create Todo</Button>}
|
|
434
|
+
{/* Todo list */}
|
|
435
|
+
{canDelete && <Button>Delete Selected</Button>}
|
|
436
|
+
</div>
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
---
|
|
442
|
+
|
|
443
|
+
## PocketBase API Rules
|
|
444
|
+
|
|
445
|
+
### Collection API Rules
|
|
446
|
+
|
|
447
|
+
**These rules enforce data isolation and permissions at the database level:**
|
|
448
|
+
|
|
449
|
+
```javascript
|
|
450
|
+
{
|
|
451
|
+
name: 'todos',
|
|
452
|
+
|
|
453
|
+
// List: Users can view todos if they're in the org
|
|
454
|
+
listRule: '@request.auth.id != "" && organizationId = @request.auth.membership.organizations',
|
|
455
|
+
|
|
456
|
+
// View: Same as list
|
|
457
|
+
viewRule: '@request.auth.id != "" && organizationId = @request.auth.memberships.organizations',
|
|
458
|
+
|
|
459
|
+
// Create: Users can create todos in their orgs
|
|
460
|
+
createRule: '@request.auth.id != "" && organizationId = @request.auth.memberships.organizations',
|
|
461
|
+
|
|
462
|
+
// Update: Only todo owner can update
|
|
463
|
+
updateRule: '@request.auth.id != "" && organizationId = @request.auth.memberships.organizations && ownerId = @request.auth.id',
|
|
464
|
+
|
|
465
|
+
// Delete: Only todo owner can delete
|
|
466
|
+
deleteRule: '@request.auth.id != "" && organizationId = @request.auth.memberships.organizations && ownerId = @request.auth.id',
|
|
467
|
+
}
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
## Best Practices
|
|
473
|
+
|
|
474
|
+
### Authentication
|
|
475
|
+
|
|
476
|
+
1. **Always check `isAuthenticated`** before showing protected content
|
|
477
|
+
2. **Use `useRequireAuth()`** for protected pages
|
|
478
|
+
3. **Handle loading states** during auth checks
|
|
479
|
+
4. **Clear sensitive data** on logout
|
|
480
|
+
|
|
481
|
+
### Authorization
|
|
482
|
+
|
|
483
|
+
1. **Scope all data to organization** - Always filter by `organizationId`
|
|
484
|
+
2. **Check permissions before actions** - Use `useHasPermission()` or `PermissionGate`
|
|
485
|
+
3. **Declare plugin permissions** - In `plugin.config.ts`
|
|
486
|
+
4. **Use system roles** - Don't create roles unless needed
|
|
487
|
+
|
|
488
|
+
### Multi-Tenancy
|
|
489
|
+
|
|
490
|
+
1. **Always get org from kernel state** - Don't hardcode org IDs
|
|
491
|
+
2. **Subscribe to org changes** - Refresh data when org changes
|
|
492
|
+
3. **Include org in all queries** - Filter by `organizationId`
|
|
493
|
+
4. **Set org on create** - Always set `organizationId` when creating records
|
|
494
|
+
|
|
495
|
+
---
|
|
496
|
+
|
|
497
|
+
**Next:** Read [`11-hooks-api.md`](./11-hooks-api.md) for complete hooks reference.
|