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
@@ -8,7 +8,7 @@ const GoogleProvider = (GoogleProviderImport as any).default || GoogleProviderIm
8
8
  import FacebookProviderImport from "next-auth/providers/facebook";
9
9
  const FacebookProvider = (FacebookProviderImport as any).default || FacebookProviderImport;
10
10
  import { get_oauth_config } from "../oauth_config.server.js";
11
- import { handle_google_oauth_login, handle_facebook_oauth_login } from "../services/oauth_service.js";
11
+ import { handle_google_oauth_login } from "../services/oauth_service.js";
12
12
  import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
13
13
  import { create_app_logger } from "../app_logger.js";
14
14
 
@@ -37,13 +37,6 @@ export type NextAuthCallbackProfile = {
37
37
  email_verified?: boolean;
38
38
  };
39
39
 
40
- export type FacebookCallbackProfile = {
41
- id?: string;
42
- name?: string;
43
- email?: string;
44
- picture?: { data?: { url: string } } | string;
45
- };
46
-
47
40
  // section: config
48
41
  /**
49
42
  * Gets NextAuth.js configuration with enabled OAuth providers
@@ -76,17 +69,12 @@ export function get_nextauth_config(): AuthOptions {
76
69
  }
77
70
  }
78
71
 
79
- // Add Facebook provider if enabled and credentials are present
80
- if (oauth_config.enable_facebook && oauth_config.facebook_client_id && oauth_config.facebook_client_secret) {
72
+ // Add Facebook provider if enabled
73
+ if (oauth_config.enable_facebook_oauth && oauth_config.facebook_app_id) {
81
74
  providers.push(
82
75
  FacebookProvider({
83
- clientId: oauth_config.facebook_client_id,
84
- clientSecret: oauth_config.facebook_client_secret,
85
- authorization: {
86
- params: {
87
- scope: "email,public_profile",
88
- },
89
- },
76
+ clientId: oauth_config.facebook_app_id,
77
+ clientSecret: oauth_config.facebook_app_secret,
90
78
  })
91
79
  );
92
80
  }
@@ -109,10 +97,10 @@ export function get_nextauth_config(): AuthOptions {
109
97
 
110
98
  // Always redirect to our custom callback after sign-in to set hazo_auth cookies
111
99
  // The callbackUrl from signIn() comes through as `url`
112
- if (url.includes("/api/hazo_auth/oauth/google/callback")) {
113
- return url;
114
- }
115
- if (url.includes("/api/hazo_auth/oauth/facebook/callback")) {
100
+ if (
101
+ url.includes("/api/hazo_auth/oauth/google/callback") ||
102
+ url.includes("/api/hazo_auth/oauth/facebook/callback")
103
+ ) {
116
104
  return url;
117
105
  }
118
106
 
@@ -125,6 +113,9 @@ export function get_nextauth_config(): AuthOptions {
125
113
  }
126
114
 
127
115
  // Default: redirect to our custom OAuth callback to set cookies
116
+ if (url.includes("facebook")) {
117
+ return `${baseUrl}/api/hazo_auth/oauth/facebook/callback`;
118
+ }
128
119
  return `${baseUrl}/api/hazo_auth/oauth/google/callback`;
129
120
  },
130
121
  /**
@@ -192,48 +183,27 @@ export function get_nextauth_config(): AuthOptions {
192
183
 
193
184
  if (account?.provider === "facebook" && profile) {
194
185
  try {
195
- const fbProfile = profile as FacebookCallbackProfile;
186
+ const fbProfile = profile as NextAuthCallbackProfile;
196
187
  const hazoConnect = get_hazo_connect_instance();
197
- const current_oauth_config = get_oauth_config();
198
-
199
- // Resolve profile picture URL from Facebook's nested structure
200
- let fb_picture_url: string | undefined;
201
- if (fbProfile.picture) {
202
- if (typeof fbProfile.picture === "string") {
203
- fb_picture_url = fbProfile.picture;
204
- } else if (fbProfile.picture?.data?.url) {
205
- fb_picture_url = fbProfile.picture.data.url;
206
- }
207
- }
208
- if (!fb_picture_url && user.image) {
209
- fb_picture_url = user.image ?? undefined;
210
- }
188
+ const { handle_facebook_oauth_login } = await import("../services/oauth_service");
211
189
 
212
190
  logger.info("nextauth_facebook_signin_attempt", {
213
191
  email: user.email,
214
- facebook_id: fbProfile.id,
215
- name: user.name,
192
+ facebook_id: account.providerAccountId,
216
193
  });
217
194
 
218
- const result = await handle_facebook_oauth_login(
219
- hazoConnect,
220
- {
221
- facebook_id: fbProfile.id || account.providerAccountId,
222
- email: user.email ?? fbProfile.email ?? null,
223
- name: user.name || fbProfile.name || undefined,
224
- profile_picture_url: fb_picture_url,
225
- },
226
- { auto_link_unverified: current_oauth_config.auto_link_unverified_accounts_facebook }
227
- );
195
+ const result = await handle_facebook_oauth_login(hazoConnect, {
196
+ facebook_id: account.providerAccountId,
197
+ email: user.email || fbProfile.email || "",
198
+ name: user.name || fbProfile.name || undefined,
199
+ profile_picture_url: user.image || undefined,
200
+ // Facebook's email_verified is not exposed in the profile; default to false
201
+ // for safety — the user will be auto-verified if email matches a verified hazo user.
202
+ email_verified: false,
203
+ });
228
204
 
229
205
  if (!result.success) {
230
- logger.error("nextauth_facebook_signin_failed", {
231
- email: user.email,
232
- error: result.error,
233
- });
234
- if (result.error === "link_blocked_unverified") {
235
- return `/hazo_auth/login?error=link_blocked_unverified`;
236
- }
206
+ logger.error("nextauth_facebook_signin_failed", { email: user.email, error: result.error });
237
207
  return false;
238
208
  }
239
209
 
@@ -244,16 +214,10 @@ export function get_nextauth_config(): AuthOptions {
244
214
  was_linked: result.was_linked,
245
215
  });
246
216
 
247
- // Store user_id in account for the JWT callback to pick up
248
217
  (account as Record<string, unknown>).hazo_user_id = result.user_id;
249
-
250
218
  return true;
251
- } catch (error) {
252
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
253
- logger.error("nextauth_facebook_signin_exception", {
254
- email: user.email,
255
- error: errorMessage,
256
- });
219
+ } catch (err) {
220
+ logger.error("nextauth_facebook_signin_exception", { error: String(err) });
257
221
  return false;
258
222
  }
259
223
  }
@@ -321,9 +285,5 @@ export function has_oauth_providers(): boolean {
321
285
  if (has_google_credentials) return true;
322
286
  }
323
287
 
324
- if (oauth_config.enable_facebook && oauth_config.facebook_client_id && oauth_config.facebook_client_secret) {
325
- return true;
326
- }
327
-
328
288
  return false;
329
289
  }
@@ -10,7 +10,7 @@ import {
10
10
  PermissionError,
11
11
  type TenantAuthOptions,
12
12
  type TenantAuthResult,
13
- type SelectedScope,
13
+ type TenantOrganization,
14
14
  type HazoAuthUser,
15
15
  type ScopeDetails,
16
16
  type ScopeAccessInfo,
@@ -27,19 +27,19 @@ export type AuthenticatedTenantAuth = {
27
27
  permissions: string[];
28
28
  permission_ok: boolean;
29
29
  missing_permissions?: string[];
30
- selected_scope: SelectedScope | null;
31
- selected_scope_id: string | null;
30
+ organization: TenantOrganization | null;
31
+ organization_id: string | null;
32
32
  user_scopes: ScopeDetails[];
33
33
  scope_ok?: boolean;
34
34
  scope_access_via?: ScopeAccessInfo;
35
35
  };
36
36
 
37
37
  /**
38
- * Authenticated branch with guaranteed non-null selected_scope
38
+ * Authenticated branch with guaranteed non-null organization
39
39
  */
40
- export type AuthenticatedTenantAuthWithSelectedScope = AuthenticatedTenantAuth & {
41
- selected_scope: SelectedScope;
42
- selected_scope_id: string;
40
+ export type AuthenticatedTenantAuthWithOrg = AuthenticatedTenantAuth & {
41
+ organization: TenantOrganization;
42
+ organization_id: string;
43
43
  };
44
44
 
45
45
  /**
@@ -48,8 +48,8 @@ export type AuthenticatedTenantAuthWithSelectedScope = AuthenticatedTenantAuth &
48
48
  */
49
49
  export type WithAuthOptions = TenantAuthOptions & {
50
50
  /**
51
- * If true, requires tenant/scope context (403 if missing)
52
- * Narrows auth type to AuthenticatedTenantAuthWithSelectedScope
51
+ * If true, requires organization context (403 if missing)
52
+ * Narrows auth type to AuthenticatedTenantAuthWithOrg
53
53
  */
54
54
  require_tenant?: boolean;
55
55
  };
@@ -75,7 +75,7 @@ type AuthenticatedHandler<TParams> = (
75
75
  */
76
76
  type AuthenticatedTenantHandler<TParams> = (
77
77
  request: NextRequest,
78
- auth: AuthenticatedTenantAuthWithSelectedScope,
78
+ auth: AuthenticatedTenantAuthWithOrg,
79
79
  params: TParams,
80
80
  ) => Promise<NextResponse> | NextResponse;
81
81
 
@@ -138,7 +138,7 @@ async function resolve_params<TParams>(
138
138
  *
139
139
  * - Calls `hazo_get_tenant_auth` and returns 401 if not authenticated
140
140
  * - Returns 403 if `required_permissions` are specified and not satisfied
141
- * - Returns 403 if `require_tenant: true` and no tenant/scope context
141
+ * - Returns 403 if `require_tenant: true` and no organization context
142
142
  * - Resolves `await context.params` (Next.js 15 pattern)
143
143
  * - Catches HazoAuthError, PermissionError, and unexpected errors
144
144
  *
@@ -161,8 +161,8 @@ async function resolve_params<TParams>(
161
161
  * // With tenant requirement
162
162
  * export const GET = withAuth<{ id: string }>(
163
163
  * async (request, auth, { id }) => {
164
- * // auth.selected_scope is guaranteed non-null
165
- * const data = await getData(auth.selected_scope.id, id);
164
+ * // auth.organization is guaranteed non-null
165
+ * const data = await getData(auth.organization.id, id);
166
166
  * return NextResponse.json(data);
167
167
  * },
168
168
  * { require_tenant: true }
@@ -227,10 +227,10 @@ export function withAuth<TParams = Record<string, never>>(
227
227
  }
228
228
 
229
229
  // Check tenant requirement
230
- if (options.require_tenant && !auth.selected_scope) {
230
+ if (options.require_tenant && !auth.organization) {
231
231
  return NextResponse.json(
232
232
  {
233
- error: "Tenant scope context required",
233
+ error: "Organization context required",
234
234
  code: "TENANT_REQUIRED",
235
235
  },
236
236
  { status: 403 },
@@ -177,6 +177,14 @@ export const DEFAULT_OAUTH = {
177
177
  skip_invitation_check: false,
178
178
  /** Redirect when skip_invitation_check=true and user has no scope */
179
179
  no_scope_redirect: "/",
180
+ /** Enable Facebook OAuth login (requires HAZO_AUTH_FACEBOOK_APP_ID and HAZO_AUTH_FACEBOOK_APP_SECRET env vars) */
181
+ enable_facebook_oauth: false,
182
+ /** Facebook App ID — set via env var HAZO_AUTH_FACEBOOK_APP_ID or config */
183
+ facebook_app_id: "",
184
+ /** Facebook App Secret — set via env var HAZO_AUTH_FACEBOOK_APP_SECRET or config */
185
+ facebook_app_secret: "",
186
+ /** Text displayed on the Facebook sign-in button */
187
+ facebook_button_text: "Continue with Facebook",
180
188
  } as const;
181
189
 
182
190
  // section: navbar
@@ -27,10 +27,10 @@ export const BASE_COOKIE_NAMES = {
27
27
  USER_ID: "hazo_auth_user_id",
28
28
  USER_EMAIL: "hazo_auth_user_email",
29
29
  SESSION: "hazo_auth_session",
30
- SESSION_KIND: "hazo_auth_session_kind", // v6.1: marks OTP-issued sessions so /me can apply sliding expiry
31
30
  DEV_LOCK: "hazo_auth_dev_lock",
32
31
  SCOPE_ID: "hazo_auth_scope_id", // v5.2: Tenant context cookie for multi-tenancy
33
32
  ANON_ID: "hazo_auth_anon_id", // v5.2: Stable opaque per-visitor ID for anonymous flows (e.g. hazo_feedback)
33
+ SESSION_KIND: "hazo_auth_session_kind", // v5.4: Sign-in method identifier (e.g. "otp", "google", "password")
34
34
  } as const;
35
35
 
36
36
  // section: main_function
@@ -4,6 +4,12 @@ import "server-only";
4
4
 
5
5
  // section: imports
6
6
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
7
+ import { get_config_value } from "./config/config_loader.server.js";
8
+
9
+ // Default image path - consuming apps should either:
10
+ // 1. Configure their own image_src in hazo_auth_config.ini
11
+ // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
12
+ const DEFAULT_VERIFY_EMAIL_IMAGE_PATH = "/hazo_auth/images/verify_email_default.jpg";
7
13
 
8
14
  // section: types
9
15
  export type EmailVerificationConfig = {
@@ -12,6 +18,9 @@ export type EmailVerificationConfig = {
12
18
  showReturnHomeButton: boolean;
13
19
  returnHomeButtonLabel: string;
14
20
  returnHomePath: string;
21
+ imageSrc: string;
22
+ imageAlt: string;
23
+ imageBackgroundColor: string;
15
24
  };
16
25
 
17
26
  // section: helpers
@@ -21,15 +30,40 @@ export type EmailVerificationConfig = {
21
30
  * @returns Email verification configuration options
22
31
  */
23
32
  export function get_email_verification_config(): EmailVerificationConfig {
33
+ const section = "hazo_auth__email_verification_layout";
34
+
24
35
  // Get shared already logged in config
25
36
  const alreadyLoggedInConfig = get_already_logged_in_config();
26
37
 
38
+ // Read image configuration
39
+ // If not set in config, falls back to default path-based image
40
+ // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
41
+ const imageSrc = get_config_value(
42
+ section,
43
+ "image_src",
44
+ DEFAULT_VERIFY_EMAIL_IMAGE_PATH
45
+ );
46
+
47
+ const imageAlt = get_config_value(
48
+ section,
49
+ "image_alt",
50
+ "Email verification illustration"
51
+ );
52
+ const imageBackgroundColor = get_config_value(
53
+ section,
54
+ "image_background_color",
55
+ "#f1f5f9"
56
+ );
57
+
27
58
  return {
28
59
  alreadyLoggedInMessage: alreadyLoggedInConfig.message,
29
60
  showLogoutButton: alreadyLoggedInConfig.showLogoutButton,
30
61
  showReturnHomeButton: alreadyLoggedInConfig.showReturnHomeButton,
31
62
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
32
63
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
64
+ imageSrc,
65
+ imageAlt,
66
+ imageBackgroundColor,
33
67
  };
34
68
  }
35
69
 
@@ -4,6 +4,12 @@ import "server-only";
4
4
 
5
5
  // section: imports
6
6
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
7
+ import { get_config_value } from "./config/config_loader.server.js";
8
+
9
+ // Default image path - consuming apps should either:
10
+ // 1. Configure their own image_src in hazo_auth_config.ini
11
+ // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
12
+ const DEFAULT_FORGOT_PASSWORD_IMAGE_PATH = "/hazo_auth/images/forgot_password_default.jpg";
7
13
 
8
14
  // section: types
9
15
  export type ForgotPasswordConfig = {
@@ -12,6 +18,9 @@ export type ForgotPasswordConfig = {
12
18
  showReturnHomeButton: boolean;
13
19
  returnHomeButtonLabel: string;
14
20
  returnHomePath: string;
21
+ imageSrc: string;
22
+ imageAlt: string;
23
+ imageBackgroundColor: string;
15
24
  };
16
25
 
17
26
  // section: helpers
@@ -21,15 +30,40 @@ export type ForgotPasswordConfig = {
21
30
  * @returns Forgot password configuration options
22
31
  */
23
32
  export function get_forgot_password_config(): ForgotPasswordConfig {
33
+ const section = "hazo_auth__forgot_password_layout";
34
+
24
35
  // Get shared already logged in config
25
36
  const alreadyLoggedInConfig = get_already_logged_in_config();
26
37
 
38
+ // Read image configuration
39
+ // If not set in config, falls back to default path-based image
40
+ // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
41
+ const imageSrc = get_config_value(
42
+ section,
43
+ "image_src",
44
+ DEFAULT_FORGOT_PASSWORD_IMAGE_PATH
45
+ );
46
+
47
+ const imageAlt = get_config_value(
48
+ section,
49
+ "image_alt",
50
+ "Password recovery illustration"
51
+ );
52
+ const imageBackgroundColor = get_config_value(
53
+ section,
54
+ "image_background_color",
55
+ "#f1f5f9"
56
+ );
57
+
27
58
  return {
28
59
  alreadyLoggedInMessage: alreadyLoggedInConfig.message,
29
60
  showLogoutButton: alreadyLoggedInConfig.showLogoutButton,
30
61
  showReturnHomeButton: alreadyLoggedInConfig.showReturnHomeButton,
31
62
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
32
63
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
64
+ imageSrc,
65
+ imageAlt,
66
+ imageBackgroundColor,
33
67
  };
34
68
  }
35
69
 
@@ -0,0 +1,61 @@
1
+ // file_description: server-only helper to read legal docs configuration from hazo_auth_config.ini
2
+ // section: server-only-guard
3
+ import 'server-only';
4
+
5
+ // section: imports
6
+ import { read_config_section } from '../config/config_loader.server.js';
7
+ import type { LegalDocConfig, LegalDocsConfig } from './legal_docs_types';
8
+
9
+ // section: constants
10
+ const SECTION_NAME = 'hazo_auth__legal_docs';
11
+
12
+ // section: cache
13
+ // Cached after first load — INI changes require server restart anyway
14
+ let _cached: LegalDocsConfig | null = null;
15
+
16
+ // section: exports
17
+
18
+ /**
19
+ * Reads legal docs configuration from hazo_auth_config.ini.
20
+ * Returns an empty docs array if the section is absent (legal docs disabled).
21
+ *
22
+ * Expected INI shape:
23
+ * [hazo_auth__legal_docs]
24
+ * display_mode = separate ; or: combined
25
+ * doc_1_key = terms
26
+ * doc_1_title = Terms of Service
27
+ * doc_1_path = legal/terms.md
28
+ * doc_2_key = privacy
29
+ * doc_2_title = Privacy Policy
30
+ * doc_2_path = legal/privacy.md
31
+ */
32
+ export function get_legal_docs_config(): LegalDocsConfig {
33
+ if (_cached) return _cached;
34
+
35
+ const section = read_config_section(SECTION_NAME) ?? {};
36
+
37
+ const docs: LegalDocConfig[] = [];
38
+ let i = 1;
39
+ while (section[`doc_${i}_key`]) {
40
+ docs.push({
41
+ key: section[`doc_${i}_key`],
42
+ title: section[`doc_${i}_title`] ?? section[`doc_${i}_key`],
43
+ path: section[`doc_${i}_path`],
44
+ });
45
+ i++;
46
+ }
47
+
48
+ _cached = {
49
+ docs,
50
+ display_mode: section['display_mode'] === 'combined' ? 'combined' : 'separate',
51
+ };
52
+
53
+ return _cached;
54
+ }
55
+
56
+ /**
57
+ * Call this in tests to clear the cache between runs.
58
+ */
59
+ export function _reset_legal_docs_config_cache(): void {
60
+ _cached = null;
61
+ }
@@ -0,0 +1,36 @@
1
+ // file_description: server-only utility that reads a legal document from disk and returns its content + SHA-256 hash
2
+ // section: server-only-guard
3
+ import 'server-only';
4
+
5
+ // section: imports
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import { createHash } from 'crypto';
9
+
10
+ // section: types
11
+
12
+ export interface ReadDocResult {
13
+ content: string;
14
+ hash: string; // "sha256:<hex>"
15
+ }
16
+
17
+ // section: exports
18
+
19
+ /**
20
+ * Reads a legal document from the filesystem and returns its text content
21
+ * together with a deterministic SHA-256 hash of that content.
22
+ *
23
+ * @param doc_path - Absolute path, or a path relative to process.cwd().
24
+ * @returns { content, hash } where hash is formatted as "sha256:<hex>".
25
+ * @throws If the file cannot be read.
26
+ */
27
+ export function read_legal_doc(doc_path: string): ReadDocResult {
28
+ const abs_path = path.isAbsolute(doc_path)
29
+ ? doc_path
30
+ : path.join(process.cwd(), doc_path);
31
+
32
+ const content = fs.readFileSync(abs_path, 'utf-8');
33
+ const hex = createHash('sha256').update(content).digest('hex');
34
+
35
+ return { content, hash: `sha256:${hex}` };
36
+ }