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.
Files changed (274) hide show
  1. package/README.md +96 -319
  2. package/SETUP_CHECKLIST.md +59 -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 +15 -21
  6. package/cli-src/lib/auth/hazo_get_auth.server.ts +19 -0
  7. package/cli-src/lib/auth/hazo_get_tenant_auth.server.ts +24 -25
  8. package/cli-src/lib/auth/index.ts +2 -2
  9. package/cli-src/lib/auth/nextauth_config.ts +27 -67
  10. package/cli-src/lib/auth/with_auth.server.ts +15 -15
  11. package/cli-src/lib/config/default_config.ts +8 -0
  12. package/cli-src/lib/cookies_config.server.ts +1 -1
  13. package/cli-src/lib/email_verification_config.server.ts +34 -0
  14. package/cli-src/lib/forgot_password_config.server.ts +34 -0
  15. package/cli-src/lib/legal/legal_docs_config.server.ts +61 -0
  16. package/cli-src/lib/legal/legal_docs_reader.server.ts +36 -0
  17. package/cli-src/lib/legal/legal_docs_service.ts +196 -0
  18. package/cli-src/lib/legal/legal_docs_types.ts +31 -0
  19. package/cli-src/lib/login_config.server.ts +29 -14
  20. package/cli-src/lib/my_settings_config.server.ts +3 -0
  21. package/cli-src/lib/oauth_config.server.ts +31 -57
  22. package/cli-src/lib/register_config.server.ts +35 -11
  23. package/cli-src/lib/reset_password_config.server.ts +31 -0
  24. package/cli-src/lib/services/email_template_manifest.ts +0 -17
  25. package/cli-src/lib/services/index.ts +2 -8
  26. package/cli-src/lib/services/oauth_service.ts +74 -128
  27. package/cli-src/lib/services/otp_service.ts +7 -2
  28. package/cli-src/lib/services/registration_service.ts +16 -1
  29. package/cli-src/lib/services/session_token_service.ts +0 -2
  30. package/config/hazo_auth_config.example.ini +41 -76
  31. package/dist/cli/generate.d.ts.map +1 -1
  32. package/dist/cli/generate.js +1 -10
  33. package/dist/cli/validate.d.ts.map +1 -1
  34. package/dist/cli/validate.js +0 -4
  35. package/dist/client.d.ts +1 -2
  36. package/dist/client.d.ts.map +1 -1
  37. package/dist/client.js +3 -1
  38. package/dist/components/layouts/create_firm/index.d.ts +8 -4
  39. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  40. package/dist/components/layouts/create_firm/index.js +3 -3
  41. package/dist/components/layouts/email_verification/index.d.ts +5 -4
  42. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  43. package/dist/components/layouts/email_verification/index.js +4 -4
  44. package/dist/components/layouts/forgot_password/index.d.ts +5 -4
  45. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  46. package/dist/components/layouts/forgot_password/index.js +2 -2
  47. package/dist/components/layouts/index.d.ts +1 -0
  48. package/dist/components/layouts/index.d.ts.map +1 -1
  49. package/dist/components/layouts/index.js +2 -0
  50. package/dist/components/layouts/legal/index.d.ts +5 -0
  51. package/dist/components/layouts/legal/index.d.ts.map +1 -0
  52. package/dist/components/layouts/legal/index.js +4 -0
  53. package/dist/components/layouts/legal/legal_acceptance_gate.d.ts +7 -0
  54. package/dist/components/layouts/legal/legal_acceptance_gate.d.ts.map +1 -0
  55. package/dist/components/layouts/legal/legal_acceptance_gate.js +84 -0
  56. package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts +9 -0
  57. package/dist/components/layouts/legal/legal_doc_checkbox_list.d.ts.map +1 -0
  58. package/dist/components/layouts/legal/legal_doc_checkbox_list.js +11 -0
  59. package/dist/components/layouts/legal/legal_doc_combined_view.d.ts +9 -0
  60. package/dist/components/layouts/legal/legal_doc_combined_view.d.ts.map +1 -0
  61. package/dist/components/layouts/legal/legal_doc_combined_view.js +11 -0
  62. package/dist/components/layouts/legal/legal_doc_drawer.d.ts +8 -0
  63. package/dist/components/layouts/legal/legal_doc_drawer.d.ts.map +1 -0
  64. package/dist/components/layouts/legal/legal_doc_drawer.js +55 -0
  65. package/dist/components/layouts/login/index.d.ts +13 -19
  66. package/dist/components/layouts/login/index.d.ts.map +1 -1
  67. package/dist/components/layouts/login/index.js +8 -11
  68. package/dist/components/layouts/otp/index.d.ts +5 -1
  69. package/dist/components/layouts/otp/index.d.ts.map +1 -1
  70. package/dist/components/layouts/otp/index.js +2 -2
  71. package/dist/components/layouts/register/hooks/use_register_form.d.ts +5 -1
  72. package/dist/components/layouts/register/hooks/use_register_form.d.ts.map +1 -1
  73. package/dist/components/layouts/register/hooks/use_register_form.js +25 -10
  74. package/dist/components/layouts/register/index.d.ts +11 -11
  75. package/dist/components/layouts/register/index.d.ts.map +1 -1
  76. package/dist/components/layouts/register/index.js +26 -7
  77. package/dist/components/layouts/reset_password/index.d.ts +5 -4
  78. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  79. package/dist/components/layouts/reset_password/index.js +5 -5
  80. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +5 -3
  81. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  82. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  83. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +2 -6
  84. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -1
  85. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +11 -13
  86. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  87. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +3 -8
  88. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +6 -3
  89. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  90. package/dist/components/layouts/shared/components/two_column_auth_layout.js +5 -8
  91. package/dist/components/layouts/shared/index.d.ts +2 -0
  92. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  93. package/dist/components/layouts/shared/index.js +1 -0
  94. package/dist/components/layouts/user_management/index.d.ts.map +1 -1
  95. package/dist/components/layouts/user_management/index.js +84 -9
  96. package/dist/components/ui/button.d.ts +1 -1
  97. package/dist/components/ui/input-otp.d.ts +2 -2
  98. package/dist/components/ui/sheet.d.ts +1 -1
  99. package/dist/index.d.ts +2 -1
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/lib/auth/auth_types.d.ts +14 -13
  102. package/dist/lib/auth/auth_types.d.ts.map +1 -1
  103. package/dist/lib/auth/auth_types.js +0 -10
  104. package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
  105. package/dist/lib/auth/hazo_get_auth.server.js +19 -0
  106. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts +7 -8
  107. package/dist/lib/auth/hazo_get_tenant_auth.server.d.ts.map +1 -1
  108. package/dist/lib/auth/hazo_get_tenant_auth.server.js +22 -23
  109. package/dist/lib/auth/index.d.ts +2 -2
  110. package/dist/lib/auth/index.d.ts.map +1 -1
  111. package/dist/lib/auth/nextauth_config.d.ts +0 -10
  112. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  113. package/dist/lib/auth/nextauth_config.js +23 -52
  114. package/dist/lib/auth/with_auth.server.d.ts +13 -13
  115. package/dist/lib/auth/with_auth.server.d.ts.map +1 -1
  116. package/dist/lib/auth/with_auth.server.js +2 -2
  117. package/dist/lib/config/default_config.d.ts +16 -0
  118. package/dist/lib/config/default_config.d.ts.map +1 -1
  119. package/dist/lib/config/default_config.js +8 -0
  120. package/dist/lib/cookies_config.server.d.ts +1 -1
  121. package/dist/lib/cookies_config.server.js +1 -1
  122. package/dist/lib/email_verification_config.server.d.ts +3 -0
  123. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  124. package/dist/lib/email_verification_config.server.js +15 -0
  125. package/dist/lib/forgot_password_config.server.d.ts +3 -0
  126. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  127. package/dist/lib/forgot_password_config.server.js +15 -0
  128. package/dist/lib/legal/legal_docs_config.server.d.ts +22 -0
  129. package/dist/lib/legal/legal_docs_config.server.d.ts.map +1 -0
  130. package/dist/lib/legal/legal_docs_config.server.js +52 -0
  131. package/dist/lib/legal/legal_docs_reader.server.d.ts +15 -0
  132. package/dist/lib/legal/legal_docs_reader.server.d.ts.map +1 -0
  133. package/dist/lib/legal/legal_docs_reader.server.js +24 -0
  134. package/dist/lib/legal/legal_docs_service.d.ts +49 -0
  135. package/dist/lib/legal/legal_docs_service.d.ts.map +1 -0
  136. package/dist/lib/legal/legal_docs_service.js +140 -0
  137. package/dist/lib/legal/legal_docs_types.d.ts +25 -0
  138. package/dist/lib/legal/legal_docs_types.d.ts.map +1 -0
  139. package/dist/lib/legal/legal_docs_types.js +2 -0
  140. package/dist/lib/login_config.server.d.ts +3 -6
  141. package/dist/lib/login_config.server.d.ts.map +1 -1
  142. package/dist/lib/login_config.server.js +11 -7
  143. package/dist/lib/my_settings_config.server.d.ts +1 -0
  144. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  145. package/dist/lib/my_settings_config.server.js +2 -0
  146. package/dist/lib/oauth_config.server.d.ts +8 -17
  147. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  148. package/dist/lib/oauth_config.server.js +10 -25
  149. package/dist/lib/register_config.server.d.ts +5 -2
  150. package/dist/lib/register_config.server.d.ts.map +1 -1
  151. package/dist/lib/register_config.server.js +15 -4
  152. package/dist/lib/reset_password_config.server.d.ts +3 -0
  153. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  154. package/dist/lib/reset_password_config.server.js +13 -0
  155. package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
  156. package/dist/lib/services/email_template_manifest.js +0 -17
  157. package/dist/lib/services/index.d.ts +0 -2
  158. package/dist/lib/services/index.d.ts.map +1 -1
  159. package/dist/lib/services/index.js +0 -1
  160. package/dist/lib/services/oauth_service.d.ts +11 -22
  161. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  162. package/dist/lib/services/oauth_service.js +63 -96
  163. package/dist/lib/services/otp_service.d.ts +1 -1
  164. package/dist/lib/services/otp_service.d.ts.map +1 -1
  165. package/dist/lib/services/otp_service.js +6 -1
  166. package/dist/lib/services/registration_service.d.ts +5 -0
  167. package/dist/lib/services/registration_service.d.ts.map +1 -1
  168. package/dist/lib/services/registration_service.js +6 -0
  169. package/dist/lib/services/session_token_service.d.ts +0 -2
  170. package/dist/lib/services/session_token_service.d.ts.map +1 -1
  171. package/dist/lib/services/session_token_service.js +0 -2
  172. package/dist/page_components/create_firm.d.ts +1 -13
  173. package/dist/page_components/create_firm.d.ts.map +1 -1
  174. package/dist/page_components/create_firm.js +6 -10
  175. package/dist/page_components/forgot_password.d.ts +4 -1
  176. package/dist/page_components/forgot_password.d.ts.map +1 -1
  177. package/dist/page_components/forgot_password.js +6 -2
  178. package/dist/page_components/index.d.ts +0 -5
  179. package/dist/page_components/index.d.ts.map +1 -1
  180. package/dist/page_components/index.js +0 -5
  181. package/dist/page_components/login.d.ts +4 -1
  182. package/dist/page_components/login.d.ts.map +1 -1
  183. package/dist/page_components/login.js +6 -2
  184. package/dist/page_components/register.d.ts +4 -1
  185. package/dist/page_components/register.d.ts.map +1 -1
  186. package/dist/page_components/register.js +6 -2
  187. package/dist/page_components/reset_password.d.ts +4 -1
  188. package/dist/page_components/reset_password.d.ts.map +1 -1
  189. package/dist/page_components/reset_password.js +6 -2
  190. package/dist/page_components/verify_email.d.ts +4 -1
  191. package/dist/page_components/verify_email.d.ts.map +1 -1
  192. package/dist/page_components/verify_email.js +6 -2
  193. package/dist/server/routes/assets.d.ts +8 -0
  194. package/dist/server/routes/assets.d.ts.map +1 -0
  195. package/dist/server/routes/assets.js +38 -0
  196. package/dist/server/routes/consent_me.d.ts +4 -0
  197. package/dist/server/routes/consent_me.d.ts.map +1 -0
  198. package/dist/server/routes/consent_me.js +15 -0
  199. package/dist/server/routes/index.d.ts +9 -4
  200. package/dist/server/routes/index.d.ts.map +1 -1
  201. package/dist/server/routes/index.js +13 -5
  202. package/dist/server/routes/legal_docs_accept.d.ts +3 -0
  203. package/dist/server/routes/legal_docs_accept.d.ts.map +1 -0
  204. package/dist/server/routes/legal_docs_accept.js +43 -0
  205. package/dist/server/routes/legal_docs_get.d.ts +3 -0
  206. package/dist/server/routes/legal_docs_get.d.ts.map +1 -0
  207. package/dist/server/routes/legal_docs_get.js +49 -0
  208. package/dist/server/routes/legal_docs_publish.d.ts +3 -0
  209. package/dist/server/routes/legal_docs_publish.d.ts.map +1 -0
  210. package/dist/server/routes/legal_docs_publish.js +35 -0
  211. package/dist/server/routes/me.d.ts.map +1 -1
  212. package/dist/server/routes/me.js +1 -43
  213. package/dist/server/routes/oauth_facebook_callback.d.ts +1 -1
  214. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -1
  215. package/dist/server/routes/oauth_facebook_callback.js +8 -1
  216. package/dist/server/routes/oauth_google_callback.js +1 -1
  217. package/dist/server/routes/otp/verify.js +2 -2
  218. package/dist/server/routes/register.d.ts.map +1 -1
  219. package/dist/server/routes/register.js +26 -0
  220. package/dist/server/routes/strings_defaults.d.ts +4 -0
  221. package/dist/server/routes/strings_defaults.d.ts.map +1 -0
  222. package/dist/server/routes/strings_defaults.js +7 -0
  223. package/dist/server/routes/user_management_users.d.ts +11 -0
  224. package/dist/server/routes/user_management_users.d.ts.map +1 -1
  225. package/dist/server/routes/user_management_users.js +94 -0
  226. package/dist/server-lib.d.ts +0 -3
  227. package/dist/server-lib.d.ts.map +1 -1
  228. package/dist/server-lib.js +0 -2
  229. package/dist/server_pages/forgot_password.d.ts +18 -14
  230. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  231. package/dist/server_pages/forgot_password.js +14 -12
  232. package/dist/server_pages/forgot_password_client_wrapper.d.ts +8 -7
  233. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  234. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  235. package/dist/server_pages/index.d.ts +2 -0
  236. package/dist/server_pages/index.d.ts.map +1 -1
  237. package/dist/server_pages/index.js +1 -0
  238. package/dist/server_pages/login.d.ts +22 -23
  239. package/dist/server_pages/login.d.ts.map +1 -1
  240. package/dist/server_pages/login.js +27 -14
  241. package/dist/server_pages/login_client_wrapper.d.ts +9 -10
  242. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  243. package/dist/server_pages/login_client_wrapper.js +2 -2
  244. package/dist/server_pages/my_settings.d.ts +1 -3
  245. package/dist/server_pages/my_settings.d.ts.map +1 -1
  246. package/dist/server_pages/my_settings.js +2 -9
  247. package/dist/server_pages/register.d.ts +17 -20
  248. package/dist/server_pages/register.d.ts.map +1 -1
  249. package/dist/server_pages/register.js +20 -15
  250. package/dist/server_pages/register_client_wrapper.d.ts +8 -10
  251. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  252. package/dist/server_pages/register_client_wrapper.js +2 -2
  253. package/dist/server_pages/reset_password.d.ts +16 -11
  254. package/dist/server_pages/reset_password.d.ts.map +1 -1
  255. package/dist/server_pages/reset_password.js +14 -10
  256. package/dist/server_pages/reset_password_client_wrapper.d.ts +8 -7
  257. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  258. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  259. package/dist/server_pages/verify_email.d.ts +18 -12
  260. package/dist/server_pages/verify_email.d.ts.map +1 -1
  261. package/dist/server_pages/verify_email.js +13 -11
  262. package/dist/server_pages/verify_email_client_wrapper.d.ts +8 -7
  263. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  264. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  265. package/dist/strings.d.ts +2 -0
  266. package/dist/strings.d.ts.map +1 -0
  267. package/dist/strings.js +3 -0
  268. package/dist/themes/index.d.ts +0 -1
  269. package/dist/themes/index.d.ts.map +1 -1
  270. package/dist/themes/index.js +1 -1
  271. package/package.json +30 -61
  272. package/dist/themes/preset_indigo_sunset.d.ts +0 -3
  273. package/dist/themes/preset_indigo_sunset.d.ts.map +0 -1
  274. 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
- ## What's New in v7.0.0
5
+ ### What's New in v8.0.1 🔧
6
6
 
7
- **Themes, cookie consent, text overrides, and Facebook OAuth.**
7
+ **Auto-test and middleware bug fixes**
8
8
 
9
- ### Highlights
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
- - **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`.
12
+ ### What's New in v8.0.0 ⚠️ BREAKING CHANGE
16
13
 
17
- ### Breaking Changes Summary
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
- - 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`).
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
- > Full upgrade guide: [MIGRATION.md](./MIGRATION.md)
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
- // AFTER (v6.0)
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
- 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.
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 (v7.0+):**
476
-
477
- Use `HazoAuthThemeProvider` instead of per-page image props (which were removed in v7.0):
339
+ **Customizing Visual Appearance (Optional):**
478
340
 
479
- ```tsx
480
- import { createTheme, HazoAuthThemeProvider } from "hazo_auth/theme";
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
- <HazoAuthThemeProvider theme={theme}>
491
- <LoginPage theme={theme} />
492
- </HazoAuthThemeProvider>
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 including the currently selected scope.
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
- 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
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
- selected_scope: null;
1995
- selected_scope_id: null;
1724
+ organization: null;
1996
1725
  user_scopes: [];
1997
1726
  scope_ok: false;
1998
1727
  };
1999
1728
 
2000
- type SelectedScope = {
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.selected_scope) {
1771
+ if (!auth.organization) {
2043
1772
  return NextResponse.json(
2044
1773
  {
2045
- error: "No tenant scope context",
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.selected_scope.id);
1782
+ const data = await getTenantData(auth.organization.id);
2054
1783
 
2055
1784
  return NextResponse.json({
2056
- selected_scope: auth.selected_scope,
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.selected_scope is guaranteed non-null here
2075
- const reports = await getReports(auth.selected_scope.id);
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 `selected_scope`
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
- // selected_scope is guaranteed to exist
2132
- const { selected_scope, user, permissions } = await require_tenant_auth(request);
1860
+ // organization is guaranteed to exist
1861
+ const { organization, user, permissions } = await require_tenant_auth(request);
2133
1862
 
2134
- const data = await getData(selected_scope.id);
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.selected_scope guaranteed non-null)
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.selected_scope.id, id);
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
+ ```