hazo_auth 1.0.0 → 1.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/README.md +628 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -29,6 +29,632 @@ After installing the package, you need to set up configuration files in your pro
|
|
|
29
29
|
|
|
30
30
|
**Important:** The configuration files must be located in your project root directory (where `process.cwd()` points to), not inside `node_modules`. The package reads configuration from `process.cwd()` at runtime.
|
|
31
31
|
|
|
32
|
+
## Authentication Service
|
|
33
|
+
|
|
34
|
+
The `hazo_auth` package provides a comprehensive authentication and authorization system with role-based access control (RBAC). The main authentication utility is `hazo_get_auth`, which provides user details, permissions, and permission checking with built-in caching and rate limiting.
|
|
35
|
+
|
|
36
|
+
### Server-Side Functions
|
|
37
|
+
|
|
38
|
+
#### `hazo_get_auth` (Recommended)
|
|
39
|
+
|
|
40
|
+
The primary authentication utility for server-side use in API routes. Returns user details, permissions, and optionally checks required permissions.
|
|
41
|
+
|
|
42
|
+
**Location:** `src/lib/auth/hazo_get_auth.server.ts`
|
|
43
|
+
|
|
44
|
+
**Function Signature:**
|
|
45
|
+
```typescript
|
|
46
|
+
import { hazo_get_auth } from "hazo_auth/lib/auth/hazo_get_auth.server";
|
|
47
|
+
import type { HazoAuthResult, HazoAuthOptions } from "hazo_auth/lib/auth/auth_types";
|
|
48
|
+
|
|
49
|
+
async function hazo_get_auth(
|
|
50
|
+
request: NextRequest,
|
|
51
|
+
options?: HazoAuthOptions
|
|
52
|
+
): Promise<HazoAuthResult>
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Options:**
|
|
56
|
+
- `required_permissions?: string[]` - Array of permission names to check
|
|
57
|
+
- `strict?: boolean` - If `true`, throws `PermissionError` when permissions are missing (default: `false`)
|
|
58
|
+
|
|
59
|
+
**Return Type:**
|
|
60
|
+
```typescript
|
|
61
|
+
type HazoAuthResult =
|
|
62
|
+
| {
|
|
63
|
+
authenticated: true;
|
|
64
|
+
user: {
|
|
65
|
+
id: string;
|
|
66
|
+
name: string | null;
|
|
67
|
+
email_address: string;
|
|
68
|
+
is_active: boolean;
|
|
69
|
+
profile_picture_url: string | null;
|
|
70
|
+
};
|
|
71
|
+
permissions: string[];
|
|
72
|
+
permission_ok: boolean;
|
|
73
|
+
missing_permissions?: string[];
|
|
74
|
+
}
|
|
75
|
+
| {
|
|
76
|
+
authenticated: false;
|
|
77
|
+
user: null;
|
|
78
|
+
permissions: [];
|
|
79
|
+
permission_ok: false;
|
|
80
|
+
};
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Features:**
|
|
84
|
+
- **Caching:** LRU cache with TTL (configurable, default: 15 minutes)
|
|
85
|
+
- **Rate Limiting:** Per-user and per-IP rate limiting
|
|
86
|
+
- **Permission Checking:** Optional permission validation with detailed error messages
|
|
87
|
+
- **Audit Logging:** Logs permission denials for security auditing
|
|
88
|
+
- **Performance:** Optimized database queries with single JOIN
|
|
89
|
+
|
|
90
|
+
**Example Usage:**
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
// In an API route (src/app/api/protected/route.ts)
|
|
94
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
95
|
+
import { hazo_get_auth } from "hazo_auth/lib/auth/hazo_get_auth.server";
|
|
96
|
+
import { PermissionError } from "hazo_auth/lib/auth/auth_types";
|
|
97
|
+
|
|
98
|
+
export async function GET(request: NextRequest) {
|
|
99
|
+
try {
|
|
100
|
+
// Check authentication and permissions
|
|
101
|
+
const authResult = await hazo_get_auth(request, {
|
|
102
|
+
required_permissions: ["admin_user_management"],
|
|
103
|
+
strict: true, // Throws PermissionError if missing permissions
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (!authResult.authenticated) {
|
|
107
|
+
return NextResponse.json(
|
|
108
|
+
{ error: "Authentication required" },
|
|
109
|
+
{ status: 401 }
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// User is authenticated and has required permissions
|
|
114
|
+
return NextResponse.json({
|
|
115
|
+
message: "Access granted",
|
|
116
|
+
user: authResult.user,
|
|
117
|
+
permissions: authResult.permissions,
|
|
118
|
+
});
|
|
119
|
+
} catch (error) {
|
|
120
|
+
if (error instanceof PermissionError) {
|
|
121
|
+
return NextResponse.json(
|
|
122
|
+
{
|
|
123
|
+
error: "Permission denied",
|
|
124
|
+
missing_permissions: error.missing_permissions,
|
|
125
|
+
user_friendly_message: error.user_friendly_message,
|
|
126
|
+
},
|
|
127
|
+
{ status: 403 }
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
throw error;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**Non-strict mode (returns permission status without throwing):**
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
// Check permissions without throwing errors
|
|
139
|
+
const authResult = await hazo_get_auth(request, {
|
|
140
|
+
required_permissions: ["admin_user_management"],
|
|
141
|
+
strict: false, // Returns permission_ok: false if missing
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (authResult.authenticated && authResult.permission_ok) {
|
|
145
|
+
// User has required permissions
|
|
146
|
+
} else if (authResult.authenticated) {
|
|
147
|
+
// User is authenticated but missing permissions
|
|
148
|
+
console.log("Missing permissions:", authResult.missing_permissions);
|
|
149
|
+
} else {
|
|
150
|
+
// User is not authenticated
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
#### `get_authenticated_user`
|
|
155
|
+
|
|
156
|
+
Basic authentication check for API routes. Returns user info if authenticated, or `{ authenticated: false }` if not.
|
|
157
|
+
|
|
158
|
+
**Location:** `src/lib/auth/auth_utils.server.ts`
|
|
159
|
+
|
|
160
|
+
**Function Signature:**
|
|
161
|
+
```typescript
|
|
162
|
+
import { get_authenticated_user } from "hazo_auth/lib/auth/auth_utils.server";
|
|
163
|
+
import type { AuthResult } from "hazo_auth/lib/auth/auth_utils.server";
|
|
164
|
+
|
|
165
|
+
async function get_authenticated_user(request: NextRequest): Promise<AuthResult>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
**Example Usage:**
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
172
|
+
import { get_authenticated_user } from "hazo_auth/lib/auth/auth_utils.server";
|
|
173
|
+
|
|
174
|
+
export async function GET(request: NextRequest) {
|
|
175
|
+
const authResult = await get_authenticated_user(request);
|
|
176
|
+
|
|
177
|
+
if (!authResult.authenticated) {
|
|
178
|
+
return NextResponse.json(
|
|
179
|
+
{ error: "Authentication required" },
|
|
180
|
+
{ status: 401 }
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return NextResponse.json({
|
|
185
|
+
user_id: authResult.user_id,
|
|
186
|
+
email: authResult.email,
|
|
187
|
+
name: authResult.name,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `require_auth`
|
|
193
|
+
|
|
194
|
+
Requires authentication and throws an error if the user is not authenticated. Useful for protected API routes.
|
|
195
|
+
|
|
196
|
+
**Location:** `src/lib/auth/auth_utils.server.ts`
|
|
197
|
+
|
|
198
|
+
**Function Signature:**
|
|
199
|
+
```typescript
|
|
200
|
+
import { require_auth } from "hazo_auth/lib/auth/auth_utils.server";
|
|
201
|
+
import type { AuthUser } from "hazo_auth/lib/auth/auth_utils.server";
|
|
202
|
+
|
|
203
|
+
async function require_auth(request: NextRequest): Promise<AuthUser>
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**Example Usage:**
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
210
|
+
import { require_auth } from "hazo_auth/lib/auth/auth_utils.server";
|
|
211
|
+
|
|
212
|
+
export async function GET(request: NextRequest) {
|
|
213
|
+
try {
|
|
214
|
+
const user = await require_auth(request);
|
|
215
|
+
// User is guaranteed to be authenticated here
|
|
216
|
+
return NextResponse.json({ user_id: user.user_id, email: user.email });
|
|
217
|
+
} catch (error) {
|
|
218
|
+
return NextResponse.json(
|
|
219
|
+
{ error: "Authentication required" },
|
|
220
|
+
{ status: 401 }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### `is_authenticated`
|
|
227
|
+
|
|
228
|
+
Simple boolean check for authentication status.
|
|
229
|
+
|
|
230
|
+
**Location:** `src/lib/auth/auth_utils.server.ts`
|
|
231
|
+
|
|
232
|
+
**Function Signature:**
|
|
233
|
+
```typescript
|
|
234
|
+
import { is_authenticated } from "hazo_auth/lib/auth/auth_utils.server";
|
|
235
|
+
|
|
236
|
+
async function is_authenticated(request: NextRequest): Promise<boolean>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Example Usage:**
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
243
|
+
import { is_authenticated } from "hazo_auth/lib/auth/auth_utils.server";
|
|
244
|
+
|
|
245
|
+
export async function GET(request: NextRequest) {
|
|
246
|
+
const authenticated = await is_authenticated(request);
|
|
247
|
+
|
|
248
|
+
if (!authenticated) {
|
|
249
|
+
return NextResponse.json(
|
|
250
|
+
{ error: "Authentication required" },
|
|
251
|
+
{ status: 401 }
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Continue with authenticated logic
|
|
256
|
+
}
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
#### `get_server_auth_user`
|
|
260
|
+
|
|
261
|
+
Gets authenticated user in server components and pages (uses Next.js `cookies()` function).
|
|
262
|
+
|
|
263
|
+
**Location:** `src/lib/auth/server_auth.ts`
|
|
264
|
+
|
|
265
|
+
**Function Signature:**
|
|
266
|
+
```typescript
|
|
267
|
+
import { get_server_auth_user } from "hazo_auth/lib/auth/server_auth";
|
|
268
|
+
import type { ServerAuthResult } from "hazo_auth/lib/auth/server_auth";
|
|
269
|
+
|
|
270
|
+
async function get_server_auth_user(): Promise<ServerAuthResult>
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Example Usage:**
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
// In a server component (src/app/dashboard/page.tsx)
|
|
277
|
+
import { get_server_auth_user } from "hazo_auth/lib/auth/server_auth";
|
|
278
|
+
|
|
279
|
+
export default async function DashboardPage() {
|
|
280
|
+
const authResult = await get_server_auth_user();
|
|
281
|
+
|
|
282
|
+
if (!authResult.authenticated) {
|
|
283
|
+
return <div>Please log in to access this page.</div>;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
return (
|
|
287
|
+
<div>
|
|
288
|
+
<h1>Welcome, {authResult.name || authResult.email}!</h1>
|
|
289
|
+
<p>User ID: {authResult.user_id}</p>
|
|
290
|
+
</div>
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Client-Side Hooks
|
|
296
|
+
|
|
297
|
+
#### `use_hazo_auth`
|
|
298
|
+
|
|
299
|
+
React hook for fetching authentication status and permissions on the client side.
|
|
300
|
+
|
|
301
|
+
**Location:** `src/components/layouts/shared/hooks/use_hazo_auth.ts`
|
|
302
|
+
|
|
303
|
+
**Function Signature:**
|
|
304
|
+
```typescript
|
|
305
|
+
import { use_hazo_auth } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
306
|
+
import type { UseHazoAuthOptions, UseHazoAuthResult } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
307
|
+
|
|
308
|
+
function use_hazo_auth(options?: UseHazoAuthOptions): UseHazoAuthResult
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**Options:**
|
|
312
|
+
- `required_permissions?: string[]` - Array of permission names to check
|
|
313
|
+
- `strict?: boolean` - If `true`, throws error when permissions are missing (default: `false`)
|
|
314
|
+
- `skip?: boolean` - Skip fetch (for conditional use)
|
|
315
|
+
|
|
316
|
+
**Return Type:**
|
|
317
|
+
```typescript
|
|
318
|
+
type UseHazoAuthResult = HazoAuthResult & {
|
|
319
|
+
loading: boolean;
|
|
320
|
+
error: Error | null;
|
|
321
|
+
refetch: () => Promise<void>;
|
|
322
|
+
};
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
**Example Usage:**
|
|
326
|
+
|
|
327
|
+
```typescript
|
|
328
|
+
"use client";
|
|
329
|
+
|
|
330
|
+
import { use_hazo_auth } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
331
|
+
|
|
332
|
+
export function ProtectedComponent() {
|
|
333
|
+
const { authenticated, user, permissions, permission_ok, loading, error, refetch } =
|
|
334
|
+
use_hazo_auth({
|
|
335
|
+
required_permissions: ["admin_user_management"],
|
|
336
|
+
strict: false,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
if (loading) {
|
|
340
|
+
return <div>Loading...</div>;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (error) {
|
|
344
|
+
return <div>Error: {error.message}</div>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (!authenticated) {
|
|
348
|
+
return <div>Please log in to access this page.</div>;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!permission_ok) {
|
|
352
|
+
return <div>You don't have permission to access this page.</div>;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return (
|
|
356
|
+
<div>
|
|
357
|
+
<h1>Welcome, {user?.name || user?.email_address}!</h1>
|
|
358
|
+
<p>Your permissions: {permissions.join(", ")}</p>
|
|
359
|
+
<button onClick={() => refetch()}>Refresh Auth Status</button>
|
|
360
|
+
</div>
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Conditional Permission Checks:**
|
|
366
|
+
|
|
367
|
+
```typescript
|
|
368
|
+
"use client";
|
|
369
|
+
|
|
370
|
+
import { use_hazo_auth } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
371
|
+
|
|
372
|
+
export function ConditionalComponent() {
|
|
373
|
+
// Check multiple permissions
|
|
374
|
+
const userManagementAuth = use_hazo_auth({
|
|
375
|
+
required_permissions: ["admin_user_management"],
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
const roleManagementAuth = use_hazo_auth({
|
|
379
|
+
required_permissions: ["admin_role_management"],
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
return (
|
|
383
|
+
<div>
|
|
384
|
+
{userManagementAuth.permission_ok && (
|
|
385
|
+
<button>Manage Users</button>
|
|
386
|
+
)}
|
|
387
|
+
{roleManagementAuth.permission_ok && (
|
|
388
|
+
<button>Manage Roles</button>
|
|
389
|
+
)}
|
|
390
|
+
</div>
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `trigger_hazo_auth_refresh`
|
|
396
|
+
|
|
397
|
+
Triggers a refresh of authentication status across all components using `use_hazo_auth`. Useful after login, logout, or permission changes.
|
|
398
|
+
|
|
399
|
+
**Location:** `src/components/layouts/shared/hooks/use_hazo_auth.ts`
|
|
400
|
+
|
|
401
|
+
**Function Signature:**
|
|
402
|
+
```typescript
|
|
403
|
+
import { trigger_hazo_auth_refresh } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
404
|
+
|
|
405
|
+
function trigger_hazo_auth_refresh(): void
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
**Example Usage:**
|
|
409
|
+
|
|
410
|
+
```typescript
|
|
411
|
+
"use client";
|
|
412
|
+
|
|
413
|
+
import { trigger_hazo_auth_refresh } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
414
|
+
|
|
415
|
+
export function LogoutButton() {
|
|
416
|
+
const handleLogout = async () => {
|
|
417
|
+
await fetch("/api/hazo_auth/logout", { method: "POST" });
|
|
418
|
+
trigger_hazo_auth_refresh(); // Notify all components to refresh auth status
|
|
419
|
+
window.location.href = "/hazo_auth/login";
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
return <button onClick={handleLogout}>Logout</button>;
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Configuration
|
|
427
|
+
|
|
428
|
+
The authentication utility can be configured in `hazo_auth_config.ini` under the `[hazo_auth__auth_utility]` section:
|
|
429
|
+
|
|
430
|
+
```ini
|
|
431
|
+
[hazo_auth__auth_utility]
|
|
432
|
+
# Cache settings
|
|
433
|
+
# Maximum number of users to cache (LRU eviction, default: 10000)
|
|
434
|
+
cache_max_users = 10000
|
|
435
|
+
|
|
436
|
+
# Cache TTL in minutes (default: 15)
|
|
437
|
+
cache_ttl_minutes = 15
|
|
438
|
+
|
|
439
|
+
# Force cache refresh if older than this many minutes (default: 30)
|
|
440
|
+
cache_max_age_minutes = 30
|
|
441
|
+
|
|
442
|
+
# Rate limiting for /api/hazo_auth/get_auth endpoint
|
|
443
|
+
# Per-user rate limit (requests per minute, default: 100)
|
|
444
|
+
rate_limit_per_user = 100
|
|
445
|
+
|
|
446
|
+
# Per-IP rate limit for unauthenticated requests (default: 200)
|
|
447
|
+
rate_limit_per_ip = 200
|
|
448
|
+
|
|
449
|
+
# Permission check behavior
|
|
450
|
+
# Log all permission denials for security audit (default: true)
|
|
451
|
+
log_permission_denials = true
|
|
452
|
+
|
|
453
|
+
# User-friendly error messages
|
|
454
|
+
# Enable mapping of technical permissions to user-friendly messages (default: true)
|
|
455
|
+
enable_friendly_error_messages = true
|
|
456
|
+
|
|
457
|
+
# Permission message mappings (optional, comma-separated: permission_name:user_message)
|
|
458
|
+
# Example: admin_user_management:You don't have access to user management,admin_role_management:You don't have access to role management
|
|
459
|
+
permission_error_messages =
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### Testing Authentication and RBAC
|
|
463
|
+
|
|
464
|
+
#### Testing User Authentication
|
|
465
|
+
|
|
466
|
+
To test if a user is authenticated, use the `hazo_get_auth` function or the `use_hazo_auth` hook:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
// Server-side test
|
|
470
|
+
const authResult = await hazo_get_auth(request);
|
|
471
|
+
if (authResult.authenticated) {
|
|
472
|
+
console.log("User is authenticated:", authResult.user.email_address);
|
|
473
|
+
console.log("User permissions:", authResult.permissions);
|
|
474
|
+
} else {
|
|
475
|
+
console.log("User is not authenticated");
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Client-side test
|
|
479
|
+
const { authenticated, user, permissions } = use_hazo_auth();
|
|
480
|
+
if (authenticated) {
|
|
481
|
+
console.log("User is authenticated:", user?.email_address);
|
|
482
|
+
console.log("User permissions:", permissions);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
#### Testing RBAC Permissions
|
|
487
|
+
|
|
488
|
+
To test if a user has specific permissions:
|
|
489
|
+
|
|
490
|
+
```typescript
|
|
491
|
+
// Server-side - strict mode (throws error if missing)
|
|
492
|
+
try {
|
|
493
|
+
const authResult = await hazo_get_auth(request, {
|
|
494
|
+
required_permissions: ["admin_user_management", "admin_role_management"],
|
|
495
|
+
strict: true,
|
|
496
|
+
});
|
|
497
|
+
// User has all required permissions
|
|
498
|
+
console.log("Access granted");
|
|
499
|
+
} catch (error) {
|
|
500
|
+
if (error instanceof PermissionError) {
|
|
501
|
+
console.log("Missing permissions:", error.missing_permissions);
|
|
502
|
+
console.log("User permissions:", error.user_permissions);
|
|
503
|
+
console.log("User-friendly message:", error.user_friendly_message);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// Server-side - non-strict mode (returns status)
|
|
508
|
+
const authResult = await hazo_get_auth(request, {
|
|
509
|
+
required_permissions: ["admin_user_management"],
|
|
510
|
+
strict: false,
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
if (authResult.authenticated && authResult.permission_ok) {
|
|
514
|
+
console.log("User has required permissions");
|
|
515
|
+
} else if (authResult.authenticated) {
|
|
516
|
+
console.log("User is missing permissions:", authResult.missing_permissions);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
// Client-side test
|
|
520
|
+
const { permission_ok, missing_permissions, permissions } = use_hazo_auth({
|
|
521
|
+
required_permissions: ["admin_user_management"],
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
if (permission_ok) {
|
|
525
|
+
console.log("User has required permissions");
|
|
526
|
+
} else {
|
|
527
|
+
console.log("Missing permissions:", missing_permissions);
|
|
528
|
+
console.log("User permissions:", permissions);
|
|
529
|
+
}
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
#### Getting All User Permissions
|
|
533
|
+
|
|
534
|
+
To get all permissions for the current user:
|
|
535
|
+
|
|
536
|
+
```typescript
|
|
537
|
+
// Server-side
|
|
538
|
+
const authResult = await hazo_get_auth(request);
|
|
539
|
+
if (authResult.authenticated) {
|
|
540
|
+
console.log("All user permissions:", authResult.permissions);
|
|
541
|
+
// Check if user has a specific permission
|
|
542
|
+
const hasPermission = authResult.permissions.includes("admin_user_management");
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Client-side
|
|
546
|
+
const { permissions } = use_hazo_auth();
|
|
547
|
+
console.log("All user permissions:", permissions);
|
|
548
|
+
const hasPermission = permissions.includes("admin_user_management");
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
#### Testing in API Routes
|
|
552
|
+
|
|
553
|
+
Example of a protected API route with permission checking:
|
|
554
|
+
|
|
555
|
+
```typescript
|
|
556
|
+
// src/app/api/admin/users/route.ts
|
|
557
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
558
|
+
import { hazo_get_auth } from "hazo_auth/lib/auth/hazo_get_auth.server";
|
|
559
|
+
import { PermissionError } from "hazo_auth/lib/auth/auth_types";
|
|
560
|
+
|
|
561
|
+
export async function GET(request: NextRequest) {
|
|
562
|
+
try {
|
|
563
|
+
// Require authentication and specific permission
|
|
564
|
+
const authResult = await hazo_get_auth(request, {
|
|
565
|
+
required_permissions: ["admin_user_management"],
|
|
566
|
+
strict: true,
|
|
567
|
+
});
|
|
568
|
+
|
|
569
|
+
// Fetch users (only accessible to admins)
|
|
570
|
+
const users = await fetchUsers();
|
|
571
|
+
|
|
572
|
+
return NextResponse.json({ users });
|
|
573
|
+
} catch (error) {
|
|
574
|
+
if (error instanceof PermissionError) {
|
|
575
|
+
return NextResponse.json(
|
|
576
|
+
{
|
|
577
|
+
error: "Permission denied",
|
|
578
|
+
missing_permissions: error.missing_permissions,
|
|
579
|
+
user_friendly_message: error.user_friendly_message,
|
|
580
|
+
},
|
|
581
|
+
{ status: 403 }
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return NextResponse.json(
|
|
586
|
+
{ error: "Authentication required" },
|
|
587
|
+
{ status: 401 }
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
#### Testing in React Components
|
|
594
|
+
|
|
595
|
+
Example of a protected component with permission-based UI:
|
|
596
|
+
|
|
597
|
+
```typescript
|
|
598
|
+
"use client";
|
|
599
|
+
|
|
600
|
+
import { use_hazo_auth } from "hazo_auth/components/layouts/shared/hooks/use_hazo_auth";
|
|
601
|
+
|
|
602
|
+
export function AdminDashboard() {
|
|
603
|
+
const userManagementAuth = use_hazo_auth({
|
|
604
|
+
required_permissions: ["admin_user_management"],
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
const roleManagementAuth = use_hazo_auth({
|
|
608
|
+
required_permissions: ["admin_role_management"],
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
if (userManagementAuth.loading || roleManagementAuth.loading) {
|
|
612
|
+
return <div>Loading...</div>;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
if (!userManagementAuth.authenticated) {
|
|
616
|
+
return <div>Please log in to access this page.</div>;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return (
|
|
620
|
+
<div>
|
|
621
|
+
<h1>Admin Dashboard</h1>
|
|
622
|
+
{userManagementAuth.permission_ok && (
|
|
623
|
+
<section>
|
|
624
|
+
<h2>User Management</h2>
|
|
625
|
+
{/* User management UI */}
|
|
626
|
+
</section>
|
|
627
|
+
)}
|
|
628
|
+
{roleManagementAuth.permission_ok && (
|
|
629
|
+
<section>
|
|
630
|
+
<h2>Role Management</h2>
|
|
631
|
+
{/* Role management UI */}
|
|
632
|
+
</section>
|
|
633
|
+
)}
|
|
634
|
+
{!userManagementAuth.permission_ok && !roleManagementAuth.permission_ok && (
|
|
635
|
+
<div>You don't have permission to access any admin features.</div>
|
|
636
|
+
)}
|
|
637
|
+
</div>
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
### Cache Invalidation
|
|
643
|
+
|
|
644
|
+
The authentication cache is automatically invalidated in the following scenarios:
|
|
645
|
+
- User logout
|
|
646
|
+
- Password change
|
|
647
|
+
- User deactivation
|
|
648
|
+
- Role assignment changes
|
|
649
|
+
- Permission changes to roles
|
|
650
|
+
|
|
651
|
+
You can also manually invalidate the cache using the API endpoint:
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
// POST /api/hazo_auth/invalidate_cache
|
|
655
|
+
// Body: { user_id?: string, role_ids?: number[], invalidate_all?: boolean }
|
|
656
|
+
```
|
|
657
|
+
|
|
32
658
|
### Local Development (for package contributors)
|
|
33
659
|
|
|
34
660
|
- `npm install` to install dependencies.
|
|
@@ -38,8 +664,9 @@ After installing the package, you need to set up configuration files in your pro
|
|
|
38
664
|
### Project Structure
|
|
39
665
|
|
|
40
666
|
- `src/app` contains the application shell and route composition.
|
|
667
|
+
- `src/lib` is the home for shared utilities and authentication functions.
|
|
668
|
+
- `src/components` contains React components and hooks.
|
|
41
669
|
- `src/stories` holds Storybook stories for documenting components.
|
|
42
|
-
- `src/lib` is the home for shared utilities.
|
|
43
670
|
|
|
44
671
|
### Next Steps
|
|
45
672
|
|