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.
Files changed (2) hide show
  1. package/README.md +628 -1
  2. 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "files": [
5
5
  "src/**/*",
6
6
  "public/file.svg",