spaps-sdk 1.6.1 โ 1.6.3
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/CHANGELOG.md +87 -0
- package/LICENSE +21 -0
- package/PERMISSIONS.md +390 -0
- package/README.md +133 -349
- package/dist/index.d.mts +37 -3
- package/dist/index.d.ts +37 -3
- package/dist/index.js +73 -0
- package/dist/index.mjs +73 -0
- package/package.json +8 -4
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
- No changes yet.
|
|
10
|
+
|
|
11
|
+
## [1.6.2] - 2026-03-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added canonical issue-reporting client methods for end-user create, list, detail, status, update, and reply flows.
|
|
16
|
+
|
|
17
|
+
## [1.6.1] - 2026-02-16
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Made the SDK `tsconfig` self-contained after root-config removal.
|
|
22
|
+
- Restored API-key header injection behavior for axios v1+ interceptor semantics.
|
|
23
|
+
|
|
24
|
+
## [1.6.0] - 2026-02-14
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- CFO roles, `company_id`, and affiliate-referral domain support.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
|
|
32
|
+
- Consolidated SPAPS integration patterns into published SDK packages.
|
|
33
|
+
|
|
34
|
+
## [1.5.1] - 2026-02-11
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
|
|
38
|
+
- Standardized local/dev config defaults, including port `3301`.
|
|
39
|
+
|
|
40
|
+
## [1.5.0] - 2026-01-10
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- Set-password endpoint support for authenticated users.
|
|
45
|
+
|
|
46
|
+
## [1.4.0] - 2026-01-10
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- Email service integration with Mailgun support.
|
|
51
|
+
|
|
52
|
+
## [1.2.1] - 2025-12-31
|
|
53
|
+
|
|
54
|
+
### Added
|
|
55
|
+
|
|
56
|
+
- Stripe webhook admin tooling for entitlement operations.
|
|
57
|
+
- CFO auth integration helpers.
|
|
58
|
+
|
|
59
|
+
## [1.1.52] - 2025-10-15
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- Build and type-export stability across iterative CI packaging fixes.
|
|
64
|
+
|
|
65
|
+
## [1.1.0] - 2025-10-12
|
|
66
|
+
|
|
67
|
+
### Added
|
|
68
|
+
|
|
69
|
+
- Admin API methods for Stripe product/price management and sync flows.
|
|
70
|
+
- Built-in permission utilities: `isAdminAccount`, `canAccessAdmin`, `getUserRole`.
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
|
|
74
|
+
- Subsequent patch iterations through `1.1.52` focused on CI and type-export stabilization.
|
|
75
|
+
|
|
76
|
+
## [1.0.0] - 2025-10-08
|
|
77
|
+
|
|
78
|
+
### Added
|
|
79
|
+
|
|
80
|
+
- Initial npm release as `spaps-sdk`.
|
|
81
|
+
- Zero-config client for SPAPS auth, payments, and permission checks.
|
|
82
|
+
- Local-mode auto-detection (`localhost`/`127.0.0.1`).
|
|
83
|
+
- Email/password, wallet, and magic-link authentication flows.
|
|
84
|
+
- Stripe checkout helpers with legal-consent support.
|
|
85
|
+
- Usage tracking and secure messaging helpers.
|
|
86
|
+
- TypeScript definitions with CJS/ESM dual exports.
|
|
87
|
+
- Node/browser/Next.js/React Native compatibility.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Sweet Potato Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/PERMISSIONS.md
ADDED
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# ๐ SPAPS SDK Permission Utilities
|
|
2
|
+
|
|
3
|
+
Client-side permission checking and role management utilities for SPAPS applications.
|
|
4
|
+
|
|
5
|
+
## ๐ Quick Start
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import {
|
|
9
|
+
isAdminAccount,
|
|
10
|
+
getUserRole,
|
|
11
|
+
hasPermission,
|
|
12
|
+
canAccessAdmin,
|
|
13
|
+
PermissionChecker,
|
|
14
|
+
createPermissionChecker
|
|
15
|
+
} from 'spaps-sdk';
|
|
16
|
+
|
|
17
|
+
// Check if user is admin
|
|
18
|
+
const isAdmin = isAdminAccount('buildooor@gmail.com'); // true
|
|
19
|
+
|
|
20
|
+
// Get user role
|
|
21
|
+
const role = getUserRole('user@example.com'); // 'user' | 'admin' | 'guest'
|
|
22
|
+
|
|
23
|
+
// Check admin access
|
|
24
|
+
const adminCheck = canAccessAdmin(user);
|
|
25
|
+
if (adminCheck.allowed) {
|
|
26
|
+
// Show admin features
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check specific permissions
|
|
30
|
+
const canEdit = hasPermission(user, 'edit');
|
|
31
|
+
const canManage = hasPermission(user, ['create', 'delete']);
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ๐ Default Admin Accounts
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
const DEFAULT_ADMIN_ACCOUNTS = {
|
|
38
|
+
email: 'buildooor@gmail.com',
|
|
39
|
+
wallets: {
|
|
40
|
+
ethereum: '0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba',
|
|
41
|
+
solana: 'HVEbdiYU3Rr34NHBSgKs7q8cvdTeZLqNL77Z1FB2vjLy'
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## ๐ฏ React Component Examples
|
|
47
|
+
|
|
48
|
+
### Admin Button with Permission Checking
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import React from 'react';
|
|
52
|
+
import { canAccessAdmin, getUserDisplay } from 'spaps-sdk';
|
|
53
|
+
|
|
54
|
+
interface AdminButtonProps {
|
|
55
|
+
user: User | null;
|
|
56
|
+
onClick: () => void;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function AdminButton({ user, onClick }: AdminButtonProps) {
|
|
60
|
+
const permissionCheck = canAccessAdmin(user);
|
|
61
|
+
const userDisplay = getUserDisplay(user);
|
|
62
|
+
|
|
63
|
+
if (!permissionCheck.allowed) {
|
|
64
|
+
return (
|
|
65
|
+
<button
|
|
66
|
+
disabled
|
|
67
|
+
title={permissionCheck.reason}
|
|
68
|
+
className="btn-disabled"
|
|
69
|
+
>
|
|
70
|
+
Admin Panel
|
|
71
|
+
</button>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<button onClick={onClick} className="btn-primary">
|
|
77
|
+
{userDisplay.badge} Admin Panel
|
|
78
|
+
</button>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Protected Route Component
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import React from 'react';
|
|
87
|
+
import { canAccessAdmin, getRoleAwareErrorMessage } from 'spaps-sdk';
|
|
88
|
+
|
|
89
|
+
interface ProtectedRouteProps {
|
|
90
|
+
user: User | null;
|
|
91
|
+
children: React.ReactNode;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function AdminRoute({ user, children }: ProtectedRouteProps) {
|
|
95
|
+
const permissionCheck = canAccessAdmin(user);
|
|
96
|
+
|
|
97
|
+
if (!permissionCheck.allowed) {
|
|
98
|
+
const errorMessage = getRoleAwareErrorMessage(
|
|
99
|
+
'admin',
|
|
100
|
+
permissionCheck.userRole,
|
|
101
|
+
'access this page'
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className="error-container">
|
|
106
|
+
<h2>Access Denied</h2>
|
|
107
|
+
<p>{errorMessage}</p>
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return <>{children}</>;
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### User Display with Role Badge
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
import React from 'react';
|
|
120
|
+
import { getUserDisplay } from 'spaps-sdk';
|
|
121
|
+
|
|
122
|
+
export function UserProfile({ user }: { user: User | null }) {
|
|
123
|
+
const display = getUserDisplay(user);
|
|
124
|
+
|
|
125
|
+
return (
|
|
126
|
+
<div className="user-profile">
|
|
127
|
+
<span>{display.displayName}</span>
|
|
128
|
+
{display.badge && (
|
|
129
|
+
<span className="role-badge">{display.badge}</span>
|
|
130
|
+
)}
|
|
131
|
+
<span className="role-text">({display.role})</span>
|
|
132
|
+
</div>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## ๐ ๏ธ Advanced Usage
|
|
138
|
+
|
|
139
|
+
### Custom Permission Checker
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { createPermissionChecker } from 'spaps-sdk';
|
|
143
|
+
|
|
144
|
+
// Create checker with custom admin accounts
|
|
145
|
+
const customAdmins = [
|
|
146
|
+
'admin@mycompany.com',
|
|
147
|
+
'0x1234567890abcdef...',
|
|
148
|
+
{
|
|
149
|
+
email: 'ceo@company.com',
|
|
150
|
+
wallets: {
|
|
151
|
+
ethereum: '0xabcdef...',
|
|
152
|
+
solana: 'ABC123...'
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
const checker = createPermissionChecker(customAdmins);
|
|
158
|
+
|
|
159
|
+
// Use the checker
|
|
160
|
+
const isAdmin = checker.isAdmin('admin@mycompany.com'); // true
|
|
161
|
+
const userDisplay = checker.getUserDisplay(user);
|
|
162
|
+
const canAccess = checker.canAccessAdmin(user);
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Permission-Based UI Rendering
|
|
166
|
+
|
|
167
|
+
```tsx
|
|
168
|
+
import React from 'react';
|
|
169
|
+
import { hasPermission, PermissionChecker } from 'spaps-sdk';
|
|
170
|
+
|
|
171
|
+
const checker = new PermissionChecker();
|
|
172
|
+
|
|
173
|
+
function Dashboard({ user }: { user: User | null }) {
|
|
174
|
+
return (
|
|
175
|
+
<div>
|
|
176
|
+
<h1>Dashboard</h1>
|
|
177
|
+
|
|
178
|
+
{/* Show admin panel for admins only */}
|
|
179
|
+
{!checker.requiresAdmin(user) && (
|
|
180
|
+
<AdminPanel />
|
|
181
|
+
)}
|
|
182
|
+
|
|
183
|
+
{/* Show billing section for users with billing permission */}
|
|
184
|
+
{hasPermission(user, 'billing') && (
|
|
185
|
+
<BillingSection />
|
|
186
|
+
)}
|
|
187
|
+
|
|
188
|
+
{/* Show different content based on role */}
|
|
189
|
+
{checker.getRole(user?.email) === 'admin' ? (
|
|
190
|
+
<AdminDashboard />
|
|
191
|
+
) : (
|
|
192
|
+
<UserDashboard />
|
|
193
|
+
)}
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Form with Permission Validation
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import React, { useState } from 'react';
|
|
203
|
+
import { hasPermission, getRoleAwareErrorMessage } from 'spaps-sdk';
|
|
204
|
+
|
|
205
|
+
function ProductForm({ user }: { user: User | null }) {
|
|
206
|
+
const [product, setProduct] = useState({ name: '', price: '' });
|
|
207
|
+
const canCreate = hasPermission(user, 'create');
|
|
208
|
+
|
|
209
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
210
|
+
e.preventDefault();
|
|
211
|
+
|
|
212
|
+
if (!canCreate) {
|
|
213
|
+
const errorMessage = getRoleAwareErrorMessage(
|
|
214
|
+
'permission',
|
|
215
|
+
user ? 'user' : 'guest',
|
|
216
|
+
'create products'
|
|
217
|
+
);
|
|
218
|
+
alert(errorMessage);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Submit product
|
|
223
|
+
createProduct(product);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return (
|
|
227
|
+
<form onSubmit={handleSubmit}>
|
|
228
|
+
<input
|
|
229
|
+
value={product.name}
|
|
230
|
+
onChange={(e) => setProduct({ ...product, name: e.target.value })}
|
|
231
|
+
placeholder="Product name"
|
|
232
|
+
/>
|
|
233
|
+
<input
|
|
234
|
+
value={product.price}
|
|
235
|
+
onChange={(e) => setProduct({ ...product, price: e.target.value })}
|
|
236
|
+
placeholder="Price"
|
|
237
|
+
/>
|
|
238
|
+
<button
|
|
239
|
+
type="submit"
|
|
240
|
+
disabled={!canCreate}
|
|
241
|
+
title={!canCreate ? 'Insufficient permissions' : undefined}
|
|
242
|
+
>
|
|
243
|
+
{canCreate ? 'Create Product' : 'Create Product (No Permission)'}
|
|
244
|
+
</button>
|
|
245
|
+
</form>
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## ๐งช Testing Examples
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
import {
|
|
254
|
+
isAdminAccount,
|
|
255
|
+
getUserRole,
|
|
256
|
+
canAccessAdmin,
|
|
257
|
+
createPermissionChecker
|
|
258
|
+
} from 'spaps-sdk';
|
|
259
|
+
|
|
260
|
+
describe('Permission Utilities', () => {
|
|
261
|
+
describe('isAdminAccount', () => {
|
|
262
|
+
it('should recognize default admin accounts', () => {
|
|
263
|
+
expect(isAdminAccount('buildooor@gmail.com')).toBe(true);
|
|
264
|
+
expect(isAdminAccount('0xa72bb7CeF1e4B2Cc144373d8dE0Add7CCc8DF4Ba')).toBe(true);
|
|
265
|
+
expect(isAdminAccount('user@example.com')).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should recognize custom admin accounts', () => {
|
|
269
|
+
const customAdmins = ['admin@test.com'];
|
|
270
|
+
expect(isAdminAccount('admin@test.com', customAdmins)).toBe(true);
|
|
271
|
+
expect(isAdminAccount('user@test.com', customAdmins)).toBe(false);
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe('getUserRole', () => {
|
|
276
|
+
it('should return correct roles', () => {
|
|
277
|
+
expect(getUserRole('buildooor@gmail.com')).toBe('admin');
|
|
278
|
+
expect(getUserRole('user@example.com')).toBe('user');
|
|
279
|
+
expect(getUserRole()).toBe('guest');
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
describe('canAccessAdmin', () => {
|
|
284
|
+
it('should check admin access correctly', () => {
|
|
285
|
+
const adminUser = { id: '1', email: 'buildooor@gmail.com' };
|
|
286
|
+
const regularUser = { id: '2', email: 'user@example.com' };
|
|
287
|
+
|
|
288
|
+
expect(canAccessAdmin(adminUser).allowed).toBe(true);
|
|
289
|
+
expect(canAccessAdmin(regularUser).allowed).toBe(false);
|
|
290
|
+
expect(canAccessAdmin(null).allowed).toBe(false);
|
|
291
|
+
});
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
describe('PermissionChecker', () => {
|
|
295
|
+
it('should work with custom admins', () => {
|
|
296
|
+
const checker = createPermissionChecker(['admin@test.com']);
|
|
297
|
+
|
|
298
|
+
expect(checker.isAdmin('admin@test.com')).toBe(true);
|
|
299
|
+
expect(checker.getRole('admin@test.com')).toBe('admin');
|
|
300
|
+
expect(checker.canAccessAdmin({
|
|
301
|
+
id: '1',
|
|
302
|
+
email: 'admin@test.com'
|
|
303
|
+
}).allowed).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## ๐ API Reference
|
|
310
|
+
|
|
311
|
+
### Core Functions
|
|
312
|
+
|
|
313
|
+
#### `isAdminAccount(identifier: string, customAdmins?: AdminConfig[]): boolean`
|
|
314
|
+
Check if an identifier (email/wallet) is an admin account.
|
|
315
|
+
|
|
316
|
+
#### `getUserRole(identifier?: string, customAdmins?: AdminConfig[]): string`
|
|
317
|
+
Get user role: 'admin', 'user', or 'guest'.
|
|
318
|
+
|
|
319
|
+
#### `hasPermission(user: User | null, permissions: string | string[], customAdmins?: AdminConfig[]): boolean`
|
|
320
|
+
Check if user has specific permissions.
|
|
321
|
+
|
|
322
|
+
#### `canAccessAdmin(user: User | null, customAdmins?: AdminConfig[]): PermissionCheckResult`
|
|
323
|
+
Check if user can access admin features.
|
|
324
|
+
|
|
325
|
+
#### `getRoleAwareErrorMessage(requiredRole: string, userRole: string, action?: string): string`
|
|
326
|
+
Get contextual error message based on roles.
|
|
327
|
+
|
|
328
|
+
#### `getUserDisplay(user: User | null, customAdmins?: AdminConfig[])`
|
|
329
|
+
Get user display information with role indicators.
|
|
330
|
+
|
|
331
|
+
### PermissionChecker Class
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
class PermissionChecker {
|
|
335
|
+
constructor(customAdmins?: AdminConfig[]);
|
|
336
|
+
|
|
337
|
+
isAdmin(identifier: string): boolean;
|
|
338
|
+
getRole(identifier?: string): string;
|
|
339
|
+
hasPermission(user: User | null, permissions: string | string[]): boolean;
|
|
340
|
+
canAccessAdmin(user: User | null): PermissionCheckResult;
|
|
341
|
+
getErrorMessage(requiredRole: string, userRole: string, action?: string): string;
|
|
342
|
+
getUserDisplay(user: User | null);
|
|
343
|
+
|
|
344
|
+
// Convenience methods
|
|
345
|
+
requiresAuth(user: User | null): boolean;
|
|
346
|
+
requiresAdmin(user: User | null): boolean;
|
|
347
|
+
|
|
348
|
+
// Admin management
|
|
349
|
+
addCustomAdmin(admin: string | AdminConfig): void;
|
|
350
|
+
removeCustomAdmin(admin: string | AdminConfig): void;
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Interfaces
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
interface User {
|
|
358
|
+
id: string;
|
|
359
|
+
email?: string;
|
|
360
|
+
wallet_address?: string;
|
|
361
|
+
role?: string;
|
|
362
|
+
permissions?: string[];
|
|
363
|
+
tier?: string;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
interface AdminConfig {
|
|
367
|
+
email: string;
|
|
368
|
+
wallets: {
|
|
369
|
+
ethereum: string;
|
|
370
|
+
solana: string;
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
interface PermissionCheckResult {
|
|
375
|
+
allowed: boolean;
|
|
376
|
+
reason?: string;
|
|
377
|
+
userRole: string;
|
|
378
|
+
requiredRole?: string;
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## ๐ Best Practices
|
|
383
|
+
|
|
384
|
+
1. **Server-side validation**: Always validate permissions on the server
|
|
385
|
+
2. **Progressive enhancement**: Use client-side checks for UX, not security
|
|
386
|
+
3. **Consistent error messages**: Use `getRoleAwareErrorMessage` for user-friendly errors
|
|
387
|
+
4. **Role-based UI**: Show/hide features based on user permissions
|
|
388
|
+
5. **Custom admin management**: Use custom admin lists for multi-tenant apps
|
|
389
|
+
6. **Testing**: Test permission logic with various user states
|
|
390
|
+
7. **Type safety**: Use TypeScript interfaces for better development experience
|