hazo_auth 6.1.1 → 7.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 (192) hide show
  1. package/README.md +163 -8
  2. package/SETUP_CHECKLIST.md +148 -0
  3. package/cli-src/lib/auth/nextauth_config.ts +101 -1
  4. package/cli-src/lib/email_verification_config.server.ts +0 -34
  5. package/cli-src/lib/forgot_password_config.server.ts +0 -34
  6. package/cli-src/lib/login_config.server.ts +0 -31
  7. package/cli-src/lib/my_settings_config.server.ts +0 -3
  8. package/cli-src/lib/oauth_config.server.ts +58 -0
  9. package/cli-src/lib/register_config.server.ts +11 -31
  10. package/cli-src/lib/reset_password_config.server.ts +0 -31
  11. package/cli-src/lib/services/oauth_service.ts +197 -0
  12. package/config/hazo_auth_config.example.ini +38 -41
  13. package/dist/components/layouts/create_firm/index.d.ts +4 -8
  14. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  15. package/dist/components/layouts/create_firm/index.js +3 -3
  16. package/dist/components/layouts/email_verification/index.d.ts +4 -5
  17. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  18. package/dist/components/layouts/email_verification/index.js +4 -4
  19. package/dist/components/layouts/forgot_password/index.d.ts +4 -5
  20. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  21. package/dist/components/layouts/forgot_password/index.js +2 -2
  22. package/dist/components/layouts/login/index.d.ts +13 -9
  23. package/dist/components/layouts/login/index.d.ts.map +1 -1
  24. package/dist/components/layouts/login/index.js +12 -6
  25. package/dist/components/layouts/otp/index.d.ts +8 -1
  26. package/dist/components/layouts/otp/index.d.ts.map +1 -1
  27. package/dist/components/layouts/otp/index.js +4 -2
  28. package/dist/components/layouts/register/index.d.ts +11 -7
  29. package/dist/components/layouts/register/index.d.ts.map +1 -1
  30. package/dist/components/layouts/register/index.js +8 -4
  31. package/dist/components/layouts/reset_password/index.d.ts +4 -5
  32. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  33. package/dist/components/layouts/reset_password/index.js +5 -5
  34. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +3 -5
  35. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  36. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  37. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +25 -0
  38. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -0
  39. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +49 -0
  40. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +1 -1
  41. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +3 -6
  42. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  43. package/dist/components/layouts/shared/components/two_column_auth_layout.js +8 -5
  44. package/dist/consent/consent_state.d.ts +18 -0
  45. package/dist/consent/consent_state.d.ts.map +1 -0
  46. package/dist/consent/consent_state.js +29 -0
  47. package/dist/consent/cookie_consent_banner.d.ts +11 -0
  48. package/dist/consent/cookie_consent_banner.d.ts.map +1 -0
  49. package/dist/consent/cookie_consent_banner.js +40 -0
  50. package/dist/consent/gtm_mapping.d.ts +13 -0
  51. package/dist/consent/gtm_mapping.d.ts.map +1 -0
  52. package/dist/consent/gtm_mapping.js +30 -0
  53. package/dist/consent/index.d.ts +7 -0
  54. package/dist/consent/index.d.ts.map +1 -0
  55. package/dist/consent/index.js +7 -0
  56. package/dist/consent/manage_modal.d.ts +2 -0
  57. package/dist/consent/manage_modal.d.ts.map +1 -0
  58. package/dist/consent/manage_modal.js +33 -0
  59. package/dist/consent/read_consent.d.ts +15 -0
  60. package/dist/consent/read_consent.d.ts.map +1 -0
  61. package/dist/consent/read_consent.js +23 -0
  62. package/dist/consent/use_consent.d.ts +7 -0
  63. package/dist/consent/use_consent.d.ts.map +1 -0
  64. package/dist/consent/use_consent.js +55 -0
  65. package/dist/lib/auth/nextauth_config.d.ts +10 -0
  66. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  67. package/dist/lib/auth/nextauth_config.js +80 -2
  68. package/dist/lib/email_verification_config.server.d.ts +0 -3
  69. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  70. package/dist/lib/email_verification_config.server.js +0 -15
  71. package/dist/lib/forgot_password_config.server.d.ts +0 -3
  72. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  73. package/dist/lib/forgot_password_config.server.js +0 -15
  74. package/dist/lib/login_config.server.d.ts +0 -3
  75. package/dist/lib/login_config.server.d.ts.map +1 -1
  76. package/dist/lib/login_config.server.js +0 -13
  77. package/dist/lib/my_settings_config.server.d.ts +0 -1
  78. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  79. package/dist/lib/my_settings_config.server.js +0 -2
  80. package/dist/lib/oauth_config.server.d.ts +17 -0
  81. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  82. package/dist/lib/oauth_config.server.js +25 -0
  83. package/dist/lib/register_config.server.d.ts +2 -3
  84. package/dist/lib/register_config.server.d.ts.map +1 -1
  85. package/dist/lib/register_config.server.js +4 -13
  86. package/dist/lib/reset_password_config.server.d.ts +0 -3
  87. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  88. package/dist/lib/reset_password_config.server.js +0 -13
  89. package/dist/lib/services/oauth_service.d.ts +24 -0
  90. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  91. package/dist/lib/services/oauth_service.js +155 -0
  92. package/dist/page_components/create_firm.d.ts +13 -1
  93. package/dist/page_components/create_firm.d.ts.map +1 -1
  94. package/dist/page_components/create_firm.js +10 -6
  95. package/dist/page_components/forgot_password.d.ts +1 -4
  96. package/dist/page_components/forgot_password.d.ts.map +1 -1
  97. package/dist/page_components/forgot_password.js +2 -6
  98. package/dist/page_components/login.d.ts +1 -4
  99. package/dist/page_components/login.d.ts.map +1 -1
  100. package/dist/page_components/login.js +2 -6
  101. package/dist/page_components/register.d.ts +1 -4
  102. package/dist/page_components/register.d.ts.map +1 -1
  103. package/dist/page_components/register.js +2 -6
  104. package/dist/page_components/reset_password.d.ts +1 -4
  105. package/dist/page_components/reset_password.d.ts.map +1 -1
  106. package/dist/page_components/reset_password.js +2 -6
  107. package/dist/page_components/verify_email.d.ts +1 -4
  108. package/dist/page_components/verify_email.d.ts.map +1 -1
  109. package/dist/page_components/verify_email.js +2 -6
  110. package/dist/server/routes/index.d.ts +1 -0
  111. package/dist/server/routes/index.d.ts.map +1 -1
  112. package/dist/server/routes/index.js +1 -0
  113. package/dist/server/routes/oauth_facebook_callback.d.ts +8 -0
  114. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -0
  115. package/dist/server/routes/oauth_facebook_callback.js +157 -0
  116. package/dist/server/routes/oauth_google_callback.js +1 -1
  117. package/dist/server_pages/forgot_password.d.ts +13 -17
  118. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  119. package/dist/server_pages/forgot_password.js +12 -8
  120. package/dist/server_pages/forgot_password_client_wrapper.d.ts +7 -6
  121. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  122. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  123. package/dist/server_pages/login.d.ts +22 -21
  124. package/dist/server_pages/login.d.ts.map +1 -1
  125. package/dist/server_pages/login.js +15 -19
  126. package/dist/server_pages/login_client_wrapper.d.ts +10 -6
  127. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  128. package/dist/server_pages/login_client_wrapper.js +2 -2
  129. package/dist/server_pages/my_settings.d.ts +2 -0
  130. package/dist/server_pages/my_settings.d.ts.map +1 -1
  131. package/dist/server_pages/my_settings.js +8 -2
  132. package/dist/server_pages/otp.d.ts +16 -2
  133. package/dist/server_pages/otp.d.ts.map +1 -1
  134. package/dist/server_pages/otp.js +10 -3
  135. package/dist/server_pages/register.d.ts +19 -16
  136. package/dist/server_pages/register.d.ts.map +1 -1
  137. package/dist/server_pages/register.js +15 -12
  138. package/dist/server_pages/register_client_wrapper.d.ts +10 -6
  139. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  140. package/dist/server_pages/register_client_wrapper.js +2 -2
  141. package/dist/server_pages/reset_password.d.ts +11 -16
  142. package/dist/server_pages/reset_password.d.ts.map +1 -1
  143. package/dist/server_pages/reset_password.js +11 -9
  144. package/dist/server_pages/reset_password_client_wrapper.d.ts +7 -6
  145. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  146. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  147. package/dist/server_pages/verify_email.d.ts +11 -17
  148. package/dist/server_pages/verify_email.d.ts.map +1 -1
  149. package/dist/server_pages/verify_email.js +11 -8
  150. package/dist/server_pages/verify_email_client_wrapper.d.ts +7 -6
  151. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  152. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  153. package/dist/strings/default_strings.d.ts +47 -0
  154. package/dist/strings/default_strings.d.ts.map +1 -0
  155. package/dist/strings/default_strings.js +18 -0
  156. package/dist/strings/index.d.ts +4 -0
  157. package/dist/strings/index.d.ts.map +1 -0
  158. package/dist/strings/index.js +3 -0
  159. package/dist/strings/strings_context.d.ts +12 -0
  160. package/dist/strings/strings_context.d.ts.map +1 -0
  161. package/dist/strings/strings_context.js +23 -0
  162. package/dist/strings/strings_provider.d.ts +26 -0
  163. package/dist/strings/strings_provider.d.ts.map +1 -0
  164. package/dist/strings/strings_provider.js +45 -0
  165. package/dist/theme/create_theme.d.ts +7 -0
  166. package/dist/theme/create_theme.d.ts.map +1 -0
  167. package/dist/theme/create_theme.js +97 -0
  168. package/dist/theme/hex_to_hsl.d.ts +16 -0
  169. package/dist/theme/hex_to_hsl.d.ts.map +1 -0
  170. package/dist/theme/hex_to_hsl.js +110 -0
  171. package/dist/theme/index.d.ts +4 -0
  172. package/dist/theme/index.d.ts.map +1 -0
  173. package/dist/theme/index.js +3 -0
  174. package/dist/theme/luminance.d.ts +11 -0
  175. package/dist/theme/luminance.d.ts.map +1 -0
  176. package/dist/theme/luminance.js +45 -0
  177. package/dist/theme/theme_provider.d.ts +14 -0
  178. package/dist/theme/theme_provider.d.ts.map +1 -0
  179. package/dist/theme/theme_provider.js +23 -0
  180. package/dist/theme/theme_types.d.ts +36 -0
  181. package/dist/theme/theme_types.d.ts.map +1 -0
  182. package/dist/theme/theme_types.js +1 -0
  183. package/dist/themes/index.d.ts +3 -0
  184. package/dist/themes/index.d.ts.map +1 -0
  185. package/dist/themes/index.js +2 -0
  186. package/dist/themes/preset_indigo_sunset.d.ts +3 -0
  187. package/dist/themes/preset_indigo_sunset.d.ts.map +1 -0
  188. package/dist/themes/preset_indigo_sunset.js +20 -0
  189. package/dist/themes/preset_neutral.d.ts +3 -0
  190. package/dist/themes/preset_neutral.d.ts.map +1 -0
  191. package/dist/themes/preset_neutral.js +14 -0
  192. package/package.json +19 -2
package/README.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  A reusable authentication UI component package powered by Next.js, TailwindCSS, and shadcn. It integrates `hazo_config` for configuration management and `hazo_connect` for data access, enabling future components to stay aligned with platform conventions.
4
4
 
5
+ ## What's New in v7.0.0
6
+
7
+ **Themes, cookie consent, text overrides, and Facebook OAuth.**
8
+
9
+ ### Highlights
10
+
11
+ - **Pluggable theme system** — `createTheme` + `<HazoAuthThemeProvider>` (zero FOUC, compiles to shadcn CSS variables). Use a preset or build from scratch.
12
+ - **Facebook OAuth** — `<FacebookSignInButton>` and `handle_facebook_oauth_login` service. Strict linking by default (verified accounts only).
13
+ - **Cookie consent banner** — `<CookieConsentBanner>` from `hazo_auth/consent`. GTM optional, 4 categories, theme-driven.
14
+ - **Text override system** — `<HazoAuthStringsProvider>` and per-page `title`/`subtitle`/`ctaText`/`legalText` props. INI text keys removed.
15
+ - **Preset themes** — `preset_neutral` (default look, no config needed) and `preset_indigo_sunset` from `hazo_auth/themes`.
16
+
17
+ ### Breaking Changes Summary
18
+
19
+ - Per-page `imageSrc`/`imageAlt`/`imageBackgroundColor` props removed — use `theme.brandPanel`.
20
+ - INI text keys (`title`, `subtitle`, `cta_text`, `legal_text`) removed from layout sections — use `HazoAuthStringsProvider` or per-page props.
21
+ - Facebook OAuth requires new env vars (`HAZO_AUTH_FACEBOOK_APP_ID`, `HAZO_AUTH_FACEBOOK_APP_SECRET`).
22
+
23
+ > Full upgrade guide: [MIGRATION.md](./MIGRATION.md)
24
+
25
+ ---
26
+
5
27
  ## What's New in v6.1.0
6
28
 
7
29
  **Email-OTP sign-in** — a passwordless, OAuth-free way to sign in via a 6-digit code emailed to the user. Targeted at single-operator deployments that don't want to wire Google OAuth or manage passwords.
@@ -450,23 +472,30 @@ export default function Page() {
450
472
  }
451
473
  ```
452
474
 
453
- **Customizing Visual Appearance (Optional):**
475
+ **Customizing Visual Appearance (v7.0+):**
454
476
 
455
- ```typescript
456
- // All pages accept optional visual props
477
+ Use `HazoAuthThemeProvider` instead of per-page image props (which were removed in v7.0):
478
+
479
+ ```tsx
480
+ import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
457
481
  import { LoginPage } from "hazo_auth/pages/login";
458
482
 
483
+ const theme = createTheme({
484
+ layout: "split",
485
+ brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome.", backgroundGradient: "linear-gradient(135deg, #3730A3, #F59E0B)" },
486
+ });
487
+
459
488
  export default function Page() {
460
489
  return (
461
- <LoginPage
462
- image_src="/custom-login-image.jpg"
463
- image_alt="My company logo"
464
- image_background_color="#f0f0f0"
465
- />
490
+ <HazoAuthThemeProvider theme={theme}>
491
+ <LoginPage theme={theme} />
492
+ </HazoAuthThemeProvider>
466
493
  );
467
494
  }
468
495
  ```
469
496
 
497
+ See the [Theming](#theming-v700) section for all options.
498
+
470
499
  **Embedding MySettings in Your Dashboard:**
471
500
 
472
501
  ```typescript
@@ -656,6 +685,132 @@ The dark class is typically added by next-themes or similar theme providers.
656
685
 
657
686
  ---
658
687
 
688
+ ## Theming (v7.0.0+)
689
+
690
+ hazo_auth v7 ships a pluggable theme system. `<HazoAuthThemeProvider>` is a Server Component that injects CSS variables at `:root` with zero FOUC.
691
+
692
+ ### Option 1: Use a preset directly
693
+
694
+ ```tsx
695
+ import { HazoAuthThemeProvider } from "hazo_auth/theme";
696
+ import { preset_indigo_sunset } from "hazo_auth/themes";
697
+
698
+ // app/layout.tsx
699
+ export default function RootLayout({ children }) {
700
+ return (
701
+ <html>
702
+ <body>
703
+ <HazoAuthThemeProvider theme={preset_indigo_sunset}>
704
+ {children}
705
+ </HazoAuthThemeProvider>
706
+ </body>
707
+ </html>
708
+ );
709
+ }
710
+ ```
711
+
712
+ `preset_neutral` is the default look (slate-700 palette, centered layout). Auth pages render acceptably with no theme configuration at all.
713
+
714
+ ### Option 2: Customize a preset
715
+
716
+ ```tsx
717
+ import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
718
+ import { preset_indigo_sunset } from "hazo_auth/themes";
719
+
720
+ const theme = createTheme({
721
+ ...preset_indigo_sunset,
722
+ brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
723
+ });
724
+
725
+ <HazoAuthThemeProvider theme={theme}>{children}</HazoAuthThemeProvider>
726
+ ```
727
+
728
+ ### Option 3: Build from scratch
729
+
730
+ ```tsx
731
+ import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
732
+
733
+ const theme = createTheme({
734
+ colors: { primary: "#2563EB", primaryForeground: "#ffffff" },
735
+ layout: "split",
736
+ brandPanel: {
737
+ logoSrc: "/logo.svg",
738
+ tagline: "Welcome to MyApp.",
739
+ backgroundGradient: "linear-gradient(135deg, #2563EB, #7C3AED)",
740
+ },
741
+ });
742
+
743
+ <HazoAuthThemeProvider theme={theme}>{children}</HazoAuthThemeProvider>
744
+ ```
745
+
746
+ **Note:** `preset_indigo_sunset` uses `var(--font-display)` and `var(--font-body)`. Wire these in `app/layout.tsx` using `next/font`. See [MIGRATION.md §3](./MIGRATION.md).
747
+
748
+ ---
749
+
750
+ ## Cookie Consent (v7.0.0+)
751
+
752
+ ```tsx
753
+ import { CookieConsentBanner } from "hazo_auth/consent";
754
+
755
+ // In your root layout or a page:
756
+ <CookieConsentBanner />
757
+
758
+ // With GTM:
759
+ <CookieConsentBanner enableGTM gtmContainerId="GTM-XXXX" />
760
+ ```
761
+
762
+ Read consent server-side:
763
+
764
+ ```tsx
765
+ import { read_consent } from "hazo_auth/consent";
766
+
767
+ export async function GET(request: NextRequest) {
768
+ const consent = read_consent(request.headers);
769
+ if (consent.analytics) {
770
+ // Track event
771
+ }
772
+ }
773
+ ```
774
+
775
+ Use the `useConsent` hook client-side:
776
+
777
+ ```tsx
778
+ "use client";
779
+ import { useConsent } from "hazo_auth/consent";
780
+
781
+ export function MyComponent() {
782
+ const { analytics, marketing } = useConsent();
783
+ // Syncs across tabs via custom event
784
+ }
785
+ ```
786
+
787
+ ---
788
+
789
+ ## Strings & Text Overrides (v7.0.0+)
790
+
791
+ INI text keys (`title`, `subtitle`, `cta_text`, `legal_text`) are removed in v7. Use `<HazoAuthStringsProvider>` or per-page props instead.
792
+
793
+ ### Global overrides (app/layout.tsx)
794
+
795
+ ```tsx
796
+ import { HazoAuthStringsProvider } from "hazo_auth/strings";
797
+
798
+ <HazoAuthStringsProvider strings={{ login: { title: "Sign in to MyApp" } }}>
799
+ {children}
800
+ </HazoAuthStringsProvider>
801
+ ```
802
+
803
+ ### Per-page overrides
804
+
805
+ ```tsx
806
+ <LoginPage title="Sign in to MyApp" subtitle="Access your workspace" />
807
+ <RegisterPage title="Create your account" ctaText="Get started" />
808
+ ```
809
+
810
+ `DEFAULT_STRINGS` is exported from `hazo_auth/strings` for reference.
811
+
812
+ ---
813
+
659
814
  ## Configuration Setup
660
815
 
661
816
  After installing the package, you need to set up configuration files in your project root:
@@ -1535,6 +1535,154 @@ npm install input-otp
1535
1535
 
1536
1536
  ---
1537
1537
 
1538
+ ## Phase 5.4: Theme Setup (v7.0.0+)
1539
+
1540
+ ### Step 5.4.1: Mount HazoAuthThemeProvider (Required for themed auth pages)
1541
+
1542
+ Mount `<HazoAuthThemeProvider>` in `app/layout.tsx`. Use `preset_neutral` for the default look, or configure a custom theme.
1543
+
1544
+ **Option A: Default look (no config needed)**
1545
+ ```tsx
1546
+ import { HazoAuthThemeProvider } from "hazo_auth/theme";
1547
+ import { preset_neutral } from "hazo_auth/themes";
1548
+
1549
+ export default function RootLayout({ children }) {
1550
+ return (
1551
+ <html>
1552
+ <body>
1553
+ <HazoAuthThemeProvider theme={preset_neutral}>
1554
+ {children}
1555
+ </HazoAuthThemeProvider>
1556
+ </body>
1557
+ </html>
1558
+ );
1559
+ }
1560
+ ```
1561
+
1562
+ **Option B: Custom theme**
1563
+ ```tsx
1564
+ import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
1565
+
1566
+ const theme = createTheme({
1567
+ colors: { primary: "#2563EB" },
1568
+ layout: "split",
1569
+ brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
1570
+ });
1571
+
1572
+ <HazoAuthThemeProvider theme={theme}>{children}</HazoAuthThemeProvider>
1573
+ ```
1574
+
1575
+ **If using `preset_indigo_sunset` (custom fonts required):**
1576
+
1577
+ Wire `next/font` in `app/layout.tsx` to expose `--font-display` and `--font-body` CSS variables. See [MIGRATION.md §3](./MIGRATION.md).
1578
+
1579
+ ```tsx
1580
+ import { Crimson_Pro, DM_Sans } from "next/font/google";
1581
+ const display = Crimson_Pro({ subsets: ["latin"], variable: "--font-display" });
1582
+ const body = DM_Sans({ subsets: ["latin"], variable: "--font-body" });
1583
+
1584
+ export default function Layout({ children }) {
1585
+ return <html className={`${display.variable} ${body.variable}`}>{children}</html>;
1586
+ }
1587
+ ```
1588
+
1589
+ **Checklist:**
1590
+ - [ ] `<HazoAuthThemeProvider>` mounted in `app/layout.tsx`
1591
+ - [ ] Preset or custom theme configured
1592
+ - [ ] If using `preset_indigo_sunset`: `next/font` wired for `--font-display` and `--font-body`
1593
+
1594
+ ---
1595
+
1596
+ ## Phase 5.5: Facebook OAuth Setup (Optional, v7.0.0+)
1597
+
1598
+ ### Step 5.5.1: Get Facebook App credentials
1599
+
1600
+ 1. Go to [Meta Developers](https://developers.facebook.com/)
1601
+ 2. Create or select an app
1602
+ 3. Add the **Facebook Login** product
1603
+ 4. Set authorized redirect URIs: `http://localhost:3000/api/auth/callback/facebook` (dev) and your production URL
1604
+ 5. Copy **App ID** and **App Secret**
1605
+
1606
+ ### Step 5.5.2: Add Facebook OAuth environment variables
1607
+
1608
+ Add to your `.env.local`:
1609
+ ```env
1610
+ HAZO_AUTH_FACEBOOK_APP_ID=your_app_id
1611
+ HAZO_AUTH_FACEBOOK_APP_SECRET=your_app_secret
1612
+ ```
1613
+
1614
+ The Facebook sign-in button is hidden when these vars are not set.
1615
+
1616
+ ### Step 5.5.3: Enable Facebook in config
1617
+
1618
+ Add to `config/hazo_auth_config.ini`:
1619
+ ```ini
1620
+ [hazo_auth__oauth]
1621
+ enable_facebook = true
1622
+
1623
+ ; Facebook: default false — stricter security posture
1624
+ ; Set true only if you want to auto-link to unverified email accounts
1625
+ auto_link_unverified_accounts_facebook = false
1626
+ ```
1627
+
1628
+ ### Step 5.5.4: Apply Facebook migration
1629
+
1630
+ ```bash
1631
+ npm run migrate migrations/016_facebook_oauth.sql
1632
+ ```
1633
+
1634
+ ### Step 5.5.5: Create Facebook OAuth callback route
1635
+
1636
+ ```typescript
1637
+ // app/api/hazo_auth/oauth/facebook/callback/route.ts
1638
+ export { GET } from "hazo_auth/server/routes/oauth_facebook_callback";
1639
+ ```
1640
+
1641
+ **Facebook OAuth Checklist:**
1642
+ - [ ] Facebook App credentials obtained
1643
+ - [ ] `HAZO_AUTH_FACEBOOK_APP_ID` and `HAZO_AUTH_FACEBOOK_APP_SECRET` set in `.env.local`
1644
+ - [ ] `enable_facebook = true` in `[hazo_auth__oauth]`
1645
+ - [ ] Migration `016_facebook_oauth.sql` applied
1646
+ - [ ] Facebook callback route created
1647
+
1648
+ ---
1649
+
1650
+ ## Phase 5.6: Cookie Consent Setup (Optional, v7.0.0+)
1651
+
1652
+ ### Step 5.6.1: Mount CookieConsentBanner
1653
+
1654
+ Mount `<CookieConsentBanner>` in your root layout:
1655
+
1656
+ ```tsx
1657
+ import { CookieConsentBanner } from "hazo_auth/consent";
1658
+
1659
+ export default function RootLayout({ children }) {
1660
+ return (
1661
+ <html>
1662
+ <body>
1663
+ <HazoAuthThemeProvider theme={preset_neutral}>
1664
+ {children}
1665
+ <CookieConsentBanner />
1666
+ </HazoAuthThemeProvider>
1667
+ </body>
1668
+ </html>
1669
+ );
1670
+ }
1671
+ ```
1672
+
1673
+ **With GTM (optional):**
1674
+ ```tsx
1675
+ <CookieConsentBanner enableGTM gtmContainerId="GTM-XXXX" />
1676
+ ```
1677
+
1678
+ See the README [Cookie Consent](#cookie-consent-v700) section for server-side `read_consent` usage.
1679
+
1680
+ **Checklist:**
1681
+ - [ ] `<CookieConsentBanner>` mounted in root layout
1682
+ - [ ] GTM container ID configured (if using GTM)
1683
+
1684
+ ---
1685
+
1538
1686
  ## Phase 6: Verification Tests
1539
1687
 
1540
1688
  Run these tests to verify your setup is working correctly.
@@ -5,8 +5,10 @@ import type { JWT } from "next-auth/jwt";
5
5
  // ESM/CJS interop: next-auth providers are CommonJS, handle both export scenarios
6
6
  import GoogleProviderImport from "next-auth/providers/google";
7
7
  const GoogleProvider = (GoogleProviderImport as any).default || GoogleProviderImport;
8
+ import FacebookProviderImport from "next-auth/providers/facebook";
9
+ const FacebookProvider = (FacebookProviderImport as any).default || FacebookProviderImport;
8
10
  import { get_oauth_config } from "../oauth_config.server.js";
9
- import { handle_google_oauth_login } from "../services/oauth_service.js";
11
+ import { handle_google_oauth_login, handle_facebook_oauth_login } from "../services/oauth_service.js";
10
12
  import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
11
13
  import { create_app_logger } from "../app_logger.js";
12
14
 
@@ -35,6 +37,13 @@ export type NextAuthCallbackProfile = {
35
37
  email_verified?: boolean;
36
38
  };
37
39
 
40
+ export type FacebookCallbackProfile = {
41
+ id?: string;
42
+ name?: string;
43
+ email?: string;
44
+ picture?: { data?: { url: string } } | string;
45
+ };
46
+
38
47
  // section: config
39
48
  /**
40
49
  * Gets NextAuth.js configuration with enabled OAuth providers
@@ -67,6 +76,21 @@ export function get_nextauth_config(): AuthOptions {
67
76
  }
68
77
  }
69
78
 
79
+ // Add Facebook provider if enabled and credentials are present
80
+ if (oauth_config.enable_facebook && oauth_config.facebook_client_id && oauth_config.facebook_client_secret) {
81
+ providers.push(
82
+ FacebookProvider({
83
+ clientId: oauth_config.facebook_client_id,
84
+ clientSecret: oauth_config.facebook_client_secret,
85
+ authorization: {
86
+ params: {
87
+ scope: "email,public_profile",
88
+ },
89
+ },
90
+ })
91
+ );
92
+ }
93
+
70
94
  return {
71
95
  providers,
72
96
  pages: {
@@ -88,6 +112,9 @@ export function get_nextauth_config(): AuthOptions {
88
112
  if (url.includes("/api/hazo_auth/oauth/google/callback")) {
89
113
  return url;
90
114
  }
115
+ if (url.includes("/api/hazo_auth/oauth/facebook/callback")) {
116
+ return url;
117
+ }
91
118
 
92
119
  // If URL is relative or same origin, allow it
93
120
  if (url.startsWith("/")) {
@@ -162,6 +189,75 @@ export function get_nextauth_config(): AuthOptions {
162
189
  return false;
163
190
  }
164
191
  }
192
+
193
+ if (account?.provider === "facebook" && profile) {
194
+ try {
195
+ const fbProfile = profile as FacebookCallbackProfile;
196
+ const hazoConnect = get_hazo_connect_instance();
197
+ const current_oauth_config = get_oauth_config();
198
+
199
+ // Resolve profile picture URL from Facebook's nested structure
200
+ let fb_picture_url: string | undefined;
201
+ if (fbProfile.picture) {
202
+ if (typeof fbProfile.picture === "string") {
203
+ fb_picture_url = fbProfile.picture;
204
+ } else if (fbProfile.picture?.data?.url) {
205
+ fb_picture_url = fbProfile.picture.data.url;
206
+ }
207
+ }
208
+ if (!fb_picture_url && user.image) {
209
+ fb_picture_url = user.image ?? undefined;
210
+ }
211
+
212
+ logger.info("nextauth_facebook_signin_attempt", {
213
+ email: user.email,
214
+ facebook_id: fbProfile.id,
215
+ name: user.name,
216
+ });
217
+
218
+ const result = await handle_facebook_oauth_login(
219
+ hazoConnect,
220
+ {
221
+ facebook_id: fbProfile.id || account.providerAccountId,
222
+ email: user.email ?? fbProfile.email ?? null,
223
+ name: user.name || fbProfile.name || undefined,
224
+ profile_picture_url: fb_picture_url,
225
+ },
226
+ { auto_link_unverified: current_oauth_config.auto_link_unverified_accounts_facebook }
227
+ );
228
+
229
+ if (!result.success) {
230
+ logger.error("nextauth_facebook_signin_failed", {
231
+ email: user.email,
232
+ error: result.error,
233
+ });
234
+ if (result.error === "link_blocked_unverified") {
235
+ return `/hazo_auth/login?error=link_blocked_unverified`;
236
+ }
237
+ return false;
238
+ }
239
+
240
+ logger.info("nextauth_facebook_signin_success", {
241
+ user_id: result.user_id,
242
+ email: result.email,
243
+ is_new_user: result.is_new_user,
244
+ was_linked: result.was_linked,
245
+ });
246
+
247
+ // Store user_id in account for the JWT callback to pick up
248
+ (account as Record<string, unknown>).hazo_user_id = result.user_id;
249
+
250
+ return true;
251
+ } catch (error) {
252
+ const errorMessage = error instanceof Error ? error.message : "Unknown error";
253
+ logger.error("nextauth_facebook_signin_exception", {
254
+ email: user.email,
255
+ error: errorMessage,
256
+ });
257
+ return false;
258
+ }
259
+ }
260
+
165
261
  return true;
166
262
  },
167
263
  /**
@@ -225,5 +321,9 @@ export function has_oauth_providers(): boolean {
225
321
  if (has_google_credentials) return true;
226
322
  }
227
323
 
324
+ if (oauth_config.enable_facebook && oauth_config.facebook_client_id && oauth_config.facebook_client_secret) {
325
+ return true;
326
+ }
327
+
228
328
  return false;
229
329
  }
@@ -4,12 +4,6 @@ import "server-only";
4
4
 
5
5
  // section: imports
6
6
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
7
- import { get_config_value } from "./config/config_loader.server.js";
8
-
9
- // Default image path - consuming apps should either:
10
- // 1. Configure their own image_src in hazo_auth_config.ini
11
- // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
12
- const DEFAULT_VERIFY_EMAIL_IMAGE_PATH = "/hazo_auth/images/verify_email_default.jpg";
13
7
 
14
8
  // section: types
15
9
  export type EmailVerificationConfig = {
@@ -18,9 +12,6 @@ export type EmailVerificationConfig = {
18
12
  showReturnHomeButton: boolean;
19
13
  returnHomeButtonLabel: string;
20
14
  returnHomePath: string;
21
- imageSrc: string;
22
- imageAlt: string;
23
- imageBackgroundColor: string;
24
15
  };
25
16
 
26
17
  // section: helpers
@@ -30,40 +21,15 @@ export type EmailVerificationConfig = {
30
21
  * @returns Email verification configuration options
31
22
  */
32
23
  export function get_email_verification_config(): EmailVerificationConfig {
33
- const section = "hazo_auth__email_verification_layout";
34
-
35
24
  // Get shared already logged in config
36
25
  const alreadyLoggedInConfig = get_already_logged_in_config();
37
26
 
38
- // Read image configuration
39
- // If not set in config, falls back to default path-based image
40
- // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
41
- const imageSrc = get_config_value(
42
- section,
43
- "image_src",
44
- DEFAULT_VERIFY_EMAIL_IMAGE_PATH
45
- );
46
-
47
- const imageAlt = get_config_value(
48
- section,
49
- "image_alt",
50
- "Email verification illustration"
51
- );
52
- const imageBackgroundColor = get_config_value(
53
- section,
54
- "image_background_color",
55
- "#f1f5f9"
56
- );
57
-
58
27
  return {
59
28
  alreadyLoggedInMessage: alreadyLoggedInConfig.message,
60
29
  showLogoutButton: alreadyLoggedInConfig.showLogoutButton,
61
30
  showReturnHomeButton: alreadyLoggedInConfig.showReturnHomeButton,
62
31
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
63
32
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
64
- imageSrc,
65
- imageAlt,
66
- imageBackgroundColor,
67
33
  };
68
34
  }
69
35
 
@@ -4,12 +4,6 @@ import "server-only";
4
4
 
5
5
  // section: imports
6
6
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
7
- import { get_config_value } from "./config/config_loader.server.js";
8
-
9
- // Default image path - consuming apps should either:
10
- // 1. Configure their own image_src in hazo_auth_config.ini
11
- // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
12
- const DEFAULT_FORGOT_PASSWORD_IMAGE_PATH = "/hazo_auth/images/forgot_password_default.jpg";
13
7
 
14
8
  // section: types
15
9
  export type ForgotPasswordConfig = {
@@ -18,9 +12,6 @@ export type ForgotPasswordConfig = {
18
12
  showReturnHomeButton: boolean;
19
13
  returnHomeButtonLabel: string;
20
14
  returnHomePath: string;
21
- imageSrc: string;
22
- imageAlt: string;
23
- imageBackgroundColor: string;
24
15
  };
25
16
 
26
17
  // section: helpers
@@ -30,40 +21,15 @@ export type ForgotPasswordConfig = {
30
21
  * @returns Forgot password configuration options
31
22
  */
32
23
  export function get_forgot_password_config(): ForgotPasswordConfig {
33
- const section = "hazo_auth__forgot_password_layout";
34
-
35
24
  // Get shared already logged in config
36
25
  const alreadyLoggedInConfig = get_already_logged_in_config();
37
26
 
38
- // Read image configuration
39
- // If not set in config, falls back to default path-based image
40
- // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
41
- const imageSrc = get_config_value(
42
- section,
43
- "image_src",
44
- DEFAULT_FORGOT_PASSWORD_IMAGE_PATH
45
- );
46
-
47
- const imageAlt = get_config_value(
48
- section,
49
- "image_alt",
50
- "Password recovery illustration"
51
- );
52
- const imageBackgroundColor = get_config_value(
53
- section,
54
- "image_background_color",
55
- "#f1f5f9"
56
- );
57
-
58
27
  return {
59
28
  alreadyLoggedInMessage: alreadyLoggedInConfig.message,
60
29
  showLogoutButton: alreadyLoggedInConfig.showLogoutButton,
61
30
  showReturnHomeButton: alreadyLoggedInConfig.showReturnHomeButton,
62
31
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
63
32
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
64
- imageSrc,
65
- imageAlt,
66
- imageBackgroundColor,
67
33
  };
68
34
  }
69
35
 
@@ -7,11 +7,6 @@ import { get_config_value, get_config_value_allow_empty } from "./config/config_
7
7
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
8
8
  import { get_oauth_config, type OAuthConfig } from "./oauth_config.server.js";
9
9
 
10
- // Default image path - consuming apps should either:
11
- // 1. Configure their own image_src in hazo_auth_config.ini
12
- // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
13
- const DEFAULT_LOGIN_IMAGE_PATH = "/hazo_auth/images/login_default.jpg";
14
-
15
10
  // section: types
16
11
  export type LoginConfig = {
17
12
  redirectRoute?: string;
@@ -26,9 +21,6 @@ export type LoginConfig = {
26
21
  createAccountPath: string;
27
22
  createAccountLabel: string;
28
23
  showCreateAccountLink: boolean;
29
- imageSrc: string;
30
- imageAlt: string;
31
- imageBackgroundColor: string;
32
24
  /** OAuth configuration */
33
25
  oauth: OAuthConfig;
34
26
  /** Whether the OTP sign-in link is shown below the login form */
@@ -77,26 +69,6 @@ export function get_login_config(): LoginConfig {
77
69
  // Get shared already logged in config
78
70
  const alreadyLoggedInConfig = get_already_logged_in_config();
79
71
 
80
- // Read image configuration
81
- // If not set in config, falls back to default path-based image
82
- // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
83
- const imageSrc = get_config_value(
84
- section,
85
- "image_src",
86
- DEFAULT_LOGIN_IMAGE_PATH
87
- );
88
-
89
- const imageAlt = get_config_value(
90
- section,
91
- "image_alt",
92
- "Secure login illustration"
93
- );
94
- const imageBackgroundColor = get_config_value(
95
- section,
96
- "image_background_color",
97
- "#f1f5f9"
98
- );
99
-
100
72
  // Get OAuth configuration
101
73
  const oauth = get_oauth_config();
102
74
 
@@ -118,9 +90,6 @@ export function get_login_config(): LoginConfig {
118
90
  createAccountPath,
119
91
  createAccountLabel,
120
92
  showCreateAccountLink,
121
- imageSrc,
122
- imageAlt,
123
- imageBackgroundColor,
124
93
  oauth,
125
94
  otpSigninEnabled,
126
95
  otpSigninLabel,