hazo_auth 6.0.0 → 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.
- package/README.md +233 -8
- package/SETUP_CHECKLIST.md +240 -0
- package/cli-src/cli/validate.ts +4 -0
- package/cli-src/lib/auth/nextauth_config.ts +101 -1
- package/cli-src/lib/cookies_config.server.ts +1 -0
- package/cli-src/lib/email_verification_config.server.ts +0 -34
- package/cli-src/lib/forgot_password_config.server.ts +0 -34
- package/cli-src/lib/login_config.server.ts +14 -31
- package/cli-src/lib/my_settings_config.server.ts +0 -3
- package/cli-src/lib/oauth_config.server.ts +58 -0
- package/cli-src/lib/otp_config.server.ts +91 -0
- package/cli-src/lib/register_config.server.ts +11 -31
- package/cli-src/lib/reset_password_config.server.ts +0 -31
- package/cli-src/lib/services/email_service.ts +3 -1
- package/cli-src/lib/services/email_template_manifest.ts +17 -0
- package/cli-src/lib/services/email_templates/otp_signin_code.html +13 -0
- package/cli-src/lib/services/email_templates/otp_signin_code.txt +5 -0
- package/cli-src/lib/services/index.ts +8 -2
- package/cli-src/lib/services/oauth_service.ts +197 -0
- package/cli-src/lib/services/otp_service.ts +295 -0
- package/cli-src/lib/services/session_token_service.ts +4 -1
- package/config/hazo_auth_config.example.ini +76 -41
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +4 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +1 -0
- package/dist/components/layouts/create_firm/index.d.ts +4 -8
- package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
- package/dist/components/layouts/create_firm/index.js +3 -3
- package/dist/components/layouts/email_verification/index.d.ts +4 -5
- package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
- package/dist/components/layouts/email_verification/index.js +4 -4
- package/dist/components/layouts/forgot_password/index.d.ts +4 -5
- package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
- package/dist/components/layouts/forgot_password/index.js +2 -2
- package/dist/components/layouts/login/index.d.ts +19 -9
- package/dist/components/layouts/login/index.d.ts.map +1 -1
- package/dist/components/layouts/login/index.js +12 -6
- package/dist/components/layouts/otp/index.d.ts +17 -0
- package/dist/components/layouts/otp/index.d.ts.map +1 -0
- package/dist/components/layouts/otp/index.js +16 -0
- package/dist/components/layouts/register/index.d.ts +11 -7
- package/dist/components/layouts/register/index.d.ts.map +1 -1
- package/dist/components/layouts/register/index.js +8 -4
- package/dist/components/layouts/reset_password/index.d.ts +4 -5
- package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
- package/dist/components/layouts/reset_password/index.js +5 -5
- package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +3 -5
- package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +25 -0
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -0
- package/dist/components/layouts/shared/components/facebook_sign_in_button.js +49 -0
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +8 -3
- package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +3 -6
- package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/two_column_auth_layout.js +8 -5
- package/dist/components/otp/OTPRequestForm.d.ts +11 -0
- package/dist/components/otp/OTPRequestForm.d.ts.map +1 -0
- package/dist/components/otp/OTPRequestForm.js +42 -0
- package/dist/components/otp/OTPVerifyForm.d.ts +16 -0
- package/dist/components/otp/OTPVerifyForm.d.ts.map +1 -0
- package/dist/components/otp/OTPVerifyForm.js +75 -0
- package/dist/components/otp/index.d.ts +5 -0
- package/dist/components/otp/index.d.ts.map +1 -0
- package/dist/components/otp/index.js +2 -0
- package/dist/components/ui/input-otp.d.ts +35 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +44 -0
- package/dist/consent/consent_state.d.ts +18 -0
- package/dist/consent/consent_state.d.ts.map +1 -0
- package/dist/consent/consent_state.js +29 -0
- package/dist/consent/cookie_consent_banner.d.ts +11 -0
- package/dist/consent/cookie_consent_banner.d.ts.map +1 -0
- package/dist/consent/cookie_consent_banner.js +40 -0
- package/dist/consent/gtm_mapping.d.ts +13 -0
- package/dist/consent/gtm_mapping.d.ts.map +1 -0
- package/dist/consent/gtm_mapping.js +30 -0
- package/dist/consent/index.d.ts +7 -0
- package/dist/consent/index.d.ts.map +1 -0
- package/dist/consent/index.js +7 -0
- package/dist/consent/manage_modal.d.ts +2 -0
- package/dist/consent/manage_modal.d.ts.map +1 -0
- package/dist/consent/manage_modal.js +33 -0
- package/dist/consent/read_consent.d.ts +15 -0
- package/dist/consent/read_consent.d.ts.map +1 -0
- package/dist/consent/read_consent.js +23 -0
- package/dist/consent/use_consent.d.ts +7 -0
- package/dist/consent/use_consent.d.ts.map +1 -0
- package/dist/consent/use_consent.js +55 -0
- package/dist/lib/auth/nextauth_config.d.ts +10 -0
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +80 -2
- package/dist/lib/cookies_config.server.d.ts +1 -0
- package/dist/lib/cookies_config.server.d.ts.map +1 -1
- package/dist/lib/cookies_config.server.js +1 -0
- package/dist/lib/email_verification_config.server.d.ts +0 -3
- package/dist/lib/email_verification_config.server.d.ts.map +1 -1
- package/dist/lib/email_verification_config.server.js +0 -15
- package/dist/lib/forgot_password_config.server.d.ts +0 -3
- package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
- package/dist/lib/forgot_password_config.server.js +0 -15
- package/dist/lib/login_config.server.d.ts +6 -3
- package/dist/lib/login_config.server.d.ts.map +1 -1
- package/dist/lib/login_config.server.js +7 -13
- package/dist/lib/my_settings_config.server.d.ts +0 -1
- package/dist/lib/my_settings_config.server.d.ts.map +1 -1
- package/dist/lib/my_settings_config.server.js +0 -2
- package/dist/lib/oauth_config.server.d.ts +17 -0
- package/dist/lib/oauth_config.server.d.ts.map +1 -1
- package/dist/lib/oauth_config.server.js +25 -0
- package/dist/lib/otp_config.server.d.ts +49 -0
- package/dist/lib/otp_config.server.d.ts.map +1 -0
- package/dist/lib/otp_config.server.js +48 -0
- package/dist/lib/register_config.server.d.ts +2 -3
- package/dist/lib/register_config.server.d.ts.map +1 -1
- package/dist/lib/register_config.server.js +4 -13
- package/dist/lib/reset_password_config.server.d.ts +0 -3
- package/dist/lib/reset_password_config.server.d.ts.map +1 -1
- package/dist/lib/reset_password_config.server.js +0 -13
- package/dist/lib/services/email_service.d.ts +1 -1
- package/dist/lib/services/email_service.d.ts.map +1 -1
- package/dist/lib/services/email_service.js +2 -0
- package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
- package/dist/lib/services/email_template_manifest.js +17 -0
- package/dist/lib/services/email_templates/otp_signin_code.html +13 -0
- package/dist/lib/services/email_templates/otp_signin_code.txt +5 -0
- package/dist/lib/services/index.d.ts +2 -0
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +1 -0
- package/dist/lib/services/oauth_service.d.ts +24 -0
- package/dist/lib/services/oauth_service.d.ts.map +1 -1
- package/dist/lib/services/oauth_service.js +155 -0
- package/dist/lib/services/otp_service.d.ts +46 -0
- package/dist/lib/services/otp_service.d.ts.map +1 -0
- package/dist/lib/services/otp_service.js +238 -0
- package/dist/lib/services/session_token_service.d.ts +3 -1
- package/dist/lib/services/session_token_service.d.ts.map +1 -1
- package/dist/lib/services/session_token_service.js +4 -2
- package/dist/page_components/create_firm.d.ts +13 -1
- package/dist/page_components/create_firm.d.ts.map +1 -1
- package/dist/page_components/create_firm.js +10 -6
- package/dist/page_components/forgot_password.d.ts +1 -4
- package/dist/page_components/forgot_password.d.ts.map +1 -1
- package/dist/page_components/forgot_password.js +2 -6
- package/dist/page_components/login.d.ts +1 -4
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +2 -6
- package/dist/page_components/otp.d.ts +4 -0
- package/dist/page_components/otp.d.ts.map +1 -0
- package/dist/page_components/otp.js +5 -0
- package/dist/page_components/register.d.ts +1 -4
- package/dist/page_components/register.d.ts.map +1 -1
- package/dist/page_components/register.js +2 -6
- package/dist/page_components/reset_password.d.ts +1 -4
- package/dist/page_components/reset_password.d.ts.map +1 -1
- package/dist/page_components/reset_password.js +2 -6
- package/dist/page_components/verify_email.d.ts +1 -4
- package/dist/page_components/verify_email.d.ts.map +1 -1
- package/dist/page_components/verify_email.js +2 -6
- package/dist/server/routes/index.d.ts +3 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +4 -0
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +43 -1
- package/dist/server/routes/oauth_facebook_callback.d.ts +8 -0
- package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -0
- package/dist/server/routes/oauth_facebook_callback.js +157 -0
- package/dist/server/routes/oauth_google_callback.js +1 -1
- package/dist/server/routes/otp/request.d.ts +3 -0
- package/dist/server/routes/otp/request.d.ts.map +1 -0
- package/dist/server/routes/otp/request.js +33 -0
- package/dist/server/routes/otp/verify.d.ts +3 -0
- package/dist/server/routes/otp/verify.d.ts.map +1 -0
- package/dist/server/routes/otp/verify.js +58 -0
- package/dist/server-lib.d.ts +3 -0
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +2 -0
- package/dist/server_pages/forgot_password.d.ts +13 -17
- package/dist/server_pages/forgot_password.d.ts.map +1 -1
- package/dist/server_pages/forgot_password.js +12 -8
- package/dist/server_pages/forgot_password_client_wrapper.d.ts +7 -6
- package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
- package/dist/server_pages/login.d.ts +22 -21
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +15 -19
- package/dist/server_pages/login_client_wrapper.d.ts +10 -6
- package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/login_client_wrapper.js +2 -2
- package/dist/server_pages/my_settings.d.ts +2 -0
- package/dist/server_pages/my_settings.d.ts.map +1 -1
- package/dist/server_pages/my_settings.js +8 -2
- package/dist/server_pages/otp.d.ts +56 -0
- package/dist/server_pages/otp.d.ts.map +1 -0
- package/dist/server_pages/otp.js +45 -0
- package/dist/server_pages/register.d.ts +19 -16
- package/dist/server_pages/register.d.ts.map +1 -1
- package/dist/server_pages/register.js +15 -12
- package/dist/server_pages/register_client_wrapper.d.ts +10 -6
- package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/register_client_wrapper.js +2 -2
- package/dist/server_pages/reset_password.d.ts +11 -16
- package/dist/server_pages/reset_password.d.ts.map +1 -1
- package/dist/server_pages/reset_password.js +11 -9
- package/dist/server_pages/reset_password_client_wrapper.d.ts +7 -6
- package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/reset_password_client_wrapper.js +2 -2
- package/dist/server_pages/verify_email.d.ts +11 -17
- package/dist/server_pages/verify_email.d.ts.map +1 -1
- package/dist/server_pages/verify_email.js +11 -8
- package/dist/server_pages/verify_email_client_wrapper.d.ts +7 -6
- package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
- package/dist/server_pages/verify_email_client_wrapper.js +2 -2
- package/dist/strings/default_strings.d.ts +47 -0
- package/dist/strings/default_strings.d.ts.map +1 -0
- package/dist/strings/default_strings.js +18 -0
- package/dist/strings/index.d.ts +4 -0
- package/dist/strings/index.d.ts.map +1 -0
- package/dist/strings/index.js +3 -0
- package/dist/strings/strings_context.d.ts +12 -0
- package/dist/strings/strings_context.d.ts.map +1 -0
- package/dist/strings/strings_context.js +23 -0
- package/dist/strings/strings_provider.d.ts +26 -0
- package/dist/strings/strings_provider.d.ts.map +1 -0
- package/dist/strings/strings_provider.js +45 -0
- package/dist/theme/create_theme.d.ts +7 -0
- package/dist/theme/create_theme.d.ts.map +1 -0
- package/dist/theme/create_theme.js +97 -0
- package/dist/theme/hex_to_hsl.d.ts +16 -0
- package/dist/theme/hex_to_hsl.d.ts.map +1 -0
- package/dist/theme/hex_to_hsl.js +110 -0
- package/dist/theme/index.d.ts +4 -0
- package/dist/theme/index.d.ts.map +1 -0
- package/dist/theme/index.js +3 -0
- package/dist/theme/luminance.d.ts +11 -0
- package/dist/theme/luminance.d.ts.map +1 -0
- package/dist/theme/luminance.js +45 -0
- package/dist/theme/theme_provider.d.ts +14 -0
- package/dist/theme/theme_provider.d.ts.map +1 -0
- package/dist/theme/theme_provider.js +23 -0
- package/dist/theme/theme_types.d.ts +36 -0
- package/dist/theme/theme_types.d.ts.map +1 -0
- package/dist/theme/theme_types.js +1 -0
- package/dist/themes/index.d.ts +3 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +2 -0
- package/dist/themes/preset_indigo_sunset.d.ts +3 -0
- package/dist/themes/preset_indigo_sunset.d.ts.map +1 -0
- package/dist/themes/preset_indigo_sunset.js +20 -0
- package/dist/themes/preset_neutral.d.ts +3 -0
- package/dist/themes/preset_neutral.d.ts.map +1 -0
- package/dist/themes/preset_neutral.js +14 -0
- package/package.json +36 -2
package/README.md
CHANGED
|
@@ -2,6 +2,98 @@
|
|
|
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
|
+
|
|
27
|
+
## What's New in v6.1.0
|
|
28
|
+
|
|
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.
|
|
30
|
+
|
|
31
|
+
### Highlights
|
|
32
|
+
|
|
33
|
+
- **New routes** — `POST /api/hazo_auth/otp/request` and `POST /api/hazo_auth/otp/verify`
|
|
34
|
+
- **New components** — `<OTPRequestForm/>` and `<OTPVerifyForm/>` exported from `hazo_auth/client`
|
|
35
|
+
- **New page** — `/hazo_auth/otp` (zero-config) via `hazo_auth/pages/otp`
|
|
36
|
+
- **Sliding 7-day session** — OTP sessions auto-extend on `/me` when within 24h of expiry. OAuth/password sessions keep their existing 30-day fixed behaviour.
|
|
37
|
+
- **Auto-register (opt-in)** — set `otp_auto_register = true` to let unknown emails sign in on first successful /verify
|
|
38
|
+
- **Rate limited** — 3 requests/email/15min, 20 requests/IP/hour; per-code attempts capped at 5
|
|
39
|
+
|
|
40
|
+
### Consumer example
|
|
41
|
+
|
|
42
|
+
Mount the routes:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
// app/api/hazo_auth/otp/request/route.ts
|
|
46
|
+
export { otpRequestPOST as POST } from "hazo_auth/server/routes";
|
|
47
|
+
|
|
48
|
+
// app/api/hazo_auth/otp/verify/route.ts
|
|
49
|
+
export { otpVerifyPOST as POST } from "hazo_auth/server/routes";
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Render the zero-config page:
|
|
53
|
+
|
|
54
|
+
```tsx
|
|
55
|
+
// app/hazo_auth/otp/page.tsx
|
|
56
|
+
export { default } from "hazo_auth/pages/otp";
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Or compose components yourself:
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
"use client";
|
|
63
|
+
import { OTPRequestForm, OTPVerifyForm } from "hazo_auth/client";
|
|
64
|
+
import { useState } from "react";
|
|
65
|
+
|
|
66
|
+
export default function CustomOtp() {
|
|
67
|
+
const [sent_to, set_sent_to] = useState<string | null>(null);
|
|
68
|
+
return sent_to
|
|
69
|
+
? <OTPVerifyForm email={sent_to} redirect_url="/" />
|
|
70
|
+
: <OTPRequestForm on_sent={set_sent_to} />;
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Required setup
|
|
75
|
+
|
|
76
|
+
1. Apply migration: `npm run migrate migrations/015_email_otp.sql`
|
|
77
|
+
2. Add `[hazo_auth__otp]` section to `config/hazo_auth_config.ini` (template in `hazo_auth_config.example.ini`)
|
|
78
|
+
3. `hazo_notify ^3.0.0` (existing peer dep) must be configured to deliver email
|
|
79
|
+
4. New peer dep: `input-otp` (optional; required only if you render `<OTPVerifyForm/>`)
|
|
80
|
+
5. **Add OTP routes to your middleware/proxy `public_routes`** — unauthenticated users land on the OTP page, so the page route and its API routes must bypass auth guards:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
// middleware.ts / proxy.ts (in your consuming app)
|
|
84
|
+
const public_routes = [
|
|
85
|
+
// ... existing entries ...
|
|
86
|
+
"/hazo_auth/otp", // OTP sign-in page (public — users arrive unauthenticated)
|
|
87
|
+
"/api/hazo_auth/otp", // OTP request + verify API routes
|
|
88
|
+
];
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Without this your middleware redirects unauthenticated users away from the sign-in page before they can authenticate.
|
|
92
|
+
|
|
93
|
+
See `MIGRATION.md` "v6.0.x → v6.1" for the full upgrade walkthrough.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
5
97
|
### What's New in v6.0.0 🚨 BREAKING CHANGE
|
|
6
98
|
|
|
7
99
|
**`TenantAuthResult.organization` / `.organization_id` renamed to `.selected_scope` / `.selected_scope_id`.**
|
|
@@ -380,23 +472,30 @@ export default function Page() {
|
|
|
380
472
|
}
|
|
381
473
|
```
|
|
382
474
|
|
|
383
|
-
**Customizing Visual Appearance (
|
|
475
|
+
**Customizing Visual Appearance (v7.0+):**
|
|
384
476
|
|
|
385
|
-
|
|
386
|
-
|
|
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";
|
|
387
481
|
import { LoginPage } from "hazo_auth/pages/login";
|
|
388
482
|
|
|
483
|
+
const theme = createTheme({
|
|
484
|
+
layout: "split",
|
|
485
|
+
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome.", backgroundGradient: "linear-gradient(135deg, #3730A3, #F59E0B)" },
|
|
486
|
+
});
|
|
487
|
+
|
|
389
488
|
export default function Page() {
|
|
390
489
|
return (
|
|
391
|
-
<
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
image_background_color="#f0f0f0"
|
|
395
|
-
/>
|
|
490
|
+
<HazoAuthThemeProvider theme={theme}>
|
|
491
|
+
<LoginPage theme={theme} />
|
|
492
|
+
</HazoAuthThemeProvider>
|
|
396
493
|
);
|
|
397
494
|
}
|
|
398
495
|
```
|
|
399
496
|
|
|
497
|
+
See the [Theming](#theming-v700) section for all options.
|
|
498
|
+
|
|
400
499
|
**Embedding MySettings in Your Dashboard:**
|
|
401
500
|
|
|
402
501
|
```typescript
|
|
@@ -586,6 +685,132 @@ The dark class is typically added by next-themes or similar theme providers.
|
|
|
586
685
|
|
|
587
686
|
---
|
|
588
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
|
+
|
|
589
814
|
## Configuration Setup
|
|
590
815
|
|
|
591
816
|
After installing the package, you need to set up configuration files in your project root:
|
package/SETUP_CHECKLIST.md
CHANGED
|
@@ -1443,6 +1443,246 @@ export { POST } from "hazo_auth/server/routes/pin_login";
|
|
|
1443
1443
|
|
|
1444
1444
|
---
|
|
1445
1445
|
|
|
1446
|
+
## Phase 5.3: Email-OTP Sign-in Setup (Optional)
|
|
1447
|
+
|
|
1448
|
+
Email-OTP sign-in lets users authenticate with a 6-digit code sent to their email — no password or Google OAuth required. Skip this phase if your app uses only password or OAuth authentication.
|
|
1449
|
+
|
|
1450
|
+
### Step 5.3.1: Apply the OTP migration
|
|
1451
|
+
|
|
1452
|
+
```bash
|
|
1453
|
+
npm run migrate migrations/015_email_otp.sql
|
|
1454
|
+
```
|
|
1455
|
+
|
|
1456
|
+
This creates the `hazo_email_otps` table (stores hashed codes with expiry).
|
|
1457
|
+
|
|
1458
|
+
**Verify the table exists:**
|
|
1459
|
+
```bash
|
|
1460
|
+
sqlite3 data/hazo_auth.sqlite ".tables" | tr ' ' '\n' | grep hazo_email_otps
|
|
1461
|
+
# Expected: hazo_email_otps
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
### Step 5.3.2: Add OTP config section
|
|
1465
|
+
|
|
1466
|
+
Add to `config/hazo_auth_config.ini`:
|
|
1467
|
+
|
|
1468
|
+
```ini
|
|
1469
|
+
[hazo_auth__otp]
|
|
1470
|
+
# Whether OTP sign-in is enabled
|
|
1471
|
+
enabled = true
|
|
1472
|
+
|
|
1473
|
+
# Auto-register unknown emails on first successful verify (default: false)
|
|
1474
|
+
otp_auto_register = false
|
|
1475
|
+
|
|
1476
|
+
# Code expiry in minutes (default: 10)
|
|
1477
|
+
code_expiry_minutes = 10
|
|
1478
|
+
```
|
|
1479
|
+
|
|
1480
|
+
### Step 5.3.3: Create OTP API routes
|
|
1481
|
+
|
|
1482
|
+
```bash
|
|
1483
|
+
npx hazo_auth generate-routes
|
|
1484
|
+
```
|
|
1485
|
+
|
|
1486
|
+
Or manually:
|
|
1487
|
+
|
|
1488
|
+
`app/api/hazo_auth/otp/request/route.ts`:
|
|
1489
|
+
```typescript
|
|
1490
|
+
export { otpRequestPOST as POST } from "hazo_auth/server/routes";
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
`app/api/hazo_auth/otp/verify/route.ts`:
|
|
1494
|
+
```typescript
|
|
1495
|
+
export { otpVerifyPOST as POST } from "hazo_auth/server/routes";
|
|
1496
|
+
```
|
|
1497
|
+
|
|
1498
|
+
### Step 5.3.4: Create the OTP sign-in page
|
|
1499
|
+
|
|
1500
|
+
`app/hazo_auth/otp/page.tsx`:
|
|
1501
|
+
```typescript
|
|
1502
|
+
export { default } from "hazo_auth/pages/otp";
|
|
1503
|
+
```
|
|
1504
|
+
|
|
1505
|
+
### Step 5.3.5: Add OTP routes to middleware/proxy public_routes (REQUIRED)
|
|
1506
|
+
|
|
1507
|
+
**This step is critical.** Unauthenticated users arrive at the OTP sign-in page before they have auth cookies. If the OTP routes are not in `public_routes`, your middleware will redirect them to `/login` in a loop.
|
|
1508
|
+
|
|
1509
|
+
Edit your `middleware.ts` or `proxy.ts`:
|
|
1510
|
+
|
|
1511
|
+
```typescript
|
|
1512
|
+
const public_routes = [
|
|
1513
|
+
// ... existing entries ...
|
|
1514
|
+
"/hazo_auth/otp", // OTP sign-in page (public — users arrive unauthenticated)
|
|
1515
|
+
"/api/hazo_auth/otp", // OTP request + verify API routes
|
|
1516
|
+
];
|
|
1517
|
+
```
|
|
1518
|
+
|
|
1519
|
+
### Step 5.3.6: Install optional peer dep (if using OTPVerifyForm)
|
|
1520
|
+
|
|
1521
|
+
If you render `<OTPVerifyForm/>` directly (rather than using the zero-config page), install:
|
|
1522
|
+
|
|
1523
|
+
```bash
|
|
1524
|
+
npm install input-otp
|
|
1525
|
+
```
|
|
1526
|
+
|
|
1527
|
+
**OTP Setup Checklist:**
|
|
1528
|
+
- [ ] `hazo_email_otps` table created (`npm run migrate migrations/015_email_otp.sql`)
|
|
1529
|
+
- [ ] `[hazo_auth__otp]` section added to `hazo_auth_config.ini`
|
|
1530
|
+
- [ ] OTP API routes created (`/api/hazo_auth/otp/request` and `/api/hazo_auth/otp/verify`)
|
|
1531
|
+
- [ ] OTP page created (`/hazo_auth/otp`)
|
|
1532
|
+
- [ ] `/hazo_auth/otp` and `/api/hazo_auth/otp` added to middleware `public_routes`
|
|
1533
|
+
- [ ] `input-otp` installed (only if using `<OTPVerifyForm/>` directly)
|
|
1534
|
+
- [ ] Email delivery configured (`hazo_notify` + valid `ZEPTOMAIL_API_KEY`)
|
|
1535
|
+
|
|
1536
|
+
---
|
|
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
|
+
|
|
1446
1686
|
## Phase 6: Verification Tests
|
|
1447
1687
|
|
|
1448
1688
|
Run these tests to verify your setup is working correctly.
|
package/cli-src/cli/validate.ts
CHANGED
|
@@ -64,6 +64,9 @@ const REQUIRED_API_ROUTES = [
|
|
|
64
64
|
{ path: "api/hazo_auth/user_management/users/roles", method: "GET" },
|
|
65
65
|
{ path: "api/hazo_auth/user_management/users/roles", method: "POST" },
|
|
66
66
|
{ path: "api/hazo_auth/user_management/users/roles", method: "PUT" },
|
|
67
|
+
// OTP routes
|
|
68
|
+
{ path: "api/hazo_auth/otp/request", method: "POST" },
|
|
69
|
+
{ path: "api/hazo_auth/otp/verify", method: "POST" },
|
|
67
70
|
];
|
|
68
71
|
|
|
69
72
|
// section: helpers
|
|
@@ -534,6 +537,7 @@ const REQUIRED_TABLES = [
|
|
|
534
537
|
"hazo_role_permissions",
|
|
535
538
|
"hazo_invitations",
|
|
536
539
|
"hazo_refresh_tokens",
|
|
540
|
+
"hazo_email_otps",
|
|
537
541
|
];
|
|
538
542
|
|
|
539
543
|
const TEXT_ID_TABLES = [
|
|
@@ -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
|
}
|
|
@@ -27,6 +27,7 @@ export const BASE_COOKIE_NAMES = {
|
|
|
27
27
|
USER_ID: "hazo_auth_user_id",
|
|
28
28
|
USER_EMAIL: "hazo_auth_user_email",
|
|
29
29
|
SESSION: "hazo_auth_session",
|
|
30
|
+
SESSION_KIND: "hazo_auth_session_kind", // v6.1: marks OTP-issued sessions so /me can apply sliding expiry
|
|
30
31
|
DEV_LOCK: "hazo_auth_dev_lock",
|
|
31
32
|
SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
|
|
32
33
|
ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback)
|