hazo_auth 4.0.0 → 4.2.0

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 (93) hide show
  1. package/README.md +312 -8
  2. package/SETUP_CHECKLIST.md +203 -0
  3. package/dist/app/api/hazo_auth/forgot_password/route.d.ts.map +1 -1
  4. package/dist/app/api/hazo_auth/forgot_password/route.js +15 -0
  5. package/dist/app/api/hazo_auth/logout/route.d.ts.map +1 -1
  6. package/dist/app/api/hazo_auth/logout/route.js +31 -0
  7. package/dist/app/api/hazo_auth/me/route.d.ts +3 -0
  8. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  9. package/dist/app/api/hazo_auth/me/route.js +19 -1
  10. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts +2 -0
  11. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts.map +1 -1
  12. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.js +8 -0
  13. package/dist/components/layouts/forgot_password/index.d.ts +7 -1
  14. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  15. package/dist/components/layouts/forgot_password/index.js +7 -2
  16. package/dist/components/layouts/login/index.d.ts +13 -1
  17. package/dist/components/layouts/login/index.d.ts.map +1 -1
  18. package/dist/components/layouts/login/index.js +11 -2
  19. package/dist/components/layouts/my_settings/components/connected_accounts_section.d.ts +17 -0
  20. package/dist/components/layouts/my_settings/components/connected_accounts_section.d.ts.map +1 -0
  21. package/dist/components/layouts/my_settings/components/connected_accounts_section.js +17 -0
  22. package/dist/components/layouts/my_settings/components/set_password_section.d.ts +26 -0
  23. package/dist/components/layouts/my_settings/components/set_password_section.d.ts.map +1 -0
  24. package/dist/components/layouts/my_settings/components/set_password_section.js +127 -0
  25. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts +3 -0
  26. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts.map +1 -1
  27. package/dist/components/layouts/my_settings/hooks/use_my_settings.js +9 -0
  28. package/dist/components/layouts/my_settings/index.d.ts.map +1 -1
  29. package/dist/components/layouts/my_settings/index.js +4 -2
  30. package/dist/components/layouts/profile_stamp_test/index.d.ts +10 -0
  31. package/dist/components/layouts/profile_stamp_test/index.d.ts.map +1 -0
  32. package/dist/components/layouts/profile_stamp_test/index.js +51 -0
  33. package/dist/components/layouts/shared/components/google_icon.d.ts +12 -0
  34. package/dist/components/layouts/shared/components/google_icon.d.ts.map +1 -0
  35. package/dist/components/layouts/shared/components/google_icon.js +9 -0
  36. package/dist/components/layouts/shared/components/google_sign_in_button.d.ts +21 -0
  37. package/dist/components/layouts/shared/components/google_sign_in_button.d.ts.map +1 -0
  38. package/dist/components/layouts/shared/components/google_sign_in_button.js +50 -0
  39. package/dist/components/layouts/shared/components/oauth_divider.d.ts +13 -0
  40. package/dist/components/layouts/shared/components/oauth_divider.d.ts.map +1 -0
  41. package/dist/components/layouts/shared/components/oauth_divider.js +13 -0
  42. package/dist/components/layouts/shared/components/profile_stamp.d.ts +58 -0
  43. package/dist/components/layouts/shared/components/profile_stamp.d.ts.map +1 -0
  44. package/dist/components/layouts/shared/components/profile_stamp.js +72 -0
  45. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  46. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  47. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +6 -0
  48. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
  49. package/dist/components/layouts/shared/hooks/use_auth_status.js +8 -0
  50. package/dist/components/layouts/shared/index.d.ts +7 -0
  51. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  52. package/dist/components/layouts/shared/index.js +4 -0
  53. package/dist/components/ui/button.d.ts +1 -1
  54. package/dist/components/ui/hover-card.d.ts +7 -0
  55. package/dist/components/ui/hover-card.d.ts.map +1 -0
  56. package/dist/components/ui/hover-card.js +29 -0
  57. package/dist/components/ui/index.d.ts +1 -0
  58. package/dist/components/ui/index.d.ts.map +1 -1
  59. package/dist/components/ui/index.js +1 -0
  60. package/dist/lib/auth/nextauth_config.d.ts +34 -0
  61. package/dist/lib/auth/nextauth_config.d.ts.map +1 -0
  62. package/dist/lib/auth/nextauth_config.js +171 -0
  63. package/dist/lib/config/default_config.d.ts +24 -0
  64. package/dist/lib/config/default_config.d.ts.map +1 -1
  65. package/dist/lib/config/default_config.js +14 -0
  66. package/dist/lib/index.d.ts +2 -0
  67. package/dist/lib/index.d.ts.map +1 -1
  68. package/dist/lib/index.js +1 -0
  69. package/dist/lib/login_config.server.d.ts +3 -0
  70. package/dist/lib/login_config.server.d.ts.map +1 -1
  71. package/dist/lib/login_config.server.js +4 -0
  72. package/dist/lib/oauth_config.server.d.ts +29 -0
  73. package/dist/lib/oauth_config.server.d.ts.map +1 -0
  74. package/dist/lib/oauth_config.server.js +40 -0
  75. package/dist/lib/services/login_service.d.ts.map +1 -1
  76. package/dist/lib/services/login_service.js +16 -1
  77. package/dist/lib/services/oauth_service.d.ts +88 -0
  78. package/dist/lib/services/oauth_service.d.ts.map +1 -0
  79. package/dist/lib/services/oauth_service.js +376 -0
  80. package/dist/lib/services/password_reset_service.d.ts +2 -0
  81. package/dist/lib/services/password_reset_service.d.ts.map +1 -1
  82. package/dist/lib/services/password_reset_service.js +10 -0
  83. package/dist/lib/services/registration_service.d.ts.map +1 -1
  84. package/dist/lib/services/registration_service.js +1 -0
  85. package/dist/lib/utils/password_validator.d.ts +13 -0
  86. package/dist/lib/utils/password_validator.d.ts.map +1 -0
  87. package/dist/lib/utils/password_validator.js +36 -0
  88. package/dist/server_pages/login.d.ts.map +1 -1
  89. package/dist/server_pages/login.js +6 -1
  90. package/dist/server_pages/login_client_wrapper.d.ts +5 -2
  91. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  92. package/dist/server_pages/login_client_wrapper.js +2 -2
  93. package/package.json +3 -1
package/README.md CHANGED
@@ -24,6 +24,7 @@ A reusable authentication UI component package powered by Next.js, TailwindCSS,
24
24
  - [Quick Start](#quick-start)
25
25
  - [Configuration Setup](#configuration-setup)
26
26
  - [Database Setup](#database-setup)
27
+ - [Google OAuth Setup](#google-oauth-setup)
27
28
  - [Using Components](#using-components)
28
29
  - [Authentication Service](#authentication-service)
29
30
  - [Proxy/Middleware Authentication](#proxymiddleware-authentication)
@@ -198,11 +199,12 @@ import { MySettingsLayout } from "hazo_auth/components/layouts/my_settings";
198
199
  import { UserManagementLayout } from "hazo_auth/components/layouts/user_management";
199
200
 
200
201
  // Import shared components and hooks from barrel export
201
- import {
202
- ProfilePicMenu,
202
+ import {
203
+ ProfilePicMenu,
203
204
  ProfilePicMenuWrapper,
204
- use_hazo_auth,
205
- use_auth_status
205
+ ProfileStamp,
206
+ use_hazo_auth,
207
+ use_auth_status
206
208
  } from "hazo_auth/components/layouts/shared";
207
209
 
208
210
  // Import server-side utilities
@@ -258,11 +260,12 @@ For client components (browser-safe, no Node.js dependencies):
258
260
 
259
261
  ```typescript
260
262
  // Use hazo_auth/client for client components
261
- import {
262
- ProfilePicMenu,
263
- use_auth_status,
263
+ import {
264
+ ProfilePicMenu,
265
+ ProfileStamp,
266
+ use_auth_status,
264
267
  use_hazo_auth,
265
- cn
268
+ cn
266
269
  } from "hazo_auth/client";
267
270
  ```
268
271
 
@@ -364,6 +367,19 @@ cp node_modules/hazo_auth/hazo_notify_config.example.ini ./hazo_notify_config.in
364
367
 
365
368
  **Note:** `JWT_SECRET` is required for JWT session token functionality (used for Edge-compatible proxy/middleware authentication). Generate a secure random string at least 32 characters long.
366
369
 
370
+ **For Google OAuth (optional):**
371
+ ```env
372
+ # NextAuth.js configuration (required for OAuth)
373
+ NEXTAUTH_SECRET=your_secure_random_string_at_least_32_characters
374
+ NEXTAUTH_URL=http://localhost:3000 # Change to production URL in production
375
+
376
+ # Google OAuth credentials (from Google Cloud Console)
377
+ HAZO_AUTH_GOOGLE_CLIENT_ID=your_google_client_id
378
+ HAZO_AUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret
379
+ ```
380
+
381
+ See [Google OAuth Setup](#google-oauth-setup) for detailed instructions.
382
+
367
383
  **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, so storing them elsewhere (including `node_modules/hazo_auth`) will break runtime access.
368
384
 
369
385
  ---
@@ -670,6 +686,222 @@ npx tsx scripts/apply_migration.ts
670
686
 
671
687
  ---
672
688
 
689
+ ## Google OAuth Setup
690
+
691
+ hazo_auth supports Google Sign-In via NextAuth.js v4, allowing users to authenticate with their Google accounts.
692
+
693
+ ### Features
694
+
695
+ - **Dual authentication**: Users can have BOTH Google OAuth and email/password login
696
+ - **Auto-linking**: Automatically links Google login to existing unverified email/password accounts
697
+ - **Graceful degradation**: Login page adapts based on enabled authentication methods
698
+ - **Set password feature**: Google-only users can add a password later via My Settings
699
+ - **Profile data**: Full name and profile picture automatically populated from Google
700
+
701
+ ### Step 1: Get Google OAuth Credentials
702
+
703
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
704
+ 2. Create a project or select an existing project
705
+ 3. Enable Google+ API (or Google Identity Services)
706
+ 4. Navigate to **Credentials** → **Create Credentials** → **OAuth 2.0 Client ID**
707
+ 5. Configure OAuth consent screen if prompted
708
+ 6. Set **Application type** to "Web application"
709
+ 7. Add **Authorized JavaScript origins**:
710
+ - Development: `http://localhost:3000`
711
+ - Production: `https://yourdomain.com`
712
+ 8. Add **Authorized redirect URIs**:
713
+ - Development: `http://localhost:3000/api/auth/callback/google`
714
+ - Production: `https://yourdomain.com/api/auth/callback/google`
715
+ 9. Copy the **Client ID** and **Client Secret**
716
+
717
+ ### Step 2: Add Environment Variables
718
+
719
+ Add to your `.env.local`:
720
+
721
+ ```env
722
+ # NextAuth.js configuration (REQUIRED for OAuth)
723
+ NEXTAUTH_SECRET=your_secure_random_string_at_least_32_characters
724
+ NEXTAUTH_URL=http://localhost:3000 # Change to production URL in production
725
+
726
+ # Google OAuth credentials (from Google Cloud Console)
727
+ HAZO_AUTH_GOOGLE_CLIENT_ID=your_google_client_id_from_step_1
728
+ HAZO_AUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret_from_step_1
729
+ ```
730
+
731
+ **Generate NEXTAUTH_SECRET:**
732
+ ```bash
733
+ openssl rand -base64 32
734
+ ```
735
+
736
+ ### Step 3: Run Database Migration
737
+
738
+ Add OAuth fields to the `hazo_users` table:
739
+
740
+ ```bash
741
+ npm run migrate migrations/005_add_oauth_fields_to_hazo_users.sql
742
+ ```
743
+
744
+ This migration adds:
745
+ - `google_id` - Google's unique user ID (TEXT, UNIQUE)
746
+ - `auth_providers` - Tracks authentication methods: 'email', 'google', or 'email,google'
747
+ - Index on `google_id` for fast OAuth lookups
748
+
749
+ **Manual migration (if needed):**
750
+
751
+ **PostgreSQL:**
752
+ ```sql
753
+ ALTER TABLE hazo_users
754
+ ADD COLUMN google_id TEXT UNIQUE;
755
+
756
+ ALTER TABLE hazo_users
757
+ ADD COLUMN auth_providers TEXT DEFAULT 'email';
758
+
759
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);
760
+ ```
761
+
762
+ **SQLite:**
763
+ ```sql
764
+ ALTER TABLE hazo_users
765
+ ADD COLUMN google_id TEXT;
766
+
767
+ ALTER TABLE hazo_users
768
+ ADD COLUMN auth_providers TEXT DEFAULT 'email';
769
+
770
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_users_google_id_unique ON hazo_users(google_id);
771
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);
772
+ ```
773
+
774
+ ### Step 4: Configure OAuth in hazo_auth_config.ini
775
+
776
+ ```ini
777
+ [hazo_auth__oauth]
778
+ # Enable Google OAuth login (default: true)
779
+ enable_google = true
780
+
781
+ # Enable traditional email/password login (default: true)
782
+ enable_email_password = true
783
+
784
+ # Auto-link Google login to existing unverified email/password accounts (default: true)
785
+ auto_link_unverified_accounts = true
786
+
787
+ # Customize button text (optional)
788
+ google_button_text = Continue with Google
789
+ oauth_divider_text = or
790
+ ```
791
+
792
+ ### Step 5: Create NextAuth API Routes
793
+
794
+ Create `app/api/auth/[...nextauth]/route.ts`:
795
+
796
+ ```typescript
797
+ export { GET, POST } from "hazo_auth/server/routes/nextauth";
798
+ ```
799
+
800
+ Create `app/api/hazo_auth/oauth/google/callback/route.ts`:
801
+
802
+ ```typescript
803
+ export { GET } from "hazo_auth/server/routes/oauth_google_callback";
804
+ ```
805
+
806
+ Create `app/api/hazo_auth/set_password/route.ts`:
807
+
808
+ ```typescript
809
+ export { POST } from "hazo_auth/server/routes/set_password";
810
+ ```
811
+
812
+ **Or use the CLI generator:**
813
+ ```bash
814
+ npx hazo_auth generate-routes --oauth
815
+ ```
816
+
817
+ ### Step 6: Test Google OAuth
818
+
819
+ 1. Start your dev server: `npm run dev`
820
+ 2. Visit `http://localhost:3000/hazo_auth/login`
821
+ 3. You should see the "Sign in with Google" button
822
+ 4. Click it and authenticate with your Google account
823
+ 5. You'll be redirected back and logged in
824
+
825
+ ### User Flows
826
+
827
+ **New User - Google Sign-In:**
828
+ - User clicks "Sign in with Google"
829
+ - Authenticates with Google
830
+ - Account created with Google profile data (email, name, profile picture)
831
+ - Email is automatically verified
832
+ - User can log in with Google anytime
833
+
834
+ **Existing Unverified User - Google Sign-In:**
835
+ - User has email/password account but hasn't verified email
836
+ - Clicks "Sign in with Google" with same email
837
+ - System auto-links Google account (if `auto_link_unverified_accounts = true`)
838
+ - Email becomes verified
839
+ - User can now log in with EITHER Google OR email/password
840
+
841
+ **Google-Only User Adds Password:**
842
+ - Google-only user visits My Settings
843
+ - "Set Password" section appears
844
+ - User sets a password
845
+ - User can now log in with EITHER method
846
+
847
+ **Google-Only User Tries Forgot Password:**
848
+ - User registered with Google tries "Forgot Password"
849
+ - System shows: "You registered with Google. Please sign in with Google instead."
850
+
851
+ ### Configuration Options
852
+
853
+ **Disable email/password login (Google-only):**
854
+ ```ini
855
+ [hazo_auth__oauth]
856
+ enable_google = true
857
+ enable_email_password = false
858
+ ```
859
+
860
+ **Disable Google OAuth (email/password only):**
861
+ ```ini
862
+ [hazo_auth__oauth]
863
+ enable_google = false
864
+ enable_email_password = true
865
+ ```
866
+
867
+ ### API Response Changes
868
+
869
+ The `/api/hazo_auth/me` endpoint now includes OAuth status:
870
+
871
+ ```typescript
872
+ {
873
+ authenticated: true,
874
+ // ... existing fields
875
+ auth_providers: "email,google", // NEW: Tracks authentication methods
876
+ has_password: true, // NEW: Whether user has password set
877
+ google_connected: true, // NEW: Whether Google account is linked
878
+ }
879
+ ```
880
+
881
+ ### Dependencies
882
+
883
+ Google OAuth adds one new dependency:
884
+ - `next-auth@^4.24.11` - NextAuth.js for OAuth handling (automatically installed with hazo_auth)
885
+
886
+ ### Troubleshooting
887
+
888
+ **"Sign in with Google" button not showing:**
889
+ - Verify `enable_google = true` in `[hazo_auth__oauth]` section
890
+ - Check `HAZO_AUTH_GOOGLE_CLIENT_ID` and `HAZO_AUTH_GOOGLE_CLIENT_SECRET` are set
891
+ - Check `NEXTAUTH_URL` matches your current URL
892
+
893
+ **OAuth callback error:**
894
+ - Verify redirect URI in Google Cloud Console matches exactly: `http://localhost:3000/api/auth/callback/google`
895
+ - Check `NEXTAUTH_SECRET` is set and at least 32 characters
896
+ - Verify API routes are created: `/api/auth/[...nextauth]/route.ts` and `/api/hazo_auth/oauth/google/callback/route.ts`
897
+
898
+ **User created but not logged in:**
899
+ - Check browser console for errors
900
+ - Verify `/api/hazo_auth/oauth/google/callback` route exists
901
+ - Check server logs for errors during session creation
902
+
903
+ ---
904
+
673
905
  ## Using Components
674
906
 
675
907
  ### Package Exports
@@ -863,6 +1095,10 @@ This is the **standardized endpoint** that ensures consistent response format ac
863
1095
  last_logon: string | undefined,
864
1096
  profile_picture_url: string | null,
865
1097
  profile_source: "upload" | "library" | "gravatar" | "custom" | undefined,
1098
+ // Profile picture aliases (for consuming app compatibility)
1099
+ profile_image?: string, // Alias for profile_picture_url
1100
+ avatar_url?: string, // Alias for profile_picture_url
1101
+ image?: string, // Alias for profile_picture_url
866
1102
  // Permissions (always included)
867
1103
  user: {
868
1104
  id: string,
@@ -1375,6 +1611,74 @@ The test tool uses the `/api/hazo_auth/rbac_test` endpoint which is included in
1375
1611
 
1376
1612
  ---
1377
1613
 
1614
+ ## ProfileStamp Component
1615
+
1616
+ The `ProfileStamp` component is a drop-in widget that displays a circular profile picture with a hover card showing user details. Perfect for adding profile attribution to notes, comments, or any user-generated content.
1617
+
1618
+ ### Features
1619
+
1620
+ - Displays user's profile picture or initials
1621
+ - Hover card with user name, email, and custom fields
1622
+ - Three sizes: sm (24px), default (32px), lg (40px)
1623
+ - Automatic loading state and unauthenticated fallback
1624
+ - Fully accessible with keyboard navigation
1625
+
1626
+ ### Usage
1627
+
1628
+ ```typescript
1629
+ import { ProfileStamp } from "hazo_auth/client";
1630
+
1631
+ // Basic usage
1632
+ <ProfileStamp />
1633
+
1634
+ // With custom size and fields
1635
+ <ProfileStamp
1636
+ size="lg"
1637
+ custom_fields={[
1638
+ { label: "Role", value: "Admin" },
1639
+ { label: "Department", value: "Engineering" }
1640
+ ]}
1641
+ />
1642
+
1643
+ // Hide default fields, only show custom fields
1644
+ <ProfileStamp
1645
+ show_name={false}
1646
+ show_email={false}
1647
+ custom_fields={[
1648
+ { label: "Posted", value: "2 hours ago" }
1649
+ ]}
1650
+ />
1651
+ ```
1652
+
1653
+ ### Props
1654
+
1655
+ | Prop | Type | Default | Description |
1656
+ |------|------|---------|-------------|
1657
+ | `size` | `"sm" \| "default" \| "lg"` | `"default"` | Avatar size (sm: 24px, default: 32px, lg: 40px) |
1658
+ | `custom_fields` | `ProfileStampCustomField[]` | `[]` | Custom fields to display in hover card |
1659
+ | `className` | `string` | `undefined` | Additional CSS classes |
1660
+ | `show_name` | `boolean` | `true` | Show user name in hover card |
1661
+ | `show_email` | `boolean` | `true` | Show email in hover card |
1662
+
1663
+ ### ProfileStampCustomField Type
1664
+
1665
+ ```typescript
1666
+ type ProfileStampCustomField = {
1667
+ label: string; // Field label (e.g., "Role", "Department")
1668
+ value: string; // Field value (e.g., "Admin", "Engineering")
1669
+ };
1670
+ ```
1671
+
1672
+ ### Test Page
1673
+
1674
+ Visit `/hazo_auth/profile_stamp_test` in your dev environment to see examples of ProfileStamp with various configurations:
1675
+ - Size variants
1676
+ - Custom fields
1677
+ - Display options (showing/hiding name and email)
1678
+ - Usage scenarios (notes, comments, activity feeds)
1679
+
1680
+ ---
1681
+
1378
1682
  ## Profile Picture Menu Widget
1379
1683
 
1380
1684
  The Profile Picture Menu is a versatile component for navbar or sidebar that automatically displays:
@@ -492,6 +492,169 @@ This script will:
492
492
 
493
493
  ---
494
494
 
495
+ ## Phase 3.2: Google OAuth Setup (Optional)
496
+
497
+ Google OAuth Sign-In allows users to authenticate with their Google accounts. This section is optional - skip if you don't need OAuth.
498
+
499
+ ### Step 3.2.1: Get Google OAuth Credentials
500
+
501
+ 1. Go to [Google Cloud Console](https://console.cloud.google.com/)
502
+ 2. Create a project or select an existing project
503
+ 3. Enable **Google+ API** (or Google Identity Services)
504
+ 4. Navigate to **Credentials** → **Create Credentials** → **OAuth 2.0 Client ID**
505
+ 5. Configure OAuth consent screen if prompted
506
+ 6. Set **Application type** to "Web application"
507
+ 7. Add **Authorized JavaScript origins**:
508
+ - Development: `http://localhost:3000`
509
+ - Production: `https://yourdomain.com`
510
+ 8. Add **Authorized redirect URIs**:
511
+ - Development: `http://localhost:3000/api/auth/callback/google`
512
+ - Production: `https://yourdomain.com/api/auth/callback/google`
513
+ 9. Copy the **Client ID** and **Client Secret**
514
+
515
+ ### Step 3.2.2: Add OAuth Environment Variables
516
+
517
+ Add to your `.env.local`:
518
+
519
+ ```env
520
+ # NextAuth.js configuration (REQUIRED for OAuth)
521
+ NEXTAUTH_SECRET=your_secure_random_string_at_least_32_characters
522
+ NEXTAUTH_URL=http://localhost:3000 # Change to production URL in production
523
+
524
+ # Google OAuth credentials (from Google Cloud Console)
525
+ HAZO_AUTH_GOOGLE_CLIENT_ID=your_google_client_id_from_step_1
526
+ HAZO_AUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret_from_step_1
527
+ ```
528
+
529
+ **Generate NEXTAUTH_SECRET:**
530
+ ```bash
531
+ openssl rand -base64 32
532
+ ```
533
+
534
+ ### Step 3.2.3: Run OAuth Database Migration
535
+
536
+ Add OAuth fields to the `hazo_users` table:
537
+
538
+ ```bash
539
+ npm run migrate migrations/005_add_oauth_fields_to_hazo_users.sql
540
+ ```
541
+
542
+ **Verify migration applied:**
543
+
544
+ **PostgreSQL:**
545
+ ```sql
546
+ SELECT column_name FROM information_schema.columns
547
+ WHERE table_name = 'hazo_users'
548
+ AND column_name IN ('google_id', 'auth_providers');
549
+ -- Expected: 2 rows returned
550
+ ```
551
+
552
+ **SQLite:**
553
+ ```bash
554
+ sqlite3 data/hazo_auth.sqlite ".schema hazo_users" | grep -E "google_id|auth_providers"
555
+ # Expected: google_id TEXT UNIQUE, auth_providers TEXT DEFAULT 'email'
556
+ ```
557
+
558
+ **Manual migration (if needed):**
559
+
560
+ **PostgreSQL:**
561
+ ```sql
562
+ ALTER TABLE hazo_users ADD COLUMN google_id TEXT UNIQUE;
563
+ ALTER TABLE hazo_users ADD COLUMN auth_providers TEXT DEFAULT 'email';
564
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);
565
+ ```
566
+
567
+ **SQLite:**
568
+ ```sql
569
+ ALTER TABLE hazo_users ADD COLUMN google_id TEXT;
570
+ ALTER TABLE hazo_users ADD COLUMN auth_providers TEXT DEFAULT 'email';
571
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_users_google_id_unique ON hazo_users(google_id);
572
+ CREATE INDEX IF NOT EXISTS idx_hazo_users_google_id ON hazo_users(google_id);
573
+ ```
574
+
575
+ ### Step 3.2.4: Configure OAuth in hazo_auth_config.ini
576
+
577
+ Add (or modify) the OAuth section:
578
+
579
+ ```ini
580
+ [hazo_auth__oauth]
581
+ # Enable Google OAuth login (default: true)
582
+ enable_google = true
583
+
584
+ # Enable traditional email/password login (default: true)
585
+ enable_email_password = true
586
+
587
+ # Auto-link Google login to existing unverified email/password accounts (default: true)
588
+ auto_link_unverified_accounts = true
589
+
590
+ # Customize button text (optional)
591
+ google_button_text = Continue with Google
592
+ oauth_divider_text = or
593
+ ```
594
+
595
+ **Configuration Options:**
596
+
597
+ - **Google-only authentication** (no email/password):
598
+ ```ini
599
+ enable_google = true
600
+ enable_email_password = false
601
+ ```
602
+
603
+ - **Email/password only** (no OAuth):
604
+ ```ini
605
+ enable_google = false
606
+ enable_email_password = true
607
+ ```
608
+
609
+ - **Both methods** (recommended):
610
+ ```ini
611
+ enable_google = true
612
+ enable_email_password = true
613
+ ```
614
+
615
+ ### Step 3.2.5: Create OAuth API Routes
616
+
617
+ **Option A: Use CLI generator (Recommended):**
618
+ ```bash
619
+ npx hazo_auth generate-routes --oauth
620
+ ```
621
+
622
+ **Option B: Create manually:**
623
+
624
+ Create `app/api/auth/[...nextauth]/route.ts`:
625
+ ```typescript
626
+ export { GET, POST } from "hazo_auth/server/routes/nextauth";
627
+ ```
628
+
629
+ Create `app/api/hazo_auth/oauth/google/callback/route.ts`:
630
+ ```typescript
631
+ export { GET } from "hazo_auth/server/routes/oauth_google_callback";
632
+ ```
633
+
634
+ Create `app/api/hazo_auth/set_password/route.ts`:
635
+ ```typescript
636
+ export { POST } from "hazo_auth/server/routes/set_password";
637
+ ```
638
+
639
+ ### Step 3.2.6: Verify OAuth API Routes
640
+
641
+ ```bash
642
+ ls app/api/auth/\[...nextauth\]/route.ts
643
+ ls app/api/hazo_auth/oauth/google/callback/route.ts
644
+ ls app/api/hazo_auth/set_password/route.ts
645
+ # All files should exist
646
+ ```
647
+
648
+ **OAuth Setup Checklist:**
649
+ - [ ] Google OAuth credentials obtained
650
+ - [ ] `NEXTAUTH_SECRET` and `NEXTAUTH_URL` set in `.env.local`
651
+ - [ ] `HAZO_AUTH_GOOGLE_CLIENT_ID` and `HAZO_AUTH_GOOGLE_CLIENT_SECRET` set
652
+ - [ ] OAuth migration applied (google_id and auth_providers columns added)
653
+ - [ ] `[hazo_auth__oauth]` section configured in `hazo_auth_config.ini`
654
+ - [ ] OAuth API routes created (`[...nextauth]`, `oauth/google/callback`, `set_password`)
655
+
656
+ ---
657
+
495
658
  ## Phase 4: API Routes
496
659
 
497
660
  Create API route files in your project. Each file re-exports handlers from hazo_auth.
@@ -954,6 +1117,46 @@ Visit each page and verify it loads:
954
1117
  - [ ] `http://localhost:3000/hazo_auth/register` - Registration form displays
955
1118
  - [ ] `http://localhost:3000/hazo_auth/forgot_password` - Forgot password form displays
956
1119
  - [ ] `http://localhost:3000/hazo_auth/my_settings` - Settings page displays (after login)
1120
+ - [ ] `http://localhost:3000/hazo_auth/profile_stamp_test` - ProfileStamp component examples display
1121
+
1122
+ ### Test 7: Google OAuth (if configured)
1123
+
1124
+ If you completed Phase 3.2 (Google OAuth Setup):
1125
+
1126
+ **Check login page:**
1127
+ 1. Visit `http://localhost:3000/hazo_auth/login`
1128
+ 2. Verify "Sign in with Google" button appears
1129
+ 3. Verify divider with "or" text (if email/password is also enabled)
1130
+
1131
+ **Test Google sign-in:**
1132
+ 1. Click "Sign in with Google" button
1133
+ 2. You should be redirected to Google's login page
1134
+ 3. Sign in with your Google account
1135
+ 4. You should be redirected back to your app and logged in
1136
+ 5. Visit `/hazo_auth/my_settings`
1137
+ 6. Verify "Connected Accounts" section shows Google as connected
1138
+
1139
+ **Test Google-only user features:**
1140
+ 1. If you signed in with Google (and didn't have a password account first):
1141
+ 2. Visit `/hazo_auth/my_settings`
1142
+ 3. Verify "Set Password" section appears
1143
+ 4. Set a password
1144
+ 5. Log out and try logging in with email/password (should work)
1145
+
1146
+ **Test forgot password with Google-only user:**
1147
+ 1. Create a new user with Google OAuth only
1148
+ 2. Try visiting `/hazo_auth/forgot_password`
1149
+ 3. Enter the Google user's email
1150
+ 4. Should show message: "You registered with Google. Please sign in with Google instead."
1151
+
1152
+ **OAuth Test Checklist:**
1153
+ - [ ] "Sign in with Google" button appears on login page
1154
+ - [ ] OAuth divider appears (if both auth methods enabled)
1155
+ - [ ] Google OAuth flow completes successfully
1156
+ - [ ] User is logged in after OAuth callback
1157
+ - [ ] Connected Accounts section shows Google in My Settings
1158
+ - [ ] Set Password feature works for Google-only users
1159
+ - [ ] Forgot password shows appropriate message for Google-only users
957
1160
 
958
1161
  ---
959
1162
 
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/forgot_password/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAgG9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/forgot_password/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAoH9C"}
@@ -49,6 +49,21 @@ export async function POST(request) {
49
49
  message: "If an account with that email exists, a password reset link has been sent.",
50
50
  }, { status: 200 });
51
51
  }
52
+ // Check if this is a Google-only user (no password set)
53
+ if (result.no_password_set) {
54
+ logger.info("password_reset_no_password_set", {
55
+ filename: get_filename(),
56
+ line_number: get_line_number(),
57
+ email,
58
+ note: "User does not have a password set (OAuth-only account)",
59
+ });
60
+ // Return success to prevent email enumeration, but include flag for UI handling
61
+ return NextResponse.json({
62
+ success: true,
63
+ no_password_set: true,
64
+ message: "If an account with that email exists, a password reset link has been sent.",
65
+ }, { status: 200 });
66
+ }
52
67
  logger.info("password_reset_requested", {
53
68
  filename: get_filename(),
54
69
  line_number: get_line_number(),
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/logout/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAmF9C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/logout/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAOxD,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;IAkH9C"}
@@ -32,6 +32,37 @@ export async function POST(request) {
32
32
  expires: new Date(0),
33
33
  path: "/",
34
34
  });
35
+ // Clear NextAuth session cookies (for OAuth users)
36
+ response.cookies.set("next-auth.session-token", "", {
37
+ expires: new Date(0),
38
+ path: "/",
39
+ });
40
+ response.cookies.set("next-auth.csrf-token", "", {
41
+ expires: new Date(0),
42
+ path: "/",
43
+ });
44
+ response.cookies.set("next-auth.callback-url", "", {
45
+ expires: new Date(0),
46
+ path: "/",
47
+ });
48
+ // Also clear secure cookie variants (used in production with HTTPS)
49
+ response.cookies.set("__Secure-next-auth.session-token", "", {
50
+ expires: new Date(0),
51
+ path: "/",
52
+ });
53
+ response.cookies.set("__Secure-next-auth.csrf-token", "", {
54
+ expires: new Date(0),
55
+ path: "/",
56
+ });
57
+ response.cookies.set("__Secure-next-auth.callback-url", "", {
58
+ expires: new Date(0),
59
+ path: "/",
60
+ });
61
+ // Host-prefixed variants (for some NextAuth configurations)
62
+ response.cookies.set("__Host-next-auth.csrf-token", "", {
63
+ expires: new Date(0),
64
+ path: "/",
65
+ });
35
66
  // Invalidate user cache
36
67
  if (user_id) {
37
68
  try {
@@ -14,6 +14,9 @@ import { NextRequest, NextResponse } from "next/server";
14
14
  * email_verified: boolean,
15
15
  * last_logon: string | undefined,
16
16
  * profile_picture_url: string | null,
17
+ * profile_image: string | null, // alias for profile_picture_url
18
+ * avatar_url: string | null, // alias for profile_picture_url
19
+ * image: string | null, // alias for profile_picture_url
17
20
  * profile_source: "upload" | "library" | "gravatar" | "custom" | undefined,
18
21
  * user: { id, email_address, name, is_active, profile_picture_url },
19
22
  * permissions: string[],
@@ -1 +1 @@
1
- {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/me/route.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;IA0E7C"}
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../src/app/api/hazo_auth/me/route.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;IA2F7C"}