hazo_auth 1.0.0 → 1.0.2

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 +911 -1
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -29,6 +29,915 @@ 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
+
658
+ ## Profile Picture Menu Widget
659
+
660
+ The Profile Picture Menu is a versatile component for navbar or sidebar that automatically displays:
661
+ - **When authenticated**: User's profile picture with a dropdown menu containing user info, settings link, logout, and custom menu items
662
+ - **When not authenticated**: Sign Up and Sign In buttons (or a single button, configurable)
663
+
664
+ ### Basic Usage (Recommended)
665
+
666
+ Use the `ProfilePicMenuWrapper` component which automatically loads configuration from `hazo_auth_config.ini`:
667
+
668
+ ```typescript
669
+ // In your navbar or layout component
670
+ import { ProfilePicMenuWrapper } from "hazo_auth/components/layouts/shared/components/profile_pic_menu_wrapper";
671
+
672
+ export function Navbar() {
673
+ return (
674
+ <nav className="flex items-center justify-between p-4">
675
+ <div>Logo</div>
676
+ <ProfilePicMenuWrapper
677
+ avatar_size="default" // "sm" | "default" | "lg"
678
+ className="ml-auto"
679
+ />
680
+ </nav>
681
+ );
682
+ }
683
+ ```
684
+
685
+ ### Direct Usage (Manual Configuration)
686
+
687
+ If you prefer to configure the component directly without using the config file:
688
+
689
+ ```typescript
690
+ "use client";
691
+
692
+ import { ProfilePicMenu } from "hazo_auth/components/layouts/shared/components/profile_pic_menu";
693
+
694
+ export function Navbar() {
695
+ return (
696
+ <nav className="flex items-center justify-between p-4">
697
+ <div>Logo</div>
698
+ <ProfilePicMenu
699
+ show_single_button={false}
700
+ sign_up_label="Sign Up"
701
+ sign_in_label="Sign In"
702
+ register_path="/hazo_auth/register"
703
+ login_path="/hazo_auth/login"
704
+ settings_path="/hazo_auth/my_settings"
705
+ logout_path="/api/hazo_auth/logout"
706
+ avatar_size="default"
707
+ className="ml-auto"
708
+ />
709
+ </nav>
710
+ );
711
+ }
712
+ ```
713
+
714
+ ### Configuration
715
+
716
+ Configure the Profile Picture Menu in `hazo_auth_config.ini` under the `[hazo_auth__profile_pic_menu]` section:
717
+
718
+ ```ini
719
+ [hazo_auth__profile_pic_menu]
720
+ # Button configuration for unauthenticated users
721
+ # Show only "Sign Up" button when true, show both "Sign Up" and "Sign In" buttons when false (default)
722
+ show_single_button = false
723
+
724
+ # Sign up button label
725
+ sign_up_label = Sign Up
726
+
727
+ # Sign in button label
728
+ sign_in_label = Sign In
729
+
730
+ # Register page path
731
+ register_path = /hazo_auth/register
732
+
733
+ # Login page path
734
+ login_path = /hazo_auth/login
735
+
736
+ # Settings page path (shown in dropdown menu when authenticated)
737
+ settings_path = /hazo_auth/my_settings
738
+
739
+ # Logout API endpoint path
740
+ logout_path = /api/hazo_auth/logout
741
+
742
+ # Custom menu items (optional)
743
+ # Format: "type:label:value_or_href:order" for info/link, or "separator:order" for separator
744
+ # Examples:
745
+ # - Info item: "info:Phone:+1234567890:3"
746
+ # - Link item: "link:My Account:/account:4"
747
+ # - Separator: "separator:2"
748
+ # Custom items are added to the default menu items (name, email, separator, Settings, Logout)
749
+ # Items are sorted by type (info first, then separators, then links) and then by order within each type
750
+ custom_menu_items =
751
+ ```
752
+
753
+ ### Component Props
754
+
755
+ #### `ProfilePicMenuWrapper` Props
756
+
757
+ - `className?: string` - Additional CSS classes
758
+ - `avatar_size?: "sm" | "default" | "lg"` - Size of the profile picture avatar (default: "default")
759
+
760
+ #### `ProfilePicMenu` Props
761
+
762
+ - `show_single_button?: boolean` - Show only "Sign Up" button when true (default: false)
763
+ - `sign_up_label?: string` - Label for sign up button (default: "Sign Up")
764
+ - `sign_in_label?: string` - Label for sign in button (default: "Sign In")
765
+ - `register_path?: string` - Path to registration page (default: "/hazo_auth/register")
766
+ - `login_path?: string` - Path to login page (default: "/hazo_auth/login")
767
+ - `settings_path?: string` - Path to settings page (default: "/hazo_auth/my_settings")
768
+ - `logout_path?: string` - Path to logout API endpoint (default: "/api/hazo_auth/logout")
769
+ - `custom_menu_items?: ProfilePicMenuMenuItem[]` - Array of custom menu items
770
+ - `className?: string` - Additional CSS classes
771
+ - `avatar_size?: "sm" | "default" | "lg"` - Size of the profile picture avatar (default: "default")
772
+
773
+ ### Custom Menu Items
774
+
775
+ You can add custom menu items to the dropdown menu. Items are automatically sorted by type (info → separator → link) and then by order.
776
+
777
+ **Menu Item Types:**
778
+
779
+ 1. **Info** - Display-only text (e.g., phone number, department)
780
+ - Format: `"info:label:value:order"`
781
+ - Example: `"info:Phone:+1234567890:3"`
782
+
783
+ 2. **Link** - Clickable menu item that navigates to a URL
784
+ - Format: `"link:label:href:order"`
785
+ - Example: `"link:My Account:/account:4"`
786
+
787
+ 3. **Separator** - Visual separator line
788
+ - Format: `"separator:order"`
789
+ - Example: `"separator:2"`
790
+
791
+ **Example Configuration:**
792
+
793
+ ```ini
794
+ [hazo_auth__profile_pic_menu]
795
+ # Add custom menu items
796
+ custom_menu_items = info:Phone:+1234567890:3,separator:2,link:My Account:/account:4,link:Help:/help:5
797
+ ```
798
+
799
+ This will create a menu with:
800
+ 1. Default items (name, email, separator, Settings, Logout)
801
+ 2. Custom info item: "Phone: +1234567890" (order 3)
802
+ 3. Custom separator (order 2)
803
+ 4. Custom link: "My Account" → `/account` (order 4)
804
+ 5. Custom link: "Help" → `/help` (order 5)
805
+
806
+ Items are sorted by type priority (info < separator < link) and then by order within each type.
807
+
808
+ ### Default Menu Items
809
+
810
+ When authenticated, the dropdown menu automatically includes:
811
+ - User's name (if available)
812
+ - User's email address
813
+ - Separator
814
+ - Settings link (with Settings icon)
815
+ - Logout link (with LogOut icon, triggers logout action)
816
+
817
+ ### Examples
818
+
819
+ #### Example 1: Simple Navbar Integration
820
+
821
+ ```typescript
822
+ // app/components/navbar.tsx
823
+ import { ProfilePicMenuWrapper } from "hazo_auth/components/layouts/shared/components/profile_pic_menu_wrapper";
824
+
825
+ export function Navbar() {
826
+ return (
827
+ <header className="border-b">
828
+ <nav className="container mx-auto flex items-center justify-between p-4">
829
+ <div className="text-xl font-bold">My App</div>
830
+ <ProfilePicMenuWrapper />
831
+ </nav>
832
+ </header>
833
+ );
834
+ }
835
+ ```
836
+
837
+ #### Example 2: Custom Styling and Size
838
+
839
+ ```typescript
840
+ // app/components/navbar.tsx
841
+ import { ProfilePicMenuWrapper } from "hazo_auth/components/layouts/shared/components/profile_pic_menu_wrapper";
842
+
843
+ export function Navbar() {
844
+ return (
845
+ <header className="bg-slate-900 text-white">
846
+ <nav className="container mx-auto flex items-center justify-between p-4">
847
+ <div className="text-xl font-bold">My App</div>
848
+ <ProfilePicMenuWrapper
849
+ avatar_size="sm"
850
+ className="bg-slate-800 rounded-lg p-2"
851
+ />
852
+ </nav>
853
+ </header>
854
+ );
855
+ }
856
+ ```
857
+
858
+ #### Example 3: With Custom Menu Items (Programmatic)
859
+
860
+ ```typescript
861
+ "use client";
862
+
863
+ import { ProfilePicMenu } from "hazo_auth/components/layouts/shared/components/profile_pic_menu";
864
+ import type { ProfilePicMenuMenuItem } from "hazo_auth/lib/profile_pic_menu_config.server";
865
+
866
+ export function Navbar() {
867
+ const customItems: ProfilePicMenuMenuItem[] = [
868
+ {
869
+ type: "info",
870
+ label: "Department",
871
+ value: "Engineering",
872
+ order: 3,
873
+ id: "dept_info",
874
+ },
875
+ {
876
+ type: "separator",
877
+ order: 2,
878
+ id: "custom_sep",
879
+ },
880
+ {
881
+ type: "link",
882
+ label: "Documentation",
883
+ href: "/docs",
884
+ order: 4,
885
+ id: "docs_link",
886
+ },
887
+ ];
888
+
889
+ return (
890
+ <nav className="flex items-center justify-between p-4">
891
+ <div>Logo</div>
892
+ <ProfilePicMenu
893
+ custom_menu_items={customItems}
894
+ avatar_size="default"
895
+ />
896
+ </nav>
897
+ );
898
+ }
899
+ ```
900
+
901
+ #### Example 4: Single Button Mode
902
+
903
+ ```typescript
904
+ // In hazo_auth_config.ini
905
+ [hazo_auth__profile_pic_menu]
906
+ show_single_button = true
907
+ sign_up_label = Get Started
908
+ ```
909
+
910
+ When `show_single_button` is `true`, only the "Sign Up" button is shown for unauthenticated users (no "Sign In" button).
911
+
912
+ ### Behavior
913
+
914
+ - **Loading State**: Shows a pulsing placeholder while checking authentication status
915
+ - **Unauthenticated**: Shows Sign Up/Sign In buttons (or single button if configured)
916
+ - **Authenticated**: Shows profile picture with dropdown menu
917
+ - **Profile Picture Fallback**: If no profile picture is set, shows user's initials
918
+ - **Logout**: Handles logout action, refreshes auth status, and redirects appropriately
919
+ - **Responsive**: Works well in both navbar and sidebar layouts
920
+
921
+ ### Styling
922
+
923
+ The component uses TailwindCSS classes and can be customized with:
924
+ - `className` prop for additional styling
925
+ - `avatar_size` prop for different avatar sizes
926
+ - CSS class names prefixed with `cls_profile_pic_menu_*` for targeted styling
927
+
928
+ Example custom styling:
929
+
930
+ ```css
931
+ /* Target specific elements */
932
+ .cls_profile_pic_menu_avatar {
933
+ border: 2px solid #3b82f6;
934
+ }
935
+
936
+ .cls_profile_pic_menu_dropdown {
937
+ min-width: 200px;
938
+ }
939
+ ```
940
+
32
941
  ### Local Development (for package contributors)
33
942
 
34
943
  - `npm install` to install dependencies.
@@ -38,8 +947,9 @@ After installing the package, you need to set up configuration files in your pro
38
947
  ### Project Structure
39
948
 
40
949
  - `src/app` contains the application shell and route composition.
950
+ - `src/lib` is the home for shared utilities and authentication functions.
951
+ - `src/components` contains React components and hooks.
41
952
  - `src/stories` holds Storybook stories for documenting components.
42
- - `src/lib` is the home for shared utilities.
43
953
 
44
954
  ### Next Steps
45
955
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "files": [
5
5
  "src/**/*",
6
6
  "public/file.svg",