hazo_auth 6.0.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. package/README.md +233 -8
  2. package/SETUP_CHECKLIST.md +240 -0
  3. package/cli-src/cli/validate.ts +4 -0
  4. package/cli-src/lib/auth/nextauth_config.ts +101 -1
  5. package/cli-src/lib/cookies_config.server.ts +1 -0
  6. package/cli-src/lib/email_verification_config.server.ts +0 -34
  7. package/cli-src/lib/forgot_password_config.server.ts +0 -34
  8. package/cli-src/lib/login_config.server.ts +14 -31
  9. package/cli-src/lib/my_settings_config.server.ts +0 -3
  10. package/cli-src/lib/oauth_config.server.ts +58 -0
  11. package/cli-src/lib/otp_config.server.ts +91 -0
  12. package/cli-src/lib/register_config.server.ts +11 -31
  13. package/cli-src/lib/reset_password_config.server.ts +0 -31
  14. package/cli-src/lib/services/email_service.ts +3 -1
  15. package/cli-src/lib/services/email_template_manifest.ts +17 -0
  16. package/cli-src/lib/services/email_templates/otp_signin_code.html +13 -0
  17. package/cli-src/lib/services/email_templates/otp_signin_code.txt +5 -0
  18. package/cli-src/lib/services/index.ts +8 -2
  19. package/cli-src/lib/services/oauth_service.ts +197 -0
  20. package/cli-src/lib/services/otp_service.ts +295 -0
  21. package/cli-src/lib/services/session_token_service.ts +4 -1
  22. package/config/hazo_auth_config.example.ini +76 -41
  23. package/dist/cli/validate.d.ts.map +1 -1
  24. package/dist/cli/validate.js +4 -0
  25. package/dist/client.d.ts +2 -0
  26. package/dist/client.d.ts.map +1 -1
  27. package/dist/client.js +1 -0
  28. package/dist/components/layouts/create_firm/index.d.ts +4 -8
  29. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  30. package/dist/components/layouts/create_firm/index.js +3 -3
  31. package/dist/components/layouts/email_verification/index.d.ts +4 -5
  32. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  33. package/dist/components/layouts/email_verification/index.js +4 -4
  34. package/dist/components/layouts/forgot_password/index.d.ts +4 -5
  35. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  36. package/dist/components/layouts/forgot_password/index.js +2 -2
  37. package/dist/components/layouts/login/index.d.ts +19 -9
  38. package/dist/components/layouts/login/index.d.ts.map +1 -1
  39. package/dist/components/layouts/login/index.js +12 -6
  40. package/dist/components/layouts/otp/index.d.ts +17 -0
  41. package/dist/components/layouts/otp/index.d.ts.map +1 -0
  42. package/dist/components/layouts/otp/index.js +16 -0
  43. package/dist/components/layouts/register/index.d.ts +11 -7
  44. package/dist/components/layouts/register/index.d.ts.map +1 -1
  45. package/dist/components/layouts/register/index.js +8 -4
  46. package/dist/components/layouts/reset_password/index.d.ts +4 -5
  47. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  48. package/dist/components/layouts/reset_password/index.js +5 -5
  49. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +3 -5
  50. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  51. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  52. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +25 -0
  53. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -0
  54. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +49 -0
  55. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  56. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +8 -3
  57. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +3 -6
  58. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  59. package/dist/components/layouts/shared/components/two_column_auth_layout.js +8 -5
  60. package/dist/components/otp/OTPRequestForm.d.ts +11 -0
  61. package/dist/components/otp/OTPRequestForm.d.ts.map +1 -0
  62. package/dist/components/otp/OTPRequestForm.js +42 -0
  63. package/dist/components/otp/OTPVerifyForm.d.ts +16 -0
  64. package/dist/components/otp/OTPVerifyForm.d.ts.map +1 -0
  65. package/dist/components/otp/OTPVerifyForm.js +75 -0
  66. package/dist/components/otp/index.d.ts +5 -0
  67. package/dist/components/otp/index.d.ts.map +1 -0
  68. package/dist/components/otp/index.js +2 -0
  69. package/dist/components/ui/input-otp.d.ts +35 -0
  70. package/dist/components/ui/input-otp.d.ts.map +1 -0
  71. package/dist/components/ui/input-otp.js +44 -0
  72. package/dist/consent/consent_state.d.ts +18 -0
  73. package/dist/consent/consent_state.d.ts.map +1 -0
  74. package/dist/consent/consent_state.js +29 -0
  75. package/dist/consent/cookie_consent_banner.d.ts +11 -0
  76. package/dist/consent/cookie_consent_banner.d.ts.map +1 -0
  77. package/dist/consent/cookie_consent_banner.js +40 -0
  78. package/dist/consent/gtm_mapping.d.ts +13 -0
  79. package/dist/consent/gtm_mapping.d.ts.map +1 -0
  80. package/dist/consent/gtm_mapping.js +30 -0
  81. package/dist/consent/index.d.ts +7 -0
  82. package/dist/consent/index.d.ts.map +1 -0
  83. package/dist/consent/index.js +7 -0
  84. package/dist/consent/manage_modal.d.ts +2 -0
  85. package/dist/consent/manage_modal.d.ts.map +1 -0
  86. package/dist/consent/manage_modal.js +33 -0
  87. package/dist/consent/read_consent.d.ts +15 -0
  88. package/dist/consent/read_consent.d.ts.map +1 -0
  89. package/dist/consent/read_consent.js +23 -0
  90. package/dist/consent/use_consent.d.ts +7 -0
  91. package/dist/consent/use_consent.d.ts.map +1 -0
  92. package/dist/consent/use_consent.js +55 -0
  93. package/dist/lib/auth/nextauth_config.d.ts +10 -0
  94. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  95. package/dist/lib/auth/nextauth_config.js +80 -2
  96. package/dist/lib/cookies_config.server.d.ts +1 -0
  97. package/dist/lib/cookies_config.server.d.ts.map +1 -1
  98. package/dist/lib/cookies_config.server.js +1 -0
  99. package/dist/lib/email_verification_config.server.d.ts +0 -3
  100. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  101. package/dist/lib/email_verification_config.server.js +0 -15
  102. package/dist/lib/forgot_password_config.server.d.ts +0 -3
  103. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  104. package/dist/lib/forgot_password_config.server.js +0 -15
  105. package/dist/lib/login_config.server.d.ts +6 -3
  106. package/dist/lib/login_config.server.d.ts.map +1 -1
  107. package/dist/lib/login_config.server.js +7 -13
  108. package/dist/lib/my_settings_config.server.d.ts +0 -1
  109. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  110. package/dist/lib/my_settings_config.server.js +0 -2
  111. package/dist/lib/oauth_config.server.d.ts +17 -0
  112. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  113. package/dist/lib/oauth_config.server.js +25 -0
  114. package/dist/lib/otp_config.server.d.ts +49 -0
  115. package/dist/lib/otp_config.server.d.ts.map +1 -0
  116. package/dist/lib/otp_config.server.js +48 -0
  117. package/dist/lib/register_config.server.d.ts +2 -3
  118. package/dist/lib/register_config.server.d.ts.map +1 -1
  119. package/dist/lib/register_config.server.js +4 -13
  120. package/dist/lib/reset_password_config.server.d.ts +0 -3
  121. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  122. package/dist/lib/reset_password_config.server.js +0 -13
  123. package/dist/lib/services/email_service.d.ts +1 -1
  124. package/dist/lib/services/email_service.d.ts.map +1 -1
  125. package/dist/lib/services/email_service.js +2 -0
  126. package/dist/lib/services/email_template_manifest.d.ts.map +1 -1
  127. package/dist/lib/services/email_template_manifest.js +17 -0
  128. package/dist/lib/services/email_templates/otp_signin_code.html +13 -0
  129. package/dist/lib/services/email_templates/otp_signin_code.txt +5 -0
  130. package/dist/lib/services/index.d.ts +2 -0
  131. package/dist/lib/services/index.d.ts.map +1 -1
  132. package/dist/lib/services/index.js +1 -0
  133. package/dist/lib/services/oauth_service.d.ts +24 -0
  134. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  135. package/dist/lib/services/oauth_service.js +155 -0
  136. package/dist/lib/services/otp_service.d.ts +46 -0
  137. package/dist/lib/services/otp_service.d.ts.map +1 -0
  138. package/dist/lib/services/otp_service.js +238 -0
  139. package/dist/lib/services/session_token_service.d.ts +3 -1
  140. package/dist/lib/services/session_token_service.d.ts.map +1 -1
  141. package/dist/lib/services/session_token_service.js +4 -2
  142. package/dist/page_components/create_firm.d.ts +13 -1
  143. package/dist/page_components/create_firm.d.ts.map +1 -1
  144. package/dist/page_components/create_firm.js +10 -6
  145. package/dist/page_components/forgot_password.d.ts +1 -4
  146. package/dist/page_components/forgot_password.d.ts.map +1 -1
  147. package/dist/page_components/forgot_password.js +2 -6
  148. package/dist/page_components/login.d.ts +1 -4
  149. package/dist/page_components/login.d.ts.map +1 -1
  150. package/dist/page_components/login.js +2 -6
  151. package/dist/page_components/otp.d.ts +4 -0
  152. package/dist/page_components/otp.d.ts.map +1 -0
  153. package/dist/page_components/otp.js +5 -0
  154. package/dist/page_components/register.d.ts +1 -4
  155. package/dist/page_components/register.d.ts.map +1 -1
  156. package/dist/page_components/register.js +2 -6
  157. package/dist/page_components/reset_password.d.ts +1 -4
  158. package/dist/page_components/reset_password.d.ts.map +1 -1
  159. package/dist/page_components/reset_password.js +2 -6
  160. package/dist/page_components/verify_email.d.ts +1 -4
  161. package/dist/page_components/verify_email.d.ts.map +1 -1
  162. package/dist/page_components/verify_email.js +2 -6
  163. package/dist/server/routes/index.d.ts +3 -0
  164. package/dist/server/routes/index.d.ts.map +1 -1
  165. package/dist/server/routes/index.js +4 -0
  166. package/dist/server/routes/me.d.ts.map +1 -1
  167. package/dist/server/routes/me.js +43 -1
  168. package/dist/server/routes/oauth_facebook_callback.d.ts +8 -0
  169. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -0
  170. package/dist/server/routes/oauth_facebook_callback.js +157 -0
  171. package/dist/server/routes/oauth_google_callback.js +1 -1
  172. package/dist/server/routes/otp/request.d.ts +3 -0
  173. package/dist/server/routes/otp/request.d.ts.map +1 -0
  174. package/dist/server/routes/otp/request.js +33 -0
  175. package/dist/server/routes/otp/verify.d.ts +3 -0
  176. package/dist/server/routes/otp/verify.d.ts.map +1 -0
  177. package/dist/server/routes/otp/verify.js +58 -0
  178. package/dist/server-lib.d.ts +3 -0
  179. package/dist/server-lib.d.ts.map +1 -1
  180. package/dist/server-lib.js +2 -0
  181. package/dist/server_pages/forgot_password.d.ts +13 -17
  182. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  183. package/dist/server_pages/forgot_password.js +12 -8
  184. package/dist/server_pages/forgot_password_client_wrapper.d.ts +7 -6
  185. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  186. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  187. package/dist/server_pages/login.d.ts +22 -21
  188. package/dist/server_pages/login.d.ts.map +1 -1
  189. package/dist/server_pages/login.js +15 -19
  190. package/dist/server_pages/login_client_wrapper.d.ts +10 -6
  191. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  192. package/dist/server_pages/login_client_wrapper.js +2 -2
  193. package/dist/server_pages/my_settings.d.ts +2 -0
  194. package/dist/server_pages/my_settings.d.ts.map +1 -1
  195. package/dist/server_pages/my_settings.js +8 -2
  196. package/dist/server_pages/otp.d.ts +56 -0
  197. package/dist/server_pages/otp.d.ts.map +1 -0
  198. package/dist/server_pages/otp.js +45 -0
  199. package/dist/server_pages/register.d.ts +19 -16
  200. package/dist/server_pages/register.d.ts.map +1 -1
  201. package/dist/server_pages/register.js +15 -12
  202. package/dist/server_pages/register_client_wrapper.d.ts +10 -6
  203. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  204. package/dist/server_pages/register_client_wrapper.js +2 -2
  205. package/dist/server_pages/reset_password.d.ts +11 -16
  206. package/dist/server_pages/reset_password.d.ts.map +1 -1
  207. package/dist/server_pages/reset_password.js +11 -9
  208. package/dist/server_pages/reset_password_client_wrapper.d.ts +7 -6
  209. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  210. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  211. package/dist/server_pages/verify_email.d.ts +11 -17
  212. package/dist/server_pages/verify_email.d.ts.map +1 -1
  213. package/dist/server_pages/verify_email.js +11 -8
  214. package/dist/server_pages/verify_email_client_wrapper.d.ts +7 -6
  215. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  216. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  217. package/dist/strings/default_strings.d.ts +47 -0
  218. package/dist/strings/default_strings.d.ts.map +1 -0
  219. package/dist/strings/default_strings.js +18 -0
  220. package/dist/strings/index.d.ts +4 -0
  221. package/dist/strings/index.d.ts.map +1 -0
  222. package/dist/strings/index.js +3 -0
  223. package/dist/strings/strings_context.d.ts +12 -0
  224. package/dist/strings/strings_context.d.ts.map +1 -0
  225. package/dist/strings/strings_context.js +23 -0
  226. package/dist/strings/strings_provider.d.ts +26 -0
  227. package/dist/strings/strings_provider.d.ts.map +1 -0
  228. package/dist/strings/strings_provider.js +45 -0
  229. package/dist/theme/create_theme.d.ts +7 -0
  230. package/dist/theme/create_theme.d.ts.map +1 -0
  231. package/dist/theme/create_theme.js +97 -0
  232. package/dist/theme/hex_to_hsl.d.ts +16 -0
  233. package/dist/theme/hex_to_hsl.d.ts.map +1 -0
  234. package/dist/theme/hex_to_hsl.js +110 -0
  235. package/dist/theme/index.d.ts +4 -0
  236. package/dist/theme/index.d.ts.map +1 -0
  237. package/dist/theme/index.js +3 -0
  238. package/dist/theme/luminance.d.ts +11 -0
  239. package/dist/theme/luminance.d.ts.map +1 -0
  240. package/dist/theme/luminance.js +45 -0
  241. package/dist/theme/theme_provider.d.ts +14 -0
  242. package/dist/theme/theme_provider.d.ts.map +1 -0
  243. package/dist/theme/theme_provider.js +23 -0
  244. package/dist/theme/theme_types.d.ts +36 -0
  245. package/dist/theme/theme_types.d.ts.map +1 -0
  246. package/dist/theme/theme_types.js +1 -0
  247. package/dist/themes/index.d.ts +3 -0
  248. package/dist/themes/index.d.ts.map +1 -0
  249. package/dist/themes/index.js +2 -0
  250. package/dist/themes/preset_indigo_sunset.d.ts +3 -0
  251. package/dist/themes/preset_indigo_sunset.d.ts.map +1 -0
  252. package/dist/themes/preset_indigo_sunset.js +20 -0
  253. package/dist/themes/preset_neutral.d.ts +3 -0
  254. package/dist/themes/preset_neutral.d.ts.map +1 -0
  255. package/dist/themes/preset_neutral.js +14 -0
  256. package/package.json +36 -2
@@ -167,6 +167,161 @@ export async function handle_google_oauth_login(adapter, data) {
167
167
  };
168
168
  }
169
169
  }
170
+ /**
171
+ * Handles Facebook OAuth login/registration flow
172
+ * 1. Check if user exists with facebook_id -> login
173
+ * 2. Check if user exists with email -> link Facebook account (respects auto_link_unverified)
174
+ * 3. Create new user with Facebook data (email_verified always false — never trust Facebook)
175
+ *
176
+ * @param adapter - The hazo_connect adapter instance
177
+ * @param data - Facebook OAuth user data
178
+ * @param opts - Options (auto_link_unverified: whether to link unverified accounts)
179
+ * @returns OAuth login result with user_id and status flags
180
+ */
181
+ export async function handle_facebook_oauth_login(adapter, data, opts) {
182
+ const logger = create_app_logger();
183
+ try {
184
+ const { facebook_id, email, name, profile_picture_url } = data;
185
+ const users_service = createCrudService(adapter, "hazo_users");
186
+ const now = new Date().toISOString();
187
+ // Step 1: Check if user exists with this facebook_id
188
+ const users_by_facebook_id = await users_service.findBy({ facebook_id });
189
+ if (Array.isArray(users_by_facebook_id) && users_by_facebook_id.length > 0) {
190
+ const user = users_by_facebook_id[0];
191
+ await users_service.updateById(user.id, {
192
+ last_logon: now,
193
+ changed_at: now,
194
+ });
195
+ logger.info("oauth_service_facebook_login_existing_facebook_user", {
196
+ filename: "oauth_service.ts",
197
+ line_number: get_line_number(),
198
+ user_id: user.id,
199
+ email: user.email_address,
200
+ });
201
+ return {
202
+ success: true,
203
+ user_id: user.id,
204
+ is_new_user: false,
205
+ was_linked: false,
206
+ email: user.email_address,
207
+ name: user.name,
208
+ };
209
+ }
210
+ // Step 2: Check if user exists with this email
211
+ if (email) {
212
+ const users_by_email = await users_service.findBy({ email_address: email });
213
+ if (Array.isArray(users_by_email) && users_by_email.length > 0) {
214
+ const user = users_by_email[0];
215
+ const user_email_verified = user.email_verified;
216
+ if (!user_email_verified) {
217
+ if (!(opts === null || opts === void 0 ? void 0 : opts.auto_link_unverified)) {
218
+ return {
219
+ success: false,
220
+ error: "link_blocked_unverified",
221
+ };
222
+ }
223
+ // auto_link_unverified=true: link but do NOT change email_verified status
224
+ }
225
+ // Link Facebook account to existing user
226
+ const current_auth_providers = user.auth_providers || "email";
227
+ const new_auth_providers = current_auth_providers.includes("facebook")
228
+ ? current_auth_providers
229
+ : `${current_auth_providers},facebook`;
230
+ const update_data = {
231
+ facebook_id,
232
+ auth_providers: new_auth_providers,
233
+ last_logon: now,
234
+ changed_at: now,
235
+ };
236
+ // Update name if not set and Facebook provides one
237
+ if (!user.name && name) {
238
+ update_data.name = name;
239
+ }
240
+ // Update profile picture if not set and Facebook provides one
241
+ if (!user.profile_picture_url && profile_picture_url) {
242
+ update_data.profile_picture_url = profile_picture_url;
243
+ update_data.profile_source = "custom";
244
+ }
245
+ await users_service.updateById(user.id, update_data);
246
+ logger.info("oauth_service_facebook_linked_to_existing", {
247
+ filename: "oauth_service.ts",
248
+ line_number: get_line_number(),
249
+ user_id: user.id,
250
+ email,
251
+ was_unverified: !user_email_verified,
252
+ });
253
+ return {
254
+ success: true,
255
+ user_id: user.id,
256
+ is_new_user: false,
257
+ was_linked: true,
258
+ email: user.email_address,
259
+ name: update_data.name || user.name,
260
+ };
261
+ }
262
+ }
263
+ // Step 3: Create new user with Facebook data
264
+ const user_id = randomUUID();
265
+ const insert_data = {
266
+ id: user_id,
267
+ email_address: email,
268
+ password_hash: "", // Empty string for Facebook-only users
269
+ email_verified: false, // Never trust Facebook's email_verified claim
270
+ status: "ACTIVE",
271
+ login_attempts: 0,
272
+ facebook_id,
273
+ auth_providers: "facebook",
274
+ created_at: now,
275
+ changed_at: now,
276
+ last_logon: now,
277
+ };
278
+ if (name) {
279
+ insert_data.name = name;
280
+ }
281
+ if (profile_picture_url) {
282
+ insert_data.profile_picture_url = profile_picture_url;
283
+ insert_data.profile_source = "custom";
284
+ }
285
+ const inserted_users = await users_service.insert(insert_data);
286
+ if (!Array.isArray(inserted_users) || inserted_users.length === 0) {
287
+ return {
288
+ success: false,
289
+ error: "Failed to create user account",
290
+ };
291
+ }
292
+ logger.info("oauth_service_facebook_new_user_created", {
293
+ filename: "oauth_service.ts",
294
+ line_number: get_line_number(),
295
+ user_id,
296
+ email,
297
+ });
298
+ return {
299
+ success: true,
300
+ user_id,
301
+ is_new_user: true,
302
+ was_linked: false,
303
+ email: email !== null && email !== void 0 ? email : undefined,
304
+ name,
305
+ };
306
+ }
307
+ catch (error) {
308
+ const user_friendly_error = sanitize_error_for_user(error, {
309
+ logToConsole: true,
310
+ logToLogger: true,
311
+ logger,
312
+ context: {
313
+ filename: "oauth_service.ts",
314
+ line_number: get_line_number(),
315
+ email: data.email,
316
+ operation: "handle_facebook_oauth_login",
317
+ },
318
+ });
319
+ return {
320
+ success: false,
321
+ error: user_friendly_error,
322
+ };
323
+ }
324
+ }
170
325
  /**
171
326
  * Links a Google account to an existing user
172
327
  * @param adapter - The hazo_connect adapter instance
@@ -0,0 +1,46 @@
1
+ import "server-only";
2
+ /**
3
+ * Generates a cryptographically random 6-digit numeric OTP code (000000–999999).
4
+ * Uses crypto.randomInt for uniform distribution.
5
+ */
6
+ export declare function generate_otp_code(): string;
7
+ export declare function hash_otp_code(code: string): Promise<string>;
8
+ export declare function verify_otp_code(otp_hash: string, code: string): Promise<boolean>;
9
+ export type RequestEmailOTPResult = {
10
+ ok: true;
11
+ } | {
12
+ ok: false;
13
+ error: "rate_limited";
14
+ retry_after_seconds: number;
15
+ };
16
+ /**
17
+ * Initiates an OTP sign-in flow for the given email address.
18
+ *
19
+ * Behaviour:
20
+ * 1. Per-email rate limit — rejects if too many requests in the sliding window.
21
+ * 2. Per-IP rate limit — rejects if too many requests from this IP.
22
+ * 3. Unknown email + auto_register=false — silent no-op (constant-time padding).
23
+ * 4. Unknown email + auto_register=true — inserts OTP row with user_id=null, dispatches email.
24
+ * 5. Known email — marks prior unconsumed rows consumed, inserts fresh OTP row, dispatches email.
25
+ *
26
+ * Never reveals whether an email address is registered (always returns ok:true on success).
27
+ */
28
+ export declare function request_email_otp(args: {
29
+ email: string;
30
+ ip: string;
31
+ }): Promise<RequestEmailOTPResult>;
32
+ export type VerifyEmailOTPResult = {
33
+ ok: true;
34
+ user_id: string;
35
+ email: string;
36
+ session_token: string;
37
+ } | {
38
+ ok: false;
39
+ error: "invalid_or_expired";
40
+ };
41
+ export declare function verify_email_otp(args: {
42
+ email: string;
43
+ code: string;
44
+ ip: string;
45
+ }): Promise<VerifyEmailOTPResult>;
46
+ //# sourceMappingURL=otp_service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"otp_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/otp_service.ts"],"names":[],"mappings":"AAAA,OAAO,aAAa,CAAC;AAUrB;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjE;AAED,wBAAsB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAMtF;AAID,MAAM,MAAM,qBAAqB,GAC7B;IAAE,EAAE,EAAE,IAAI,CAAA;CAAE,GACZ;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,cAAc,CAAC;IAAC,mBAAmB,EAAE,MAAM,CAAA;CAAE,CAAC;AAItE;;;;;;;;;;;GAWG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA8GjC;AAID,MAAM,MAAM,oBAAoB,GAC5B;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,GACnE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,oBAAoB,CAAA;CAAE,CAAC;AAE/C,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;CACZ,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAsHhC"}
@@ -0,0 +1,238 @@
1
+ import "server-only";
2
+ import crypto from "node:crypto";
3
+ import argon2 from "argon2";
4
+ import { createCrudService } from "hazo_connect/server";
5
+ import { get_otp_config, hazo_auth_otp_session_ttl_seconds } from "../otp_config.server.js";
6
+ import { get_hazo_connect_instance } from "../hazo_connect_instance.server.js";
7
+ import { send_template_email } from "./email_service.js";
8
+ import { create_app_logger } from "../app_logger.js";
9
+ import { create_session_token } from "./session_token_service.js";
10
+ /**
11
+ * Generates a cryptographically random 6-digit numeric OTP code (000000–999999).
12
+ * Uses crypto.randomInt for uniform distribution.
13
+ */
14
+ export function generate_otp_code() {
15
+ const n = crypto.randomInt(0, 1000000);
16
+ return String(n).padStart(6, "0");
17
+ }
18
+ export async function hash_otp_code(code) {
19
+ return argon2.hash(code);
20
+ }
21
+ export async function verify_otp_code(otp_hash, code) {
22
+ try {
23
+ return await argon2.verify(otp_hash, code);
24
+ }
25
+ catch (_a) {
26
+ return false;
27
+ }
28
+ }
29
+ // section: request_email_otp
30
+ /**
31
+ * Initiates an OTP sign-in flow for the given email address.
32
+ *
33
+ * Behaviour:
34
+ * 1. Per-email rate limit — rejects if too many requests in the sliding window.
35
+ * 2. Per-IP rate limit — rejects if too many requests from this IP.
36
+ * 3. Unknown email + auto_register=false — silent no-op (constant-time padding).
37
+ * 4. Unknown email + auto_register=true — inserts OTP row with user_id=null, dispatches email.
38
+ * 5. Known email — marks prior unconsumed rows consumed, inserts fresh OTP row, dispatches email.
39
+ *
40
+ * Never reveals whether an email address is registered (always returns ok:true on success).
41
+ */
42
+ export async function request_email_otp(args) {
43
+ const logger = create_app_logger();
44
+ const cfg = get_otp_config();
45
+ const email = args.email.trim().toLowerCase();
46
+ const ip = args.ip;
47
+ const adapter = get_hazo_connect_instance();
48
+ const otp_table = createCrudService(adapter, "hazo_email_otps");
49
+ const users_table = createCrudService(adapter, "hazo_users");
50
+ // 1. Per-email rate limit
51
+ const email_window_ms = cfg.email_rate_limit_window_seconds * 1000;
52
+ const email_threshold = new Date(Date.now() - email_window_ms).toISOString();
53
+ const recent_for_email = await otp_table.list((qb) => qb
54
+ .select(["created_at"])
55
+ .where("email", "eq", email)
56
+ .where("created_at", "gte", email_threshold));
57
+ if (recent_for_email.length >= cfg.email_rate_limit_max) {
58
+ const oldest = recent_for_email
59
+ .map((r) => Date.parse(String(r.created_at)))
60
+ .sort((a, b) => a - b)[0];
61
+ const retry_after_seconds = Math.max(1, Math.ceil((oldest + email_window_ms - Date.now()) / 1000));
62
+ logger.warn("otp_request_email_rate_limited", { email, ip, retry_after_seconds });
63
+ return { ok: false, error: "rate_limited", retry_after_seconds };
64
+ }
65
+ // 2. Per-IP rate limit
66
+ const ip_window_ms = cfg.ip_rate_limit_window_seconds * 1000;
67
+ const ip_threshold = new Date(Date.now() - ip_window_ms).toISOString();
68
+ const recent_for_ip = await otp_table.list((qb) => qb
69
+ .select(["created_at"])
70
+ .where("requester_ip", "eq", ip)
71
+ .where("created_at", "gte", ip_threshold));
72
+ if (recent_for_ip.length >= cfg.ip_rate_limit_max) {
73
+ const oldest = recent_for_ip
74
+ .map((r) => Date.parse(String(r.created_at)))
75
+ .sort((a, b) => a - b)[0];
76
+ const retry_after_seconds = Math.max(1, Math.ceil((oldest + ip_window_ms - Date.now()) / 1000));
77
+ logger.warn("otp_request_ip_rate_limited", { email, ip, retry_after_seconds });
78
+ return { ok: false, error: "rate_limited", retry_after_seconds };
79
+ }
80
+ // 3. Lookup user
81
+ const existing_users = await users_table.findBy({ email_address: email });
82
+ const existing_user = existing_users.length > 0 ? existing_users[0] : null;
83
+ // 4. Unknown email + auto_register=false → silent no-op with constant-time padding
84
+ if (!existing_user && !cfg.auto_register) {
85
+ await argon2.hash("000000"); // constant-time padding — never stored
86
+ return { ok: true };
87
+ }
88
+ // 5. Mark any unconsumed rows for this email as superseded
89
+ try {
90
+ const unconsumed = await otp_table.list((qb) => qb
91
+ .select(["id"])
92
+ .where("email", "eq", email)
93
+ .where("consumed_at", "is", null));
94
+ for (const row of unconsumed) {
95
+ await otp_table.updateById(String(row.id), { consumed_at: new Date().toISOString() });
96
+ }
97
+ }
98
+ catch (_a) {
99
+ // IS NULL filter may not be supported in all adapter versions — not critical
100
+ }
101
+ // 6. Generate code + hash + insert row
102
+ const code = generate_otp_code();
103
+ const otp_hash = await hash_otp_code(code);
104
+ const expires_at = new Date(Date.now() + cfg.code_ttl_seconds * 1000).toISOString();
105
+ const row_id = crypto.randomUUID();
106
+ await otp_table.insert({
107
+ id: row_id,
108
+ user_id: existing_user ? String(existing_user.id) : null,
109
+ email,
110
+ otp_hash,
111
+ expires_at,
112
+ attempt_count: 0,
113
+ requester_ip: ip,
114
+ });
115
+ // 7. Dispatch email — fire-and-forget; errors are logged but do not surface to caller
116
+ try {
117
+ await send_template_email("otp_signin_code", email, {
118
+ otp_code: code,
119
+ expires_in_minutes: String(Math.round(cfg.code_ttl_seconds / 60)),
120
+ });
121
+ }
122
+ catch (err) {
123
+ logger.error("otp_request_email_dispatch_failed", {
124
+ email,
125
+ ip,
126
+ error: err instanceof Error ? err.message : String(err),
127
+ });
128
+ // Return ok:true to preserve no-enumeration property — caller cannot distinguish
129
+ // a missing user from a delivery failure.
130
+ }
131
+ return { ok: true };
132
+ }
133
+ export async function verify_email_otp(args) {
134
+ const logger = create_app_logger();
135
+ const cfg = get_otp_config();
136
+ const email = args.email.trim().toLowerCase();
137
+ const code = args.code.trim();
138
+ const adapter = get_hazo_connect_instance();
139
+ const otp_table = createCrudService(adapter, "hazo_email_otps");
140
+ const users_table = createCrudService(adapter, "hazo_users");
141
+ const user_scopes_table = createCrudService(adapter, "hazo_user_scopes");
142
+ const roles_table = createCrudService(adapter, "hazo_roles");
143
+ // 1. Find most-recent unconsumed row for this email
144
+ const now_iso = new Date().toISOString();
145
+ const candidates = await otp_table.list((qb) => qb
146
+ .select(["id", "user_id", "otp_hash", "expires_at", "attempt_count"])
147
+ .where("email", "eq", email)
148
+ .where("consumed_at", "is", null)
149
+ .order("created_at", "desc")
150
+ .limit(1));
151
+ const row = candidates.length > 0 ? candidates[0] : null;
152
+ if (!row) {
153
+ return { ok: false, error: "invalid_or_expired" };
154
+ }
155
+ // 2. Check expiry
156
+ const expires_at_ms = Date.parse(String(row.expires_at));
157
+ if (Number.isNaN(expires_at_ms) || expires_at_ms < Date.now()) {
158
+ return { ok: false, error: "invalid_or_expired" };
159
+ }
160
+ // 3. argon2 verify
161
+ const is_valid = await verify_otp_code(String(row.otp_hash), code);
162
+ if (!is_valid) {
163
+ const new_attempt_count = Number(row.attempt_count) + 1;
164
+ const updates = { attempt_count: new_attempt_count };
165
+ if (new_attempt_count >= cfg.max_verify_attempts) {
166
+ updates.consumed_at = now_iso; // poison
167
+ }
168
+ await otp_table.updateById(String(row.id), updates);
169
+ logger.info("otp_verify_invalid_code", {
170
+ email,
171
+ attempt_count: new_attempt_count,
172
+ poisoned: new_attempt_count >= cfg.max_verify_attempts,
173
+ });
174
+ return { ok: false, error: "invalid_or_expired" };
175
+ }
176
+ // 4. Mark consumed
177
+ await otp_table.updateById(String(row.id), { consumed_at: now_iso });
178
+ // 5. Resolve / create user
179
+ let user_id = row.user_id ? String(row.user_id) : null;
180
+ if (user_id) {
181
+ // Ensure email_verified=true
182
+ const user = await users_table.findById(user_id);
183
+ if (user && !user.email_verified) {
184
+ await users_table.updateById(user_id, { email_verified: true });
185
+ }
186
+ }
187
+ else if (cfg.auto_register) {
188
+ // Create user + bind scope/role
189
+ const new_user_id = crypto.randomUUID();
190
+ await users_table.insert({
191
+ id: new_user_id,
192
+ email_address: email,
193
+ email_verified: true,
194
+ password_hash: null,
195
+ name: null,
196
+ });
197
+ // Resolve role_id from name — try scope-specific first, then any
198
+ const scoped_roles = await roles_table.findBy({
199
+ name: cfg.auto_assign_role_name,
200
+ scope_id: cfg.auto_assign_scope_id,
201
+ });
202
+ let role_id = null;
203
+ if (scoped_roles.length > 0) {
204
+ role_id = String(scoped_roles[0].id);
205
+ }
206
+ else {
207
+ const any_roles = await roles_table.findBy({ name: cfg.auto_assign_role_name });
208
+ if (any_roles.length > 0) {
209
+ role_id = String(any_roles[0].id);
210
+ }
211
+ }
212
+ if (!role_id) {
213
+ logger.error("otp_verify_auto_register_role_not_found", {
214
+ email,
215
+ scope_id: cfg.auto_assign_scope_id,
216
+ role_name: cfg.auto_assign_role_name,
217
+ });
218
+ return { ok: false, error: "invalid_or_expired" };
219
+ }
220
+ await user_scopes_table.insert({
221
+ user_id: new_user_id,
222
+ scope_id: cfg.auto_assign_scope_id,
223
+ root_scope_id: cfg.auto_assign_scope_id,
224
+ role_id,
225
+ });
226
+ user_id = new_user_id;
227
+ }
228
+ else {
229
+ // Row exists but no user and auto_register=false — stale edge case
230
+ logger.warn("otp_verify_no_user_resolvable", { email });
231
+ return { ok: false, error: "invalid_or_expired" };
232
+ }
233
+ // 6. Issue session JWT with OTP TTL
234
+ const ttl_seconds = hazo_auth_otp_session_ttl_seconds();
235
+ const session_token = await create_session_token(user_id, email, undefined, ttl_seconds);
236
+ logger.info("otp_verify_ok", { email, user_id, ttl_seconds });
237
+ return { ok: true, user_id, email, session_token };
238
+ }
@@ -16,9 +16,11 @@ export type ValidateSessionTokenResult = {
16
16
  * Token includes user_id, email, issued at time, and expiration
17
17
  * @param user_id - User ID
18
18
  * @param email - User email address
19
+ * @param managed_by_user_id - Optional: ID of the managing user (for impersonation)
20
+ * @param ttl_seconds - Optional: token lifetime in seconds (default: 30 days). Use 604800 for 7-day OTP sessions.
19
21
  * @returns JWT token string
20
22
  */
21
- export declare function create_session_token(user_id: string, email: string, managed_by_user_id?: string): Promise<string>;
23
+ export declare function create_session_token(user_id: string, email: string, managed_by_user_id?: string, ttl_seconds?: number): Promise<string>;
22
24
  /**
23
25
  * Validates a JWT session token
24
26
  * Checks signature and expiration
@@ -1 +1 @@
1
- {"version":3,"file":"session_token_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/session_token_service.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAuCF;;;;;;GAMG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,kBAAkB,CAAC,EAAE,MAAM,GAC1B,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,0BAA0B,CAAC,CAkDrC"}
1
+ {"version":3,"file":"session_token_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/session_token_service.ts"],"names":[],"mappings":"AAQA,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B,CAAC;AAuCF;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,kBAAkB,CAAC,EAAE,MAAM,EAC3B,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,MAAM,CAAC,CA4CjB;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,0BAA0B,CAAC,CAkDrC"}
@@ -41,14 +41,16 @@ function get_session_token_expiry_seconds() {
41
41
  * Token includes user_id, email, issued at time, and expiration
42
42
  * @param user_id - User ID
43
43
  * @param email - User email address
44
+ * @param managed_by_user_id - Optional: ID of the managing user (for impersonation)
45
+ * @param ttl_seconds - Optional: token lifetime in seconds (default: 30 days). Use 604800 for 7-day OTP sessions.
44
46
  * @returns JWT token string
45
47
  */
46
- export async function create_session_token(user_id, email, managed_by_user_id) {
48
+ export async function create_session_token(user_id, email, managed_by_user_id, ttl_seconds) {
47
49
  const logger = create_app_logger();
48
50
  try {
49
51
  const secret = get_jwt_secret();
50
52
  const now = Math.floor(Date.now() / 1000); // Current time in seconds
51
- const expiry_seconds = get_session_token_expiry_seconds();
53
+ const expiry_seconds = ttl_seconds !== null && ttl_seconds !== void 0 ? ttl_seconds : get_session_token_expiry_seconds();
52
54
  const exp = now + expiry_seconds;
53
55
  const payload = { user_id, email };
54
56
  if (managed_by_user_id) {
@@ -2,7 +2,19 @@ import "server-only";
2
2
  export type CreateFirmPageProps = {
3
3
  /** Disable the navbar wrapper */
4
4
  disableNavbar?: boolean;
5
+ /**
6
+ * Optional theme that controls visual appearance and layout mode.
7
+ * When `theme.layout` is `"split"`, activates the two-column split layout
8
+ * with the brand panel on the left.
9
+ */
10
+ theme?: import("../theme/theme_types").HazoAuthTheme;
11
+ /** Override the page heading. Falls back to HazoAuthStringsProvider → DEFAULT_STRINGS. */
12
+ title?: string;
13
+ /** Override the page subtitle. Falls back to HazoAuthStringsProvider → DEFAULT_STRINGS. */
14
+ subtitle?: string;
15
+ /** Override the submit button label. Falls back to HazoAuthStringsProvider → DEFAULT_STRINGS. */
16
+ ctaText?: string;
5
17
  };
6
- export default function CreateFirmPage({ disableNavbar }?: CreateFirmPageProps): import("react/jsx-runtime").JSX.Element;
18
+ export default function CreateFirmPage({ disableNavbar, theme, title, subtitle, ctaText }?: CreateFirmPageProps): import("react/jsx-runtime").JSX.Element;
7
19
  export { CreateFirmPage };
8
20
  //# sourceMappingURL=create_firm.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"create_firm.d.ts","sourceRoot":"","sources":["../../src/page_components/create_firm.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AAQrB,MAAM,MAAM,mBAAmB,GAAG;IAChC,iCAAiC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB,CAAC;AAsDF,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,aAAa,EAAE,GAAE,mBAAwB,2CAsBjF;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"create_firm.d.ts","sourceRoot":"","sources":["../../src/page_components/create_firm.tsx"],"names":[],"mappings":"AAEA,OAAO,aAAa,CAAC;AASrB,MAAM,MAAM,mBAAmB,GAAG;IAChC,iCAAiC;IACjC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,sBAAsB,EAAE,aAAa,CAAC;IACrD,0FAA0F;IAC1F,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAkCF,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAE,mBAAwB,2CA6BlH;AAGD,OAAO,EAAE,cAAc,EAAE,CAAC"}
@@ -6,24 +6,28 @@ import "server-only";
6
6
  import CreateFirmLayout from "../components/layouts/create_firm/index.js";
7
7
  import { AuthPageShell } from "../components/layouts/shared/components/auth_page_shell.js";
8
8
  import { get_config_value } from "../lib/config/config_loader.server.js";
9
+ import { DEFAULT_STRINGS, readStrings } from "../strings.js";
9
10
  // section: config_loader
10
11
  function get_create_firm_config() {
11
12
  return {
12
- image_src: get_config_value("hazo_auth__create_firm", "image_src", "/hazo_auth/images/new_firm_default.jpg"),
13
- heading: get_config_value("hazo_auth__create_firm", "heading", "Create Your Firm"),
14
- sub_heading: get_config_value("hazo_auth__create_firm", "sub_heading", "Set up your organisation to get started"),
15
13
  firm_name_label: get_config_value("hazo_auth__create_firm", "firm_name_label", "Firm Name"),
16
14
  org_structure_label: get_config_value("hazo_auth__create_firm", "org_structure_label", "Organisation Structure"),
17
15
  org_structure_default: get_config_value("hazo_auth__create_firm", "org_structure_default", "Headquarters"),
18
- submit_button_label: get_config_value("hazo_auth__create_firm", "submit_button_label", "Create Firm"),
19
16
  success_message: get_config_value("hazo_auth__create_firm", "success_message", "Your firm has been created successfully!"),
20
17
  redirect_route: get_config_value("hazo_auth__create_firm", "redirect_route", "/"),
21
18
  };
22
19
  }
23
20
  // section: component
24
- export default function CreateFirmPage({ disableNavbar } = {}) {
21
+ export default function CreateFirmPage({ disableNavbar, theme, title, subtitle, ctaText } = {}) {
22
+ var _a, _b, _c;
23
+ // Resolve strings: prop > HazoAuthStringsProvider > DEFAULT_STRINGS
24
+ const strings = readStrings();
25
+ const cf_strings = strings.create_firm;
26
+ const resolved_title = (_a = title !== null && title !== void 0 ? title : cf_strings.title) !== null && _a !== void 0 ? _a : DEFAULT_STRINGS.create_firm.title;
27
+ const resolved_subtitle = (_b = subtitle !== null && subtitle !== void 0 ? subtitle : cf_strings.subtitle) !== null && _b !== void 0 ? _b : DEFAULT_STRINGS.create_firm.subtitle;
28
+ const resolved_cta = (_c = ctaText !== null && ctaText !== void 0 ? ctaText : cf_strings.ctaText) !== null && _c !== void 0 ? _c : DEFAULT_STRINGS.create_firm.ctaText;
25
29
  const config = get_create_firm_config();
26
- const layoutContent = (_jsx(CreateFirmLayout, { image_src: config.image_src, heading: config.heading, sub_heading: config.sub_heading, firm_name_label: config.firm_name_label, org_structure_label: config.org_structure_label, default_org_structure: config.org_structure_default, submit_button_label: config.submit_button_label, success_message: config.success_message, redirect_route: config.redirect_route }));
30
+ const layoutContent = (_jsx(CreateFirmLayout, { heading: resolved_title, sub_heading: resolved_subtitle, firm_name_label: config.firm_name_label, org_structure_label: config.org_structure_label, default_org_structure: config.org_structure_default, submit_button_label: resolved_cta, success_message: config.success_message, redirect_route: config.redirect_route, theme: theme }));
27
31
  if (disableNavbar) {
28
32
  return layoutContent;
29
33
  }
@@ -4,9 +4,6 @@ export type ForgotPasswordPageProps = {
4
4
  showReturnHomeButton?: boolean;
5
5
  returnHomeButtonLabel?: string;
6
6
  returnHomePath?: string;
7
- imageSrc?: string;
8
- imageAlt?: string;
9
- imageBackgroundColor?: string;
10
7
  };
11
8
  /**
12
9
  * Zero-config forgot password page component
@@ -14,6 +11,6 @@ export type ForgotPasswordPageProps = {
14
11
  * @param props - Optional configuration overrides
15
12
  * @returns Forgot password page component
16
13
  */
17
- export declare function ForgotPasswordPage({ alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, imageSrc, imageAlt, imageBackgroundColor, }?: ForgotPasswordPageProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ForgotPasswordPage({ alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, }?: ForgotPasswordPageProps): import("react/jsx-runtime").JSX.Element;
18
15
  export default ForgotPasswordPage;
19
16
  //# sourceMappingURL=forgot_password.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"forgot_password.d.ts","sourceRoot":"","sources":["../../src/page_components/forgot_password.tsx"],"names":[],"mappings":"AA+BA,MAAM,MAAM,uBAAuB,GAAG;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,EACpB,QAA4B,EAC5B,QAA4B,EAC5B,oBAAuC,GACxC,GAAE,uBAA4B,2CAmC9B;AAED,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"forgot_password.d.ts","sourceRoot":"","sources":["../../src/page_components/forgot_password.tsx"],"names":[],"mappings":"AA0BA,MAAM,MAAM,uBAAuB,GAAG;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,GACrB,GAAE,uBAA4B,2CAgC9B;AAED,eAAe,kBAAkB,CAAC"}
@@ -21,10 +21,6 @@ import { useEffect, useState } from "react";
21
21
  import forgot_password_layout from "../components/layouts/forgot_password/index.js";
22
22
  import { createLayoutDataClient } from "../components/layouts/shared/data/layout_data_client.js";
23
23
  import { create_sqlite_hazo_connect } from "../lib/hazo_connect_setup.js";
24
- // section: constants
25
- const DEFAULT_IMAGE_SRC = "/hazo_auth/images/forgot_password_default.jpg";
26
- const DEFAULT_IMAGE_ALT = "Illustration of a globe representing secure authentication workflows";
27
- const DEFAULT_IMAGE_BG = "#f1f5f9";
28
24
  // section: component
29
25
  /**
30
26
  * Zero-config forgot password page component
@@ -32,7 +28,7 @@ const DEFAULT_IMAGE_BG = "#f1f5f9";
32
28
  * @param props - Optional configuration overrides
33
29
  * @returns Forgot password page component
34
30
  */
35
- export function ForgotPasswordPage({ alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", imageSrc = DEFAULT_IMAGE_SRC, imageAlt = DEFAULT_IMAGE_ALT, imageBackgroundColor = DEFAULT_IMAGE_BG, } = {}) {
31
+ export function ForgotPasswordPage({ alreadyLoggedInMessage = "You are already logged in", showLogoutButton = true, showReturnHomeButton = false, returnHomeButtonLabel = "Return home", returnHomePath = "/", } = {}) {
36
32
  const [dataClient, setDataClient] = useState(null);
37
33
  useEffect(() => {
38
34
  // Initialize hazo_connect on client side
@@ -45,6 +41,6 @@ export function ForgotPasswordPage({ alreadyLoggedInMessage = "You are already l
45
41
  return (_jsx("div", { className: "cls_forgot_password_page_loading flex items-center justify-center min-h-screen", children: _jsx("div", { className: "text-slate-600 animate-pulse", children: "Loading..." }) }));
46
42
  }
47
43
  const ForgotPasswordLayout = forgot_password_layout;
48
- return (_jsx(ForgotPasswordLayout, { image_src: imageSrc, image_alt: imageAlt, image_background_color: imageBackgroundColor, data_client: dataClient, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath }));
44
+ return (_jsx(ForgotPasswordLayout, { data_client: dataClient, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath }));
49
45
  }
50
46
  export default ForgotPasswordPage;
@@ -15,9 +15,6 @@ export type LoginPageProps = {
15
15
  createAccountPath?: string;
16
16
  createAccountLabel?: string;
17
17
  urlOnLogon?: string;
18
- imageSrc?: string;
19
- imageAlt?: string;
20
- imageBackgroundColor?: string;
21
18
  };
22
19
  /**
23
20
  * Zero-config login page component
@@ -25,6 +22,6 @@ export type LoginPageProps = {
25
22
  * @param props - Optional configuration overrides
26
23
  * @returns Login page component
27
24
  */
28
- export declare function LoginPage({ redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, urlOnLogon, imageSrc, imageAlt, imageBackgroundColor, }?: LoginPageProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function LoginPage({ redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, urlOnLogon, }?: LoginPageProps): import("react/jsx-runtime").JSX.Element;
29
26
  export default LoginPage;
30
27
  //# sourceMappingURL=login.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/page_components/login.tsx"],"names":[],"mappings":"AA+BA;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,aAAa,EACb,cAAyC,EACzC,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,EACpB,kBAAiD,EACjD,mBAAwC,EACxC,iBAAyC,EACzC,kBAAqC,EACrC,UAAU,EACV,QAA4B,EAC5B,QAA4B,EAC5B,oBAAuC,GACxC,GAAE,cAAmB,2CA0CrB;AAED,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/page_components/login.tsx"],"names":[],"mappings":"AA0BA;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,aAAa,EACb,cAAyC,EACzC,sBAAoD,EACpD,gBAAuB,EACvB,oBAA4B,EAC5B,qBAAqC,EACrC,cAAoB,EACpB,kBAAiD,EACjD,mBAAwC,EACxC,iBAAyC,EACzC,kBAAqC,EACrC,UAAU,GACX,GAAE,cAAmB,2CAuCrB;AAED,eAAe,SAAS,CAAC"}