hazo_auth 7.0.1 → 8.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 +96 -319
- package/SETUP_CHECKLIST.md +59 -248
- package/cli-src/cli/generate.ts +1 -10
- package/cli-src/cli/validate.ts +0 -4
- package/cli-src/lib/auth/auth_types.ts +15 -21
- package/cli-src/lib/auth/hazo_get_auth.server.ts +19 -0
- package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
- package/cli-src/lib/auth/index.ts +2 -2
- package/cli-src/lib/auth/nextauth_config.ts +27 -67
- package/cli-src/lib/auth/with_auth.server.ts +15 -15
- package/cli-src/lib/config/default_config.ts +8 -0
- package/cli-src/lib/cookies_config.server.ts +1 -1
- package/cli-src/lib/email_verification_config.server.ts +34 -0
- package/cli-src/lib/forgot_password_config.server.ts +34 -0
- package/cli-src/lib/legal/legal_docs_config.server.ts +61 -0
- package/cli-src/lib/legal/legal_docs_reader.server.ts +36 -0
- package/cli-src/lib/legal/legal_docs_service.ts +196 -0
- package/cli-src/lib/legal/legal_docs_types.ts +31 -0
- package/cli-src/lib/login_config.server.ts +29 -14
- package/cli-src/lib/my_settings_config.server.ts +3 -0
- package/cli-src/lib/oauth_config.server.ts +31 -57
- package/cli-src/lib/register_config.server.ts +35 -11
- package/cli-src/lib/reset_password_config.server.ts +31 -0
- package/cli-src/lib/services/email_template_manifest.ts +0 -17
- package/cli-src/lib/services/index.ts +2 -8
- package/cli-src/lib/services/oauth_service.ts +74 -128
- package/cli-src/lib/services/otp_service.ts +7 -2
- package/cli-src/lib/services/registration_service.ts +16 -1
- package/cli-src/lib/services/session_token_service.ts +0 -2
- package/config/hazo_auth_config.example.ini +41 -76
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +1 -10
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +0 -4
- package/dist/client.d.ts +1 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +3 -1
- package/dist/components/layouts/create_firm/index.d.ts +8 -4
- 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 +5 -4
- 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 +5 -4
- 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/index.d.ts +1 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +2 -0
- package/dist/components/layouts/legal/index.d.ts +5 -0
- package/dist/components/layouts/legal/index.d.ts.map +1 -0
- package/dist/components/layouts/legal/index.js +4 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.d.ts +7 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_acceptance_gate.js +84 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts +9 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_checkbox_list.js +11 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.d.ts +9 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_combined_view.js +11 -0
- package/dist/components/layouts/legal/legal_doc_drawer.d.ts +8 -0
- package/dist/components/layouts/legal/legal_doc_drawer.d.ts.map +1 -0
- package/dist/components/layouts/legal/legal_doc_drawer.js +55 -0
- package/dist/components/layouts/login/index.d.ts +13 -19
- package/dist/components/layouts/login/index.d.ts.map +1 -1
- package/dist/components/layouts/login/index.js +8 -11
- package/dist/components/layouts/otp/index.d.ts +5 -1
- package/dist/components/layouts/otp/index.d.ts.map +1 -1
- package/dist/components/layouts/otp/index.js +2 -2
- package/dist/components/layouts/register/hooks/use_register_form.d.ts +5 -1
- package/dist/components/layouts/register/hooks/use_register_form.d.ts.map +1 -1
- package/dist/components/layouts/register/hooks/use_register_form.js +25 -10
- package/dist/components/layouts/register/index.d.ts +11 -11
- package/dist/components/layouts/register/index.d.ts.map +1 -1
- package/dist/components/layouts/register/index.js +26 -7
- package/dist/components/layouts/reset_password/index.d.ts +5 -4
- 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 +5 -3
- 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 +2 -6
- package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/facebook_sign_in_button.js +11 -13
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
- package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +6 -3
- 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 +5 -8
- package/dist/components/layouts/shared/index.d.ts +2 -0
- package/dist/components/layouts/shared/index.d.ts.map +1 -1
- package/dist/components/layouts/shared/index.js +1 -0
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +84 -9
- package/dist/components/ui/button.d.ts +1 -1
- package/dist/components/ui/input-otp.d.ts +2 -2
- package/dist/components/ui/sheet.d.ts +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.d.ts +14 -13
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/auth_types.js +0 -10
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +19 -0
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
- package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
- package/dist/lib/auth/index.d.ts +2 -2
- package/dist/lib/auth/index.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.d.ts +0 -10
- package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
- package/dist/lib/auth/nextauth_config.js +23 -52
- package/dist/lib/auth/with_auth.server.d.ts +13 -13
- package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/with_auth.server.js +2 -2
- package/dist/lib/config/default_config.d.ts +16 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +8 -0
- package/dist/lib/cookies_config.server.d.ts +1 -1
- package/dist/lib/cookies_config.server.js +1 -1
- package/dist/lib/email_verification_config.server.d.ts +3 -0
- package/dist/lib/email_verification_config.server.d.ts.map +1 -1
- package/dist/lib/email_verification_config.server.js +15 -0
- package/dist/lib/forgot_password_config.server.d.ts +3 -0
- package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
- package/dist/lib/forgot_password_config.server.js +15 -0
- package/dist/lib/legal/legal_docs_config.server.d.ts +22 -0
- package/dist/lib/legal/legal_docs_config.server.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_config.server.js +52 -0
- package/dist/lib/legal/legal_docs_reader.server.d.ts +15 -0
- package/dist/lib/legal/legal_docs_reader.server.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_reader.server.js +24 -0
- package/dist/lib/legal/legal_docs_service.d.ts +49 -0
- package/dist/lib/legal/legal_docs_service.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_service.js +140 -0
- package/dist/lib/legal/legal_docs_types.d.ts +25 -0
- package/dist/lib/legal/legal_docs_types.d.ts.map +1 -0
- package/dist/lib/legal/legal_docs_types.js +2 -0
- package/dist/lib/login_config.server.d.ts +3 -6
- package/dist/lib/login_config.server.d.ts.map +1 -1
- package/dist/lib/login_config.server.js +11 -7
- package/dist/lib/my_settings_config.server.d.ts +1 -0
- package/dist/lib/my_settings_config.server.d.ts.map +1 -1
- package/dist/lib/my_settings_config.server.js +2 -0
- package/dist/lib/oauth_config.server.d.ts +8 -17
- package/dist/lib/oauth_config.server.d.ts.map +1 -1
- package/dist/lib/oauth_config.server.js +10 -25
- package/dist/lib/register_config.server.d.ts +5 -2
- package/dist/lib/register_config.server.d.ts.map +1 -1
- package/dist/lib/register_config.server.js +15 -4
- package/dist/lib/reset_password_config.server.d.ts +3 -0
- package/dist/lib/reset_password_config.server.d.ts.map +1 -1
- package/dist/lib/reset_password_config.server.js +13 -0
- package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
- package/dist/lib/services/email_template_manifest.js +0 -17
- package/dist/lib/services/index.d.ts +0 -2
- package/dist/lib/services/index.d.ts.map +1 -1
- package/dist/lib/services/index.js +0 -1
- package/dist/lib/services/oauth_service.d.ts +11 -22
- package/dist/lib/services/oauth_service.d.ts.map +1 -1
- package/dist/lib/services/oauth_service.js +63 -96
- package/dist/lib/services/otp_service.d.ts +1 -1
- package/dist/lib/services/otp_service.d.ts.map +1 -1
- package/dist/lib/services/otp_service.js +6 -1
- package/dist/lib/services/registration_service.d.ts +5 -0
- package/dist/lib/services/registration_service.d.ts.map +1 -1
- package/dist/lib/services/registration_service.js +6 -0
- package/dist/lib/services/session_token_service.d.ts +0 -2
- package/dist/lib/services/session_token_service.d.ts.map +1 -1
- package/dist/lib/services/session_token_service.js +0 -2
- package/dist/page_components/create_firm.d.ts +1 -13
- package/dist/page_components/create_firm.d.ts.map +1 -1
- package/dist/page_components/create_firm.js +6 -10
- package/dist/page_components/forgot_password.d.ts +4 -1
- package/dist/page_components/forgot_password.d.ts.map +1 -1
- package/dist/page_components/forgot_password.js +6 -2
- package/dist/page_components/index.d.ts +0 -5
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +0 -5
- package/dist/page_components/login.d.ts +4 -1
- package/dist/page_components/login.d.ts.map +1 -1
- package/dist/page_components/login.js +6 -2
- package/dist/page_components/register.d.ts +4 -1
- package/dist/page_components/register.d.ts.map +1 -1
- package/dist/page_components/register.js +6 -2
- package/dist/page_components/reset_password.d.ts +4 -1
- package/dist/page_components/reset_password.d.ts.map +1 -1
- package/dist/page_components/reset_password.js +6 -2
- package/dist/page_components/verify_email.d.ts +4 -1
- package/dist/page_components/verify_email.d.ts.map +1 -1
- package/dist/page_components/verify_email.js +6 -2
- package/dist/server/routes/assets.d.ts +8 -0
- package/dist/server/routes/assets.d.ts.map +1 -0
- package/dist/server/routes/assets.js +38 -0
- package/dist/server/routes/consent_me.d.ts +4 -0
- package/dist/server/routes/consent_me.d.ts.map +1 -0
- package/dist/server/routes/consent_me.js +15 -0
- package/dist/server/routes/index.d.ts +9 -4
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +13 -5
- package/dist/server/routes/legal_docs_accept.d.ts +3 -0
- package/dist/server/routes/legal_docs_accept.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_accept.js +43 -0
- package/dist/server/routes/legal_docs_get.d.ts +3 -0
- package/dist/server/routes/legal_docs_get.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_get.js +49 -0
- package/dist/server/routes/legal_docs_publish.d.ts +3 -0
- package/dist/server/routes/legal_docs_publish.d.ts.map +1 -0
- package/dist/server/routes/legal_docs_publish.js +35 -0
- package/dist/server/routes/me.d.ts.map +1 -1
- package/dist/server/routes/me.js +1 -43
- package/dist/server/routes/oauth_facebook_callback.d.ts +1 -1
- package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -1
- package/dist/server/routes/oauth_facebook_callback.js +8 -1
- package/dist/server/routes/oauth_google_callback.js +1 -1
- package/dist/server/routes/otp/verify.js +2 -2
- package/dist/server/routes/register.d.ts.map +1 -1
- package/dist/server/routes/register.js +26 -0
- package/dist/server/routes/strings_defaults.d.ts +4 -0
- package/dist/server/routes/strings_defaults.d.ts.map +1 -0
- package/dist/server/routes/strings_defaults.js +7 -0
- package/dist/server/routes/user_management_users.d.ts +11 -0
- package/dist/server/routes/user_management_users.d.ts.map +1 -1
- package/dist/server/routes/user_management_users.js +94 -0
- package/dist/server-lib.d.ts +0 -3
- package/dist/server-lib.d.ts.map +1 -1
- package/dist/server-lib.js +0 -2
- package/dist/server_pages/forgot_password.d.ts +18 -14
- package/dist/server_pages/forgot_password.d.ts.map +1 -1
- package/dist/server_pages/forgot_password.js +14 -12
- package/dist/server_pages/forgot_password_client_wrapper.d.ts +8 -7
- 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/index.d.ts +2 -0
- package/dist/server_pages/index.d.ts.map +1 -1
- package/dist/server_pages/index.js +1 -0
- package/dist/server_pages/login.d.ts +22 -23
- package/dist/server_pages/login.d.ts.map +1 -1
- package/dist/server_pages/login.js +27 -14
- package/dist/server_pages/login_client_wrapper.d.ts +9 -10
- 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 +1 -3
- package/dist/server_pages/my_settings.d.ts.map +1 -1
- package/dist/server_pages/my_settings.js +2 -9
- package/dist/server_pages/register.d.ts +17 -20
- package/dist/server_pages/register.d.ts.map +1 -1
- package/dist/server_pages/register.js +20 -15
- package/dist/server_pages/register_client_wrapper.d.ts +8 -10
- 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 +16 -11
- package/dist/server_pages/reset_password.d.ts.map +1 -1
- package/dist/server_pages/reset_password.js +14 -10
- package/dist/server_pages/reset_password_client_wrapper.d.ts +8 -7
- 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 +18 -12
- package/dist/server_pages/verify_email.d.ts.map +1 -1
- package/dist/server_pages/verify_email.js +13 -11
- package/dist/server_pages/verify_email_client_wrapper.d.ts +8 -7
- 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.d.ts +2 -0
- package/dist/strings.d.ts.map +1 -0
- package/dist/strings.js +3 -0
- package/dist/themes/index.d.ts +0 -1
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +1 -1
- package/package.json +30 -61
- package/dist/themes/preset_indigo_sunset.d.ts +0 -3
- package/dist/themes/preset_indigo_sunset.d.ts.map +0 -1
- package/dist/themes/preset_indigo_sunset.js +0 -20
package/README.md
CHANGED
|
@@ -2,176 +2,40 @@
|
|
|
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
|
-
|
|
5
|
+
### What's New in v8.0.1 🔧
|
|
6
6
|
|
|
7
|
-
**
|
|
7
|
+
**Auto-test and middleware bug fixes**
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Fixed 307 redirects blocking OTP, strings, consent, and legal_docs API routes when hit without auth cookies — all four are now in `middleware.ts` `public_routes`.
|
|
10
|
+
- Auto-test runner register calls now include `legal_accepted` hashes when legal docs are configured, fixing 24 test failures that cascaded from the initial registration step.
|
|
10
11
|
|
|
11
|
-
|
|
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`.
|
|
12
|
+
### What's New in v8.0.0 ⚠️ BREAKING CHANGE
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
**Legal Document Acceptance** — opt-in, INI-configured. Add `[hazo_auth__legal_docs]` to `hazo_auth_config.ini` to require users to accept terms before registering. Each doc is a markdown file; hazo_auth hashes it, tracks acceptance history, and blocks register until all current hashes are accepted.
|
|
18
15
|
|
|
19
|
-
|
|
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`).
|
|
16
|
+
**Breaking:** Deprecated page-component wrappers removed (`LoginPage`, `RegisterPage`, `ForgotPasswordPage`, `ResetPasswordPage`, `VerifyEmailPage` from `hazo_auth/page_components/*`). Use `*Layout` components directly in a server component instead.
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
Run these migrations (in order) to upgrade an existing database:
|
|
19
|
+
- `017_legal_acceptance_column.sql`
|
|
20
|
+
- `018_hazo_legal_acceptances.sql`
|
|
21
|
+
- `019_hazo_legal_doc_versions.sql`
|
|
24
22
|
|
|
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
|
-
|
|
97
|
-
### What's New in v6.0.0 🚨 BREAKING CHANGE
|
|
98
|
-
|
|
99
|
-
**`TenantAuthResult.organization` / `.organization_id` renamed to `.selected_scope` / `.selected_scope_id`.**
|
|
100
|
-
|
|
101
|
-
The multi-tenancy model has been scope-based for several releases — `auth.user_scopes`, `auth.scope_access_via`, `auth.scope_ok`, the `hazo_auth_scope_id` cookie, the `X-Hazo-Scope-Id` header. The only holdouts using the legacy "organization" name were two fields on `TenantAuthResult` that were always just *"the currently selected scope"* under the hood. This release eliminates the inconsistency.
|
|
102
|
-
|
|
103
|
-
> 📖 **Full migration guide:** [MIGRATION.md → v5.x → v6.0](./MIGRATION.md#v5x--v60-organization--selected_scope)
|
|
104
|
-
|
|
105
|
-
**No behavioral change.** `selected_scope_id` is derived from the same scope-selection cookie/header that `organization_id` was. Wire-format auth responses change field names, but the underlying scope lookup is identical.
|
|
106
|
-
|
|
107
|
-
**No deprecation shim.** A clean find-replace is the upgrade path. If you absolutely need a transitional period, pin to `hazo_auth@^5.3` until you can do the rename in one shot.
|
|
108
|
-
|
|
109
|
-
**What to replace (find ↔ replace, all in your app code):**
|
|
110
|
-
|
|
111
|
-
| Find | Replace with |
|
|
112
|
-
| ------------------------------------------------- | -------------------------------------------------- |
|
|
113
|
-
| `auth.organization` | `auth.selected_scope` |
|
|
114
|
-
| `auth.organization_id` | `auth.selected_scope_id` |
|
|
115
|
-
| `import type { TenantOrganization }` | `import type { SelectedScope }` |
|
|
116
|
-
| `: TenantOrganization` | `: SelectedScope` |
|
|
117
|
-
| `AuthenticatedTenantAuthWithOrg` | `AuthenticatedTenantAuthWithSelectedScope` |
|
|
118
|
-
|
|
119
|
-
**Before / after example:**
|
|
120
|
-
|
|
121
|
-
```typescript
|
|
122
|
-
// BEFORE (v5.x)
|
|
123
|
-
import { hazo_get_tenant_auth } from "hazo_auth/server-lib";
|
|
124
|
-
import type { TenantOrganization } from "hazo_auth";
|
|
125
|
-
|
|
126
|
-
export async function GET(request: NextRequest) {
|
|
127
|
-
const auth = await hazo_get_tenant_auth(request);
|
|
128
|
-
if (!auth.authenticated || !auth.organization) {
|
|
129
|
-
return NextResponse.json({ error: "no tenant" }, { status: 403 });
|
|
130
|
-
}
|
|
131
|
-
const data = await getData(auth.organization_id); // or auth.organization.id
|
|
132
|
-
return NextResponse.json({ org: auth.organization, data });
|
|
133
|
-
}
|
|
23
|
+
**New exports** — `LegalAcceptanceGate`, `LegalDocDrawer`, `LegalDocCheckboxList`, `LegalDocCombinedView` from `hazo_auth/client`; `legalDocsGET`, `legalDocsAcceptPOST`, `legalDocsPublishPOST` from `hazo_auth/server/routes`; `LegalDoc`, `LegalAcceptanceRecord`, `LegalAcceptanceMap` types from `hazo_auth`.
|
|
134
24
|
|
|
135
|
-
|
|
136
|
-
import { hazo_get_tenant_auth } from "hazo_auth/server-lib";
|
|
137
|
-
import type { SelectedScope } from "hazo_auth";
|
|
25
|
+
**New dependency** — `react-markdown` (markdown rendering for legal doc drawers).
|
|
138
26
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
**What did NOT change** (same names, same behavior, same wire format):
|
|
150
|
-
- `auth.user_scopes` — array of all scopes the user has access to
|
|
151
|
-
- `auth.scope_ok`, `auth.scope_access_via` — scope-access fields
|
|
152
|
-
- `hazo_auth_scope_id` cookie name and `X-Hazo-Scope-Id` request header
|
|
153
|
-
- All `Tenant*` type and class names: `TenantAuthResult`, `TenantAuthOptions`, `RequiredTenantAuthResult`, `TenantRequiredError`, `TenantAccessDeniedError`, `hazo_get_tenant_auth`, `require_tenant_auth`, `withAuth`'s `require_tenant` option
|
|
154
|
-
- The error response `{ code: "TENANT_REQUIRED" }` shape — only the human-readable `error` string was rephrased ("Organization context required" → "Tenant scope context required")
|
|
155
|
-
|
|
156
|
-
**One-liner upgrade for typical consumers:**
|
|
157
|
-
|
|
158
|
-
```bash
|
|
159
|
-
# Run from your app repo (NOT inside hazo_auth itself).
|
|
160
|
-
# Update the path glob to match your code layout.
|
|
161
|
-
git grep -l "organization\b\|TenantOrganization\|AuthenticatedTenantAuthWithOrg" -- 'src/**' 'app/**' 'lib/**' \
|
|
162
|
-
| xargs sed -i.bak '
|
|
163
|
-
s/auth\.organization_id\b/auth.selected_scope_id/g;
|
|
164
|
-
s/auth\.organization\b/auth.selected_scope/g;
|
|
165
|
-
s/\bTenantOrganization\b/SelectedScope/g;
|
|
166
|
-
s/\bAuthenticatedTenantAuthWithOrg\b/AuthenticatedTenantAuthWithSelectedScope/g;
|
|
167
|
-
'
|
|
168
|
-
# Then: review the diff carefully — sed is blunt. Remove .bak files when satisfied.
|
|
27
|
+
```ini
|
|
28
|
+
# config/hazo_auth_config.ini
|
|
29
|
+
[hazo_auth__legal_docs]
|
|
30
|
+
display_mode = separate # separate | combined
|
|
31
|
+
doc_1_key = tos
|
|
32
|
+
doc_1_title = Terms of Service
|
|
33
|
+
doc_1_path = legal/tos.md
|
|
34
|
+
doc_2_key = privacy
|
|
35
|
+
doc_2_title = Privacy Policy
|
|
36
|
+
doc_2_path = legal/privacy.md
|
|
169
37
|
```
|
|
170
38
|
|
|
171
|
-
> ⚠️ The sed line is a starting point, not a blanket "trust me." It only catches `auth.organization*` and the type names. If you destructure (`const { organization } = auth`), aliased imports (`TenantOrganization as Org`), or reference `organization` for unrelated reasons (HTML autocomplete attribute, your own variables), the script either misses or wrongly rewrites them. **Always diff before committing.**
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
39
|
### What's New in v5.3.1 🔧
|
|
176
40
|
|
|
177
41
|
**`get_client_ip(request)` exported from `hazo_auth/server-lib`** — extracts the client IP from `x-forwarded-for` (first element), falling back to `x-real-ip`, then `"unknown"`. Previously private to `hazo_get_auth.server.ts`. Useful for consumers that need consistent IP extraction across handlers (e.g., `hazo_feedback` audit logging).
|
|
@@ -472,30 +336,23 @@ export default function Page() {
|
|
|
472
336
|
}
|
|
473
337
|
```
|
|
474
338
|
|
|
475
|
-
**Customizing Visual Appearance (
|
|
476
|
-
|
|
477
|
-
Use `HazoAuthThemeProvider` instead of per-page image props (which were removed in v7.0):
|
|
339
|
+
**Customizing Visual Appearance (Optional):**
|
|
478
340
|
|
|
479
|
-
```
|
|
480
|
-
|
|
341
|
+
```typescript
|
|
342
|
+
// All pages accept optional visual props
|
|
481
343
|
import { LoginPage } from "hazo_auth/pages/login";
|
|
482
344
|
|
|
483
|
-
const theme = createTheme({
|
|
484
|
-
layout: "split",
|
|
485
|
-
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome.", backgroundGradient: "linear-gradient(135deg, #3730A3, #F59E0B)" },
|
|
486
|
-
});
|
|
487
|
-
|
|
488
345
|
export default function Page() {
|
|
489
346
|
return (
|
|
490
|
-
<
|
|
491
|
-
|
|
492
|
-
|
|
347
|
+
<LoginPage
|
|
348
|
+
image_src="/custom-login-image.jpg"
|
|
349
|
+
image_alt="My company logo"
|
|
350
|
+
image_background_color="#f0f0f0"
|
|
351
|
+
/>
|
|
493
352
|
);
|
|
494
353
|
}
|
|
495
354
|
```
|
|
496
355
|
|
|
497
|
-
See the [Theming](#theming-v700) section for all options.
|
|
498
|
-
|
|
499
356
|
**Embedding MySettings in Your Dashboard:**
|
|
500
357
|
|
|
501
358
|
```typescript
|
|
@@ -685,132 +542,6 @@ The dark class is typically added by next-themes or similar theme providers.
|
|
|
685
542
|
|
|
686
543
|
---
|
|
687
544
|
|
|
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
|
-
|
|
814
545
|
## Configuration Setup
|
|
815
546
|
|
|
816
547
|
After installing the package, you need to set up configuration files in your project root:
|
|
@@ -1947,7 +1678,7 @@ export async function proxy(request: NextRequest) {
|
|
|
1947
1678
|
|
|
1948
1679
|
#### `hazo_get_tenant_auth` (Recommended for Multi-Tenant Apps)
|
|
1949
1680
|
|
|
1950
|
-
**New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result
|
|
1681
|
+
**New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result with organization information.
|
|
1951
1682
|
|
|
1952
1683
|
**Location:** `src/lib/auth/hazo_get_tenant_auth.server.ts`
|
|
1953
1684
|
|
|
@@ -1981,9 +1712,8 @@ type TenantAuthResult =
|
|
|
1981
1712
|
permissions: string[];
|
|
1982
1713
|
permission_ok: boolean;
|
|
1983
1714
|
missing_permissions?: string[];
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
user_scopes: ScopeDetails[]; // All user's scopes for switching
|
|
1715
|
+
organization: TenantOrganization | null; // NEW: Tenant context
|
|
1716
|
+
user_scopes: ScopeDetails[]; // NEW: All user's scopes for switching
|
|
1987
1717
|
scope_ok: boolean;
|
|
1988
1718
|
}
|
|
1989
1719
|
| {
|
|
@@ -1991,13 +1721,12 @@ type TenantAuthResult =
|
|
|
1991
1721
|
user: null;
|
|
1992
1722
|
permissions: [];
|
|
1993
1723
|
permission_ok: false;
|
|
1994
|
-
|
|
1995
|
-
selected_scope_id: null;
|
|
1724
|
+
organization: null;
|
|
1996
1725
|
user_scopes: [];
|
|
1997
1726
|
scope_ok: false;
|
|
1998
1727
|
};
|
|
1999
1728
|
|
|
2000
|
-
type
|
|
1729
|
+
type TenantOrganization = {
|
|
2001
1730
|
id: string;
|
|
2002
1731
|
name: string;
|
|
2003
1732
|
slug: string | null; // URL-friendly identifier
|
|
@@ -2039,10 +1768,10 @@ export async function GET(request: NextRequest) {
|
|
|
2039
1768
|
return NextResponse.json({ error: "Authentication required" }, { status: 401 });
|
|
2040
1769
|
}
|
|
2041
1770
|
|
|
2042
|
-
if (!auth.
|
|
1771
|
+
if (!auth.organization) {
|
|
2043
1772
|
return NextResponse.json(
|
|
2044
1773
|
{
|
|
2045
|
-
error: "No
|
|
1774
|
+
error: "No organization context",
|
|
2046
1775
|
available_scopes: auth.user_scopes.map(s => ({ id: s.id, name: s.name }))
|
|
2047
1776
|
},
|
|
2048
1777
|
{ status: 403 }
|
|
@@ -2050,10 +1779,10 @@ export async function GET(request: NextRequest) {
|
|
|
2050
1779
|
}
|
|
2051
1780
|
|
|
2052
1781
|
// Access tenant-specific data
|
|
2053
|
-
const data = await getTenantData(auth.
|
|
1782
|
+
const data = await getTenantData(auth.organization.id);
|
|
2054
1783
|
|
|
2055
1784
|
return NextResponse.json({
|
|
2056
|
-
|
|
1785
|
+
organization: auth.organization,
|
|
2057
1786
|
data,
|
|
2058
1787
|
// Include available scopes for UI scope switcher
|
|
2059
1788
|
available_scopes: auth.user_scopes,
|
|
@@ -2071,8 +1800,8 @@ export async function GET(request: NextRequest) {
|
|
|
2071
1800
|
required_permissions: ["view_reports"],
|
|
2072
1801
|
});
|
|
2073
1802
|
|
|
2074
|
-
// auth.
|
|
2075
|
-
const reports = await getReports(auth.
|
|
1803
|
+
// auth.organization is guaranteed non-null here
|
|
1804
|
+
const reports = await getReports(auth.organization.id);
|
|
2076
1805
|
return NextResponse.json({ reports });
|
|
2077
1806
|
} catch (error) {
|
|
2078
1807
|
if (error instanceof HazoAuthError) {
|
|
@@ -2122,16 +1851,16 @@ Helper function that wraps `hazo_get_tenant_auth` and throws typed errors for co
|
|
|
2122
1851
|
- `TenantRequiredError` (403) - No tenant context in request
|
|
2123
1852
|
- `TenantAccessDeniedError` (403) - User lacks access to requested tenant
|
|
2124
1853
|
|
|
2125
|
-
**Returns:** `RequiredTenantAuthResult` with guaranteed non-null `
|
|
1854
|
+
**Returns:** `RequiredTenantAuthResult` with guaranteed non-null `organization`
|
|
2126
1855
|
|
|
2127
1856
|
**Example:**
|
|
2128
1857
|
```typescript
|
|
2129
1858
|
export async function GET(request: NextRequest) {
|
|
2130
1859
|
try {
|
|
2131
|
-
//
|
|
2132
|
-
const {
|
|
1860
|
+
// organization is guaranteed to exist
|
|
1861
|
+
const { organization, user, permissions } = await require_tenant_auth(request);
|
|
2133
1862
|
|
|
2134
|
-
const data = await getData(
|
|
1863
|
+
const data = await getData(organization.id);
|
|
2135
1864
|
return NextResponse.json(data);
|
|
2136
1865
|
} catch (error) {
|
|
2137
1866
|
if (error instanceof HazoAuthError) {
|
|
@@ -2184,10 +1913,10 @@ export const DELETE = withAuth<{ id: string }>(
|
|
|
2184
1913
|
{ required_permissions: ["admin_system"] }
|
|
2185
1914
|
);
|
|
2186
1915
|
|
|
2187
|
-
// With tenant requirement (auth.
|
|
1916
|
+
// With tenant requirement (auth.organization guaranteed non-null)
|
|
2188
1917
|
export const GET = withAuth<{ id: string }>(
|
|
2189
1918
|
async (request, auth, { id }) => {
|
|
2190
|
-
const data = await getData(auth.
|
|
1919
|
+
const data = await getData(auth.organization.id, id);
|
|
2191
1920
|
return NextResponse.json(data);
|
|
2192
1921
|
},
|
|
2193
1922
|
{ require_tenant: true }
|
|
@@ -3283,3 +3012,51 @@ The `package.json` exports field defines the public API:
|
|
|
3283
3012
|
- Use `npx shadcn@latest add <component>` to scaffold new UI primitives.
|
|
3284
3013
|
- Centralize configurable values through `hazo_config`.
|
|
3285
3014
|
- Access backend resources exclusively via `hazo_connect`.
|
|
3015
|
+
|
|
3016
|
+
## Email OTP sign-in
|
|
3017
|
+
|
|
3018
|
+
```tsx
|
|
3019
|
+
// app/(auth)/otp/page.tsx
|
|
3020
|
+
import { OTPPage } from "hazo_auth/pages/otp";
|
|
3021
|
+
export default function Page(props) { return <OTPPage {...props} />; }
|
|
3022
|
+
```
|
|
3023
|
+
|
|
3024
|
+
Enable in config: `[hazo_auth__otp] enable_email_otp = true`. Run `npx hazo_auth migrate migrations/015_email_otp.sql`.
|
|
3025
|
+
|
|
3026
|
+
## Theming
|
|
3027
|
+
|
|
3028
|
+
```tsx
|
|
3029
|
+
import { HazoAuthThemeProvider, createTheme } from "hazo_auth/theme";
|
|
3030
|
+
|
|
3031
|
+
const my_theme = createTheme({
|
|
3032
|
+
colors: { primary: "#1D4ED8" },
|
|
3033
|
+
layout: "split",
|
|
3034
|
+
brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
|
|
3035
|
+
});
|
|
3036
|
+
|
|
3037
|
+
<HazoAuthThemeProvider theme={my_theme}>
|
|
3038
|
+
<LoginPage />
|
|
3039
|
+
</HazoAuthThemeProvider>
|
|
3040
|
+
```
|
|
3041
|
+
|
|
3042
|
+
Theme is auth-page scoped. Does not affect `:root`.
|
|
3043
|
+
|
|
3044
|
+
## Cookie Consent
|
|
3045
|
+
|
|
3046
|
+
```tsx
|
|
3047
|
+
import { CookieConsentBanner } from "hazo_auth/consent";
|
|
3048
|
+
// Place in your root layout:
|
|
3049
|
+
<CookieConsentBanner />
|
|
3050
|
+
```
|
|
3051
|
+
|
|
3052
|
+
Server-side consent check: `GET /api/hazo_auth/consent/me` returns `ConsentState`.
|
|
3053
|
+
|
|
3054
|
+
## String overrides
|
|
3055
|
+
|
|
3056
|
+
```tsx
|
|
3057
|
+
import { HazoAuthStringsProvider } from "hazo_auth/strings";
|
|
3058
|
+
|
|
3059
|
+
<HazoAuthStringsProvider strings={{ login: { title: "Welcome back" } }}>
|
|
3060
|
+
<LoginPage />
|
|
3061
|
+
</HazoAuthStringsProvider>
|
|
3062
|
+
```
|