hazo_auth 7.0.1 → 7.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/README.md +73 -330
  2. package/SETUP_CHECKLIST.md +28 -248
  3. package/cli-src/cli/generate.ts +1 -10
  4. package/cli-src/cli/validate.ts +0 -4
  5. package/cli-src/lib/auth/auth_types.ts +12 -21
  6. package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
  7. package/cli-src/lib/auth/index.ts +2 -2
  8. package/cli-src/lib/auth/nextauth_config.ts +27 -67
  9. package/cli-src/lib/auth/with_auth.server.ts +15 -15
  10. package/cli-src/lib/config/default_config.ts +8 -0
  11. package/cli-src/lib/cookies_config.server.ts +1 -1
  12. package/cli-src/lib/email_verification_config.server.ts +34 -0
  13. package/cli-src/lib/forgot_password_config.server.ts +34 -0
  14. package/cli-src/lib/login_config.server.ts +29 -14
  15. package/cli-src/lib/my_settings_config.server.ts +3 -0
  16. package/cli-src/lib/oauth_config.server.ts +31 -57
  17. package/cli-src/lib/register_config.server.ts +35 -11
  18. package/cli-src/lib/reset_password_config.server.ts +31 -0
  19. package/cli-src/lib/services/email_template_manifest.ts +0 -17
  20. package/cli-src/lib/services/index.ts +2 -8
  21. package/cli-src/lib/services/oauth_service.ts +74 -128
  22. package/cli-src/lib/services/otp_service.ts +7 -2
  23. package/cli-src/lib/services/session_token_service.ts +0 -2
  24. package/config/hazo_auth_config.example.ini +41 -76
  25. package/dist/cli/generate.d.ts.map +1 -1
  26. package/dist/cli/generate.js +1 -10
  27. package/dist/cli/validate.d.ts.map +1 -1
  28. package/dist/cli/validate.js +0 -4
  29. package/dist/client.d.ts +0 -2
  30. package/dist/client.d.ts.map +1 -1
  31. package/dist/client.js +0 -1
  32. package/dist/components/layouts/create_firm/index.d.ts +8 -4
  33. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  34. package/dist/components/layouts/create_firm/index.js +3 -3
  35. package/dist/components/layouts/email_verification/index.d.ts +5 -4
  36. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  37. package/dist/components/layouts/email_verification/index.js +4 -4
  38. package/dist/components/layouts/forgot_password/index.d.ts +5 -4
  39. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  40. package/dist/components/layouts/forgot_password/index.js +2 -2
  41. package/dist/components/layouts/login/index.d.ts +13 -19
  42. package/dist/components/layouts/login/index.d.ts.map +1 -1
  43. package/dist/components/layouts/login/index.js +8 -11
  44. package/dist/components/layouts/otp/index.d.ts +5 -1
  45. package/dist/components/layouts/otp/index.d.ts.map +1 -1
  46. package/dist/components/layouts/otp/index.js +2 -2
  47. package/dist/components/layouts/register/index.d.ts +11 -11
  48. package/dist/components/layouts/register/index.d.ts.map +1 -1
  49. package/dist/components/layouts/register/index.js +6 -7
  50. package/dist/components/layouts/reset_password/index.d.ts +5 -4
  51. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  52. package/dist/components/layouts/reset_password/index.js +5 -5
  53. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +5 -3
  54. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  55. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  56. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +2 -6
  57. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -1
  58. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +11 -13
  59. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  60. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
  61. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +6 -3
  62. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  63. package/dist/components/layouts/shared/components/two_column_auth_layout.js +5 -8
  64. package/dist/components/layouts/shared/index.d.ts +2 -0
  65. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  66. package/dist/components/layouts/shared/index.js +1 -0
  67. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  68. package/dist/components/layouts/user_management/index.js +39 -2
  69. package/dist/index.d.ts +1 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/lib/auth/auth_types.d.ts +12 -13
  72. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  73. package/dist/lib/auth/auth_types.js +0 -8
  74. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
  75. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
  76. package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
  77. package/dist/lib/auth/index.d.ts +2 -2
  78. package/dist/lib/auth/index.d.ts.map +1 -1
  79. package/dist/lib/auth/nextauth_config.d.ts +0 -10
  80. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  81. package/dist/lib/auth/nextauth_config.js +23 -52
  82. package/dist/lib/auth/with_auth.server.d.ts +13 -13
  83. package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
  84. package/dist/lib/auth/with_auth.server.js +2 -2
  85. package/dist/lib/config/default_config.d.ts +16 -0
  86. package/dist/lib/config/default_config.d.ts.map +1 -1
  87. package/dist/lib/config/default_config.js +8 -0
  88. package/dist/lib/cookies_config.server.d.ts +1 -1
  89. package/dist/lib/cookies_config.server.js +1 -1
  90. package/dist/lib/email_verification_config.server.d.ts +3 -0
  91. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  92. package/dist/lib/email_verification_config.server.js +15 -0
  93. package/dist/lib/forgot_password_config.server.d.ts +3 -0
  94. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  95. package/dist/lib/forgot_password_config.server.js +15 -0
  96. package/dist/lib/login_config.server.d.ts +3 -6
  97. package/dist/lib/login_config.server.d.ts.map +1 -1
  98. package/dist/lib/login_config.server.js +11 -7
  99. package/dist/lib/my_settings_config.server.d.ts +1 -0
  100. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  101. package/dist/lib/my_settings_config.server.js +2 -0
  102. package/dist/lib/oauth_config.server.d.ts +8 -17
  103. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  104. package/dist/lib/oauth_config.server.js +10 -25
  105. package/dist/lib/register_config.server.d.ts +5 -2
  106. package/dist/lib/register_config.server.d.ts.map +1 -1
  107. package/dist/lib/register_config.server.js +15 -4
  108. package/dist/lib/reset_password_config.server.d.ts +3 -0
  109. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  110. package/dist/lib/reset_password_config.server.js +13 -0
  111. package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
  112. package/dist/lib/services/email_template_manifest.js +0 -17
  113. package/dist/lib/services/index.d.ts +0 -2
  114. package/dist/lib/services/index.d.ts.map +1 -1
  115. package/dist/lib/services/index.js +0 -1
  116. package/dist/lib/services/oauth_service.d.ts +11 -22
  117. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  118. package/dist/lib/services/oauth_service.js +63 -96
  119. package/dist/lib/services/otp_service.d.ts +1 -1
  120. package/dist/lib/services/otp_service.d.ts.map +1 -1
  121. package/dist/lib/services/otp_service.js +6 -1
  122. package/dist/lib/services/session_token_service.d.ts +0 -2
  123. package/dist/lib/services/session_token_service.d.ts.map +1 -1
  124. package/dist/lib/services/session_token_service.js +0 -2
  125. package/dist/page_components/create_firm.d.ts +1 -13
  126. package/dist/page_components/create_firm.d.ts.map +1 -1
  127. package/dist/page_components/create_firm.js +6 -10
  128. package/dist/page_components/forgot_password.d.ts +4 -1
  129. package/dist/page_components/forgot_password.d.ts.map +1 -1
  130. package/dist/page_components/forgot_password.js +6 -2
  131. package/dist/page_components/login.d.ts +4 -1
  132. package/dist/page_components/login.d.ts.map +1 -1
  133. package/dist/page_components/login.js +6 -2
  134. package/dist/page_components/register.d.ts +4 -1
  135. package/dist/page_components/register.d.ts.map +1 -1
  136. package/dist/page_components/register.js +6 -2
  137. package/dist/page_components/reset_password.d.ts +4 -1
  138. package/dist/page_components/reset_password.d.ts.map +1 -1
  139. package/dist/page_components/reset_password.js +6 -2
  140. package/dist/page_components/verify_email.d.ts +4 -1
  141. package/dist/page_components/verify_email.d.ts.map +1 -1
  142. package/dist/page_components/verify_email.js +6 -2
  143. package/dist/server/routes/assets.d.ts +8 -0
  144. package/dist/server/routes/assets.d.ts.map +1 -0
  145. package/dist/server/routes/assets.js +38 -0
  146. package/dist/server/routes/consent_me.d.ts +4 -0
  147. package/dist/server/routes/consent_me.d.ts.map +1 -0
  148. package/dist/server/routes/consent_me.js +15 -0
  149. package/dist/server/routes/index.d.ts +6 -4
  150. package/dist/server/routes/index.d.ts.map +1 -1
  151. package/dist/server/routes/index.js +9 -5
  152. package/dist/server/routes/me.d.ts.map +1 -1
  153. package/dist/server/routes/me.js +1 -43
  154. package/dist/server/routes/oauth_facebook_callback.d.ts +1 -1
  155. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -1
  156. package/dist/server/routes/oauth_facebook_callback.js +8 -1
  157. package/dist/server/routes/oauth_google_callback.js +1 -1
  158. package/dist/server/routes/otp/verify.js +2 -2
  159. package/dist/server/routes/strings_defaults.d.ts +4 -0
  160. package/dist/server/routes/strings_defaults.d.ts.map +1 -0
  161. package/dist/server/routes/strings_defaults.js +7 -0
  162. package/dist/server/routes/user_management_users.d.ts +11 -0
  163. package/dist/server/routes/user_management_users.d.ts.map +1 -1
  164. package/dist/server/routes/user_management_users.js +50 -0
  165. package/dist/server-lib.d.ts +0 -3
  166. package/dist/server-lib.d.ts.map +1 -1
  167. package/dist/server-lib.js +0 -2
  168. package/dist/server_pages/forgot_password.d.ts +18 -14
  169. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  170. package/dist/server_pages/forgot_password.js +14 -12
  171. package/dist/server_pages/forgot_password_client_wrapper.d.ts +8 -7
  172. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  173. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  174. package/dist/server_pages/index.d.ts +2 -0
  175. package/dist/server_pages/index.d.ts.map +1 -1
  176. package/dist/server_pages/index.js +1 -0
  177. package/dist/server_pages/login.d.ts +22 -23
  178. package/dist/server_pages/login.d.ts.map +1 -1
  179. package/dist/server_pages/login.js +27 -14
  180. package/dist/server_pages/login_client_wrapper.d.ts +9 -10
  181. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  182. package/dist/server_pages/login_client_wrapper.js +2 -2
  183. package/dist/server_pages/my_settings.d.ts +1 -3
  184. package/dist/server_pages/my_settings.d.ts.map +1 -1
  185. package/dist/server_pages/my_settings.js +2 -9
  186. package/dist/server_pages/register.d.ts +17 -20
  187. package/dist/server_pages/register.d.ts.map +1 -1
  188. package/dist/server_pages/register.js +20 -15
  189. package/dist/server_pages/register_client_wrapper.d.ts +8 -10
  190. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  191. package/dist/server_pages/register_client_wrapper.js +2 -2
  192. package/dist/server_pages/reset_password.d.ts +16 -11
  193. package/dist/server_pages/reset_password.d.ts.map +1 -1
  194. package/dist/server_pages/reset_password.js +14 -10
  195. package/dist/server_pages/reset_password_client_wrapper.d.ts +8 -7
  196. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  197. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  198. package/dist/server_pages/verify_email.d.ts +18 -12
  199. package/dist/server_pages/verify_email.d.ts.map +1 -1
  200. package/dist/server_pages/verify_email.js +13 -11
  201. package/dist/server_pages/verify_email_client_wrapper.d.ts +8 -7
  202. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  203. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  204. package/dist/themes/index.d.ts +0 -1
  205. package/dist/themes/index.d.ts.map +1 -1
  206. package/dist/themes/index.js +1 -1
  207. package/package.json +26 -40
  208. package/dist/themes/preset_indigo_sunset.d.ts +0 -3
  209. package/dist/themes/preset_indigo_sunset.d.ts.map +0 -1
  210. package/dist/themes/preset_indigo_sunset.js +0 -20
package/README.md CHANGED
@@ -2,176 +2,6 @@
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
-
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
- }
134
-
135
- // AFTER (v6.0)
136
- import { hazo_get_tenant_auth } from "hazo_auth/server-lib";
137
- import type { SelectedScope } from "hazo_auth";
138
-
139
- export async function GET(request: NextRequest) {
140
- const auth = await hazo_get_tenant_auth(request);
141
- if (!auth.authenticated || !auth.selected_scope) {
142
- return NextResponse.json({ error: "no tenant scope" }, { status: 403 });
143
- }
144
- const data = await getData(auth.selected_scope_id); // or auth.selected_scope.id
145
- return NextResponse.json({ selected_scope: auth.selected_scope, data });
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.
169
- ```
170
-
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
5
  ### What's New in v5.3.1 🔧
176
6
 
177
7
  **`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 +302,23 @@ export default function Page() {
472
302
  }
473
303
  ```
474
304
 
475
- **Customizing Visual Appearance (v7.0+):**
476
-
477
- Use `HazoAuthThemeProvider` instead of per-page image props (which were removed in v7.0):
305
+ **Customizing Visual Appearance (Optional):**
478
306
 
479
- ```tsx
480
- import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
307
+ ```typescript
308
+ // All pages accept optional visual props
481
309
  import { LoginPage } from "hazo_auth/pages/login";
482
310
 
483
- const theme = createTheme({
484
- layout: "split",
485
- brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome.", backgroundGradient: "linear-gradient(135deg, #3730A3, #F59E0B)" },
486
- });
487
-
488
311
  export default function Page() {
489
312
  return (
490
- <HazoAuthThemeProvider theme={theme}>
491
- <LoginPage theme={theme} />
492
- </HazoAuthThemeProvider>
313
+ <LoginPage
314
+ image_src="/custom-login-image.jpg"
315
+ image_alt="My company logo"
316
+ image_background_color="#f0f0f0"
317
+ />
493
318
  );
494
319
  }
495
320
  ```
496
321
 
497
- See the [Theming](#theming-v700) section for all options.
498
-
499
322
  **Embedding MySettings in Your Dashboard:**
500
323
 
501
324
  ```typescript
@@ -685,132 +508,6 @@ The dark class is typically added by next-themes or similar theme providers.
685
508
 
686
509
  ---
687
510
 
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
511
  ## Configuration Setup
815
512
 
816
513
  After installing the package, you need to set up configuration files in your project root:
@@ -1947,7 +1644,7 @@ export async function proxy(request: NextRequest) {
1947
1644
 
1948
1645
  #### `hazo_get_tenant_auth` (Recommended for Multi-Tenant Apps)
1949
1646
 
1950
- **New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result including the currently selected scope.
1647
+ **New:** Tenant-aware authentication function that extracts scope context from request headers or cookies and returns enriched result with organization information.
1951
1648
 
1952
1649
  **Location:** `src/lib/auth/hazo_get_tenant_auth.server.ts`
1953
1650
 
@@ -1981,9 +1678,8 @@ type TenantAuthResult =
1981
1678
  permissions: string[];
1982
1679
  permission_ok: boolean;
1983
1680
  missing_permissions?: string[];
1984
- selected_scope: SelectedScope | null; // Currently selected tenant/scope
1985
- selected_scope_id: string | null; // Shorthand for selected_scope?.id
1986
- user_scopes: ScopeDetails[]; // All user's scopes for switching
1681
+ organization: TenantOrganization | null; // NEW: Tenant context
1682
+ user_scopes: ScopeDetails[]; // NEW: All user's scopes for switching
1987
1683
  scope_ok: boolean;
1988
1684
  }
1989
1685
  | {
@@ -1991,13 +1687,12 @@ type TenantAuthResult =
1991
1687
  user: null;
1992
1688
  permissions: [];
1993
1689
  permission_ok: false;
1994
- selected_scope: null;
1995
- selected_scope_id: null;
1690
+ organization: null;
1996
1691
  user_scopes: [];
1997
1692
  scope_ok: false;
1998
1693
  };
1999
1694
 
2000
- type SelectedScope = {
1695
+ type TenantOrganization = {
2001
1696
  id: string;
2002
1697
  name: string;
2003
1698
  slug: string | null; // URL-friendly identifier
@@ -2039,10 +1734,10 @@ export async function GET(request: NextRequest) {
2039
1734
  return NextResponse.json({ error: "Authentication required" }, { status: 401 });
2040
1735
  }
2041
1736
 
2042
- if (!auth.selected_scope) {
1737
+ if (!auth.organization) {
2043
1738
  return NextResponse.json(
2044
1739
  {
2045
- error: "No tenant scope context",
1740
+ error: "No organization context",
2046
1741
  available_scopes: auth.user_scopes.map(s => ({ id: s.id, name: s.name }))
2047
1742
  },
2048
1743
  { status: 403 }
@@ -2050,10 +1745,10 @@ export async function GET(request: NextRequest) {
2050
1745
  }
2051
1746
 
2052
1747
  // Access tenant-specific data
2053
- const data = await getTenantData(auth.selected_scope.id);
1748
+ const data = await getTenantData(auth.organization.id);
2054
1749
 
2055
1750
  return NextResponse.json({
2056
- selected_scope: auth.selected_scope,
1751
+ organization: auth.organization,
2057
1752
  data,
2058
1753
  // Include available scopes for UI scope switcher
2059
1754
  available_scopes: auth.user_scopes,
@@ -2071,8 +1766,8 @@ export async function GET(request: NextRequest) {
2071
1766
  required_permissions: ["view_reports"],
2072
1767
  });
2073
1768
 
2074
- // auth.selected_scope is guaranteed non-null here
2075
- const reports = await getReports(auth.selected_scope.id);
1769
+ // auth.organization is guaranteed non-null here
1770
+ const reports = await getReports(auth.organization.id);
2076
1771
  return NextResponse.json({ reports });
2077
1772
  } catch (error) {
2078
1773
  if (error instanceof HazoAuthError) {
@@ -2122,16 +1817,16 @@ Helper function that wraps `hazo_get_tenant_auth` and throws typed errors for co
2122
1817
  - `TenantRequiredError` (403) - No tenant context in request
2123
1818
  - `TenantAccessDeniedError` (403) - User lacks access to requested tenant
2124
1819
 
2125
- **Returns:** `RequiredTenantAuthResult` with guaranteed non-null `selected_scope`
1820
+ **Returns:** `RequiredTenantAuthResult` with guaranteed non-null `organization`
2126
1821
 
2127
1822
  **Example:**
2128
1823
  ```typescript
2129
1824
  export async function GET(request: NextRequest) {
2130
1825
  try {
2131
- // selected_scope is guaranteed to exist
2132
- const { selected_scope, user, permissions } = await require_tenant_auth(request);
1826
+ // organization is guaranteed to exist
1827
+ const { organization, user, permissions } = await require_tenant_auth(request);
2133
1828
 
2134
- const data = await getData(selected_scope.id);
1829
+ const data = await getData(organization.id);
2135
1830
  return NextResponse.json(data);
2136
1831
  } catch (error) {
2137
1832
  if (error instanceof HazoAuthError) {
@@ -2184,10 +1879,10 @@ export const DELETE = withAuth<{ id: string }>(
2184
1879
  { required_permissions: ["admin_system"] }
2185
1880
  );
2186
1881
 
2187
- // With tenant requirement (auth.selected_scope guaranteed non-null)
1882
+ // With tenant requirement (auth.organization guaranteed non-null)
2188
1883
  export const GET = withAuth<{ id: string }>(
2189
1884
  async (request, auth, { id }) => {
2190
- const data = await getData(auth.selected_scope.id, id);
1885
+ const data = await getData(auth.organization.id, id);
2191
1886
  return NextResponse.json(data);
2192
1887
  },
2193
1888
  { require_tenant: true }
@@ -3283,3 +2978,51 @@ The `package.json` exports field defines the public API:
3283
2978
  - Use `npx shadcn@latest add <component>` to scaffold new UI primitives.
3284
2979
  - Centralize configurable values through `hazo_config`.
3285
2980
  - Access backend resources exclusively via `hazo_connect`.
2981
+
2982
+ ## Email OTP sign-in
2983
+
2984
+ ```tsx
2985
+ // app/(auth)/otp/page.tsx
2986
+ import { OTPPage } from "hazo_auth/pages/otp";
2987
+ export default function Page(props) { return <OTPPage {...props} />; }
2988
+ ```
2989
+
2990
+ Enable in config: `[hazo_auth__otp] enable_email_otp = true`. Run `npx hazo_auth migrate migrations/015_email_otp.sql`.
2991
+
2992
+ ## Theming
2993
+
2994
+ ```tsx
2995
+ import { HazoAuthThemeProvider, createTheme } from "hazo_auth/theme";
2996
+
2997
+ const my_theme = createTheme({
2998
+ colors: { primary: "#1D4ED8" },
2999
+ layout: "split",
3000
+ brandPanel: { logoSrc: "/logo.svg", tagline: "Welcome." },
3001
+ });
3002
+
3003
+ <HazoAuthThemeProvider theme={my_theme}>
3004
+ <LoginPage />
3005
+ </HazoAuthThemeProvider>
3006
+ ```
3007
+
3008
+ Theme is auth-page scoped. Does not affect `:root`.
3009
+
3010
+ ## Cookie Consent
3011
+
3012
+ ```tsx
3013
+ import { CookieConsentBanner } from "hazo_auth/consent";
3014
+ // Place in your root layout:
3015
+ <CookieConsentBanner />
3016
+ ```
3017
+
3018
+ Server-side consent check: `GET /api/hazo_auth/consent/me` returns `ConsentState`.
3019
+
3020
+ ## String overrides
3021
+
3022
+ ```tsx
3023
+ import { HazoAuthStringsProvider } from "hazo_auth/strings";
3024
+
3025
+ <HazoAuthStringsProvider strings={{ login: { title: "Welcome back" } }}>
3026
+ <LoginPage />
3027
+ </HazoAuthStringsProvider>
3028
+ ```