hazo_auth 6.1.1 → 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 (192) hide show
  1. package/README.md +163 -8
  2. package/SETUP_CHECKLIST.md +148 -0
  3. package/cli-src/lib/auth/nextauth_config.ts +101 -1
  4. package/cli-src/lib/email_verification_config.server.ts +0 -34
  5. package/cli-src/lib/forgot_password_config.server.ts +0 -34
  6. package/cli-src/lib/login_config.server.ts +0 -31
  7. package/cli-src/lib/my_settings_config.server.ts +0 -3
  8. package/cli-src/lib/oauth_config.server.ts +58 -0
  9. package/cli-src/lib/register_config.server.ts +11 -31
  10. package/cli-src/lib/reset_password_config.server.ts +0 -31
  11. package/cli-src/lib/services/oauth_service.ts +197 -0
  12. package/config/hazo_auth_config.example.ini +38 -41
  13. package/dist/components/layouts/create_firm/index.d.ts +4 -8
  14. package/dist/components/layouts/create_firm/index.d.ts.map +1 -1
  15. package/dist/components/layouts/create_firm/index.js +3 -3
  16. package/dist/components/layouts/email_verification/index.d.ts +4 -5
  17. package/dist/components/layouts/email_verification/index.d.ts.map +1 -1
  18. package/dist/components/layouts/email_verification/index.js +4 -4
  19. package/dist/components/layouts/forgot_password/index.d.ts +4 -5
  20. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  21. package/dist/components/layouts/forgot_password/index.js +2 -2
  22. package/dist/components/layouts/login/index.d.ts +13 -9
  23. package/dist/components/layouts/login/index.d.ts.map +1 -1
  24. package/dist/components/layouts/login/index.js +12 -6
  25. package/dist/components/layouts/otp/index.d.ts +8 -1
  26. package/dist/components/layouts/otp/index.d.ts.map +1 -1
  27. package/dist/components/layouts/otp/index.js +4 -2
  28. package/dist/components/layouts/register/index.d.ts +11 -7
  29. package/dist/components/layouts/register/index.d.ts.map +1 -1
  30. package/dist/components/layouts/register/index.js +8 -4
  31. package/dist/components/layouts/reset_password/index.d.ts +4 -5
  32. package/dist/components/layouts/reset_password/index.d.ts.map +1 -1
  33. package/dist/components/layouts/reset_password/index.js +5 -5
  34. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts +3 -5
  35. package/dist/components/layouts/shared/components/already_logged_in_guard.d.ts.map +1 -1
  36. package/dist/components/layouts/shared/components/already_logged_in_guard.js +2 -2
  37. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts +25 -0
  38. package/dist/components/layouts/shared/components/facebook_sign_in_button.d.ts.map +1 -0
  39. package/dist/components/layouts/shared/components/facebook_sign_in_button.js +49 -0
  40. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +1 -1
  41. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts +3 -6
  42. package/dist/components/layouts/shared/components/two_column_auth_layout.d.ts.map +1 -1
  43. package/dist/components/layouts/shared/components/two_column_auth_layout.js +8 -5
  44. package/dist/consent/consent_state.d.ts +18 -0
  45. package/dist/consent/consent_state.d.ts.map +1 -0
  46. package/dist/consent/consent_state.js +29 -0
  47. package/dist/consent/cookie_consent_banner.d.ts +11 -0
  48. package/dist/consent/cookie_consent_banner.d.ts.map +1 -0
  49. package/dist/consent/cookie_consent_banner.js +40 -0
  50. package/dist/consent/gtm_mapping.d.ts +13 -0
  51. package/dist/consent/gtm_mapping.d.ts.map +1 -0
  52. package/dist/consent/gtm_mapping.js +30 -0
  53. package/dist/consent/index.d.ts +7 -0
  54. package/dist/consent/index.d.ts.map +1 -0
  55. package/dist/consent/index.js +7 -0
  56. package/dist/consent/manage_modal.d.ts +2 -0
  57. package/dist/consent/manage_modal.d.ts.map +1 -0
  58. package/dist/consent/manage_modal.js +33 -0
  59. package/dist/consent/read_consent.d.ts +15 -0
  60. package/dist/consent/read_consent.d.ts.map +1 -0
  61. package/dist/consent/read_consent.js +23 -0
  62. package/dist/consent/use_consent.d.ts +7 -0
  63. package/dist/consent/use_consent.d.ts.map +1 -0
  64. package/dist/consent/use_consent.js +55 -0
  65. package/dist/lib/auth/nextauth_config.d.ts +10 -0
  66. package/dist/lib/auth/nextauth_config.d.ts.map +1 -1
  67. package/dist/lib/auth/nextauth_config.js +80 -2
  68. package/dist/lib/email_verification_config.server.d.ts +0 -3
  69. package/dist/lib/email_verification_config.server.d.ts.map +1 -1
  70. package/dist/lib/email_verification_config.server.js +0 -15
  71. package/dist/lib/forgot_password_config.server.d.ts +0 -3
  72. package/dist/lib/forgot_password_config.server.d.ts.map +1 -1
  73. package/dist/lib/forgot_password_config.server.js +0 -15
  74. package/dist/lib/login_config.server.d.ts +0 -3
  75. package/dist/lib/login_config.server.d.ts.map +1 -1
  76. package/dist/lib/login_config.server.js +0 -13
  77. package/dist/lib/my_settings_config.server.d.ts +0 -1
  78. package/dist/lib/my_settings_config.server.d.ts.map +1 -1
  79. package/dist/lib/my_settings_config.server.js +0 -2
  80. package/dist/lib/oauth_config.server.d.ts +17 -0
  81. package/dist/lib/oauth_config.server.d.ts.map +1 -1
  82. package/dist/lib/oauth_config.server.js +25 -0
  83. package/dist/lib/register_config.server.d.ts +2 -3
  84. package/dist/lib/register_config.server.d.ts.map +1 -1
  85. package/dist/lib/register_config.server.js +4 -13
  86. package/dist/lib/reset_password_config.server.d.ts +0 -3
  87. package/dist/lib/reset_password_config.server.d.ts.map +1 -1
  88. package/dist/lib/reset_password_config.server.js +0 -13
  89. package/dist/lib/services/oauth_service.d.ts +24 -0
  90. package/dist/lib/services/oauth_service.d.ts.map +1 -1
  91. package/dist/lib/services/oauth_service.js +155 -0
  92. package/dist/page_components/create_firm.d.ts +13 -1
  93. package/dist/page_components/create_firm.d.ts.map +1 -1
  94. package/dist/page_components/create_firm.js +10 -6
  95. package/dist/page_components/forgot_password.d.ts +1 -4
  96. package/dist/page_components/forgot_password.d.ts.map +1 -1
  97. package/dist/page_components/forgot_password.js +2 -6
  98. package/dist/page_components/login.d.ts +1 -4
  99. package/dist/page_components/login.d.ts.map +1 -1
  100. package/dist/page_components/login.js +2 -6
  101. package/dist/page_components/register.d.ts +1 -4
  102. package/dist/page_components/register.d.ts.map +1 -1
  103. package/dist/page_components/register.js +2 -6
  104. package/dist/page_components/reset_password.d.ts +1 -4
  105. package/dist/page_components/reset_password.d.ts.map +1 -1
  106. package/dist/page_components/reset_password.js +2 -6
  107. package/dist/page_components/verify_email.d.ts +1 -4
  108. package/dist/page_components/verify_email.d.ts.map +1 -1
  109. package/dist/page_components/verify_email.js +2 -6
  110. package/dist/server/routes/index.d.ts +1 -0
  111. package/dist/server/routes/index.d.ts.map +1 -1
  112. package/dist/server/routes/index.js +1 -0
  113. package/dist/server/routes/oauth_facebook_callback.d.ts +8 -0
  114. package/dist/server/routes/oauth_facebook_callback.d.ts.map +1 -0
  115. package/dist/server/routes/oauth_facebook_callback.js +157 -0
  116. package/dist/server/routes/oauth_google_callback.js +1 -1
  117. package/dist/server_pages/forgot_password.d.ts +13 -17
  118. package/dist/server_pages/forgot_password.d.ts.map +1 -1
  119. package/dist/server_pages/forgot_password.js +12 -8
  120. package/dist/server_pages/forgot_password_client_wrapper.d.ts +7 -6
  121. package/dist/server_pages/forgot_password_client_wrapper.d.ts.map +1 -1
  122. package/dist/server_pages/forgot_password_client_wrapper.js +2 -2
  123. package/dist/server_pages/login.d.ts +22 -21
  124. package/dist/server_pages/login.d.ts.map +1 -1
  125. package/dist/server_pages/login.js +15 -19
  126. package/dist/server_pages/login_client_wrapper.d.ts +10 -6
  127. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  128. package/dist/server_pages/login_client_wrapper.js +2 -2
  129. package/dist/server_pages/my_settings.d.ts +2 -0
  130. package/dist/server_pages/my_settings.d.ts.map +1 -1
  131. package/dist/server_pages/my_settings.js +8 -2
  132. package/dist/server_pages/otp.d.ts +16 -2
  133. package/dist/server_pages/otp.d.ts.map +1 -1
  134. package/dist/server_pages/otp.js +10 -3
  135. package/dist/server_pages/register.d.ts +19 -16
  136. package/dist/server_pages/register.d.ts.map +1 -1
  137. package/dist/server_pages/register.js +15 -12
  138. package/dist/server_pages/register_client_wrapper.d.ts +10 -6
  139. package/dist/server_pages/register_client_wrapper.d.ts.map +1 -1
  140. package/dist/server_pages/register_client_wrapper.js +2 -2
  141. package/dist/server_pages/reset_password.d.ts +11 -16
  142. package/dist/server_pages/reset_password.d.ts.map +1 -1
  143. package/dist/server_pages/reset_password.js +11 -9
  144. package/dist/server_pages/reset_password_client_wrapper.d.ts +7 -6
  145. package/dist/server_pages/reset_password_client_wrapper.d.ts.map +1 -1
  146. package/dist/server_pages/reset_password_client_wrapper.js +2 -2
  147. package/dist/server_pages/verify_email.d.ts +11 -17
  148. package/dist/server_pages/verify_email.d.ts.map +1 -1
  149. package/dist/server_pages/verify_email.js +11 -8
  150. package/dist/server_pages/verify_email_client_wrapper.d.ts +7 -6
  151. package/dist/server_pages/verify_email_client_wrapper.d.ts.map +1 -1
  152. package/dist/server_pages/verify_email_client_wrapper.js +2 -2
  153. package/dist/strings/default_strings.d.ts +47 -0
  154. package/dist/strings/default_strings.d.ts.map +1 -0
  155. package/dist/strings/default_strings.js +18 -0
  156. package/dist/strings/index.d.ts +4 -0
  157. package/dist/strings/index.d.ts.map +1 -0
  158. package/dist/strings/index.js +3 -0
  159. package/dist/strings/strings_context.d.ts +12 -0
  160. package/dist/strings/strings_context.d.ts.map +1 -0
  161. package/dist/strings/strings_context.js +23 -0
  162. package/dist/strings/strings_provider.d.ts +26 -0
  163. package/dist/strings/strings_provider.d.ts.map +1 -0
  164. package/dist/strings/strings_provider.js +45 -0
  165. package/dist/theme/create_theme.d.ts +7 -0
  166. package/dist/theme/create_theme.d.ts.map +1 -0
  167. package/dist/theme/create_theme.js +97 -0
  168. package/dist/theme/hex_to_hsl.d.ts +16 -0
  169. package/dist/theme/hex_to_hsl.d.ts.map +1 -0
  170. package/dist/theme/hex_to_hsl.js +110 -0
  171. package/dist/theme/index.d.ts +4 -0
  172. package/dist/theme/index.d.ts.map +1 -0
  173. package/dist/theme/index.js +3 -0
  174. package/dist/theme/luminance.d.ts +11 -0
  175. package/dist/theme/luminance.d.ts.map +1 -0
  176. package/dist/theme/luminance.js +45 -0
  177. package/dist/theme/theme_provider.d.ts +14 -0
  178. package/dist/theme/theme_provider.d.ts.map +1 -0
  179. package/dist/theme/theme_provider.js +23 -0
  180. package/dist/theme/theme_types.d.ts +36 -0
  181. package/dist/theme/theme_types.d.ts.map +1 -0
  182. package/dist/theme/theme_types.js +1 -0
  183. package/dist/themes/index.d.ts +3 -0
  184. package/dist/themes/index.d.ts.map +1 -0
  185. package/dist/themes/index.js +2 -0
  186. package/dist/themes/preset_indigo_sunset.d.ts +3 -0
  187. package/dist/themes/preset_indigo_sunset.d.ts.map +1 -0
  188. package/dist/themes/preset_indigo_sunset.js +20 -0
  189. package/dist/themes/preset_neutral.d.ts +3 -0
  190. package/dist/themes/preset_neutral.d.ts.map +1 -0
  191. package/dist/themes/preset_neutral.js +14 -0
  192. package/package.json +19 -2
@@ -34,7 +34,6 @@ export type MySettingsConfig = {
34
34
  user_photo_default_priority2?: "library" | "gravatar";
35
35
  library_photo_path: string;
36
36
  };
37
- heading?: string;
38
37
  subHeading?: string;
39
38
  profilePhotoLabel?: string;
40
39
  profilePhotoRecommendation?: string;
@@ -95,7 +94,6 @@ export function get_my_settings_config(): MySettingsConfig {
95
94
  const fileTypes = get_file_types_config();
96
95
 
97
96
  // Read optional labels with defaults
98
- const heading = get_config_value(section, "heading", "Account Settings");
99
97
  const subHeading = get_config_value(section, "sub_heading", "Manage your profile, password, and email preferences.");
100
98
  const profilePhotoLabel = get_config_value(section, "profile_photo_label", "Profile Photo");
101
99
  const profilePhotoRecommendation = get_config_value(section, "profile_photo_recommendation", "Recommended size: 200x200px. JPG, PNG.");
@@ -115,7 +113,6 @@ export function get_my_settings_config(): MySettingsConfig {
115
113
  userFields,
116
114
  passwordRequirements,
117
115
  profilePicture,
118
- heading,
119
116
  subHeading,
120
117
  profilePhotoLabel,
121
118
  profilePhotoRecommendation,
@@ -10,12 +10,20 @@ import { DEFAULT_OAUTH } from "./config/default_config.js";
10
10
  export type OAuthConfig = {
11
11
  /** Enable Google OAuth login */
12
12
  enable_google: boolean;
13
+ /** Enable Facebook OAuth login */
14
+ enable_facebook: boolean;
13
15
  /** Enable traditional email/password login */
14
16
  enable_email_password: boolean;
15
17
  /** Auto-link Google login to existing unverified email/password accounts */
16
18
  auto_link_unverified_accounts: boolean;
19
+ /** Auto-link Google login to existing unverified email/password accounts (per-provider override) */
20
+ auto_link_unverified_accounts_google: boolean;
21
+ /** Auto-link Facebook login to existing unverified email/password accounts */
22
+ auto_link_unverified_accounts_facebook: boolean;
17
23
  /** Text displayed on the Google sign-in button */
18
24
  google_button_text: string;
25
+ /** Text displayed on the Facebook sign-in button */
26
+ facebook_button_text: string;
19
27
  /** Text displayed on the divider between OAuth and email/password form */
20
28
  oauth_divider_text: string;
21
29
  /** NextAuth signIn page path */
@@ -30,6 +38,10 @@ export type OAuthConfig = {
30
38
  skip_invitation_check: boolean;
31
39
  /** Redirect when skip_invitation_check=true and user has no scope */
32
40
  no_scope_redirect: string;
41
+ /** Facebook App ID from env HAZO_AUTH_FACEBOOK_APP_ID */
42
+ facebook_client_id: string | undefined;
43
+ /** Facebook App Secret from env HAZO_AUTH_FACEBOOK_APP_SECRET */
44
+ facebook_client_secret: string | undefined;
33
45
  };
34
46
 
35
47
  // section: constants
@@ -48,24 +60,51 @@ export function get_oauth_config(): OAuthConfig {
48
60
  DEFAULT_OAUTH.enable_google
49
61
  );
50
62
 
63
+ const enable_facebook = get_config_boolean(
64
+ SECTION_NAME,
65
+ "enable_facebook",
66
+ false
67
+ );
68
+
51
69
  const enable_email_password = get_config_boolean(
52
70
  SECTION_NAME,
53
71
  "enable_email_password",
54
72
  DEFAULT_OAUTH.enable_email_password
55
73
  );
56
74
 
75
+ // Generic key (backward compat)
57
76
  const auto_link_unverified_accounts = get_config_boolean(
58
77
  SECTION_NAME,
59
78
  "auto_link_unverified_accounts",
60
79
  DEFAULT_OAUTH.auto_link_unverified_accounts
61
80
  );
62
81
 
82
+ // Per-provider Google key (falls back to generic)
83
+ const auto_link_unverified_accounts_google = get_config_boolean(
84
+ SECTION_NAME,
85
+ "auto_link_unverified_accounts_google",
86
+ auto_link_unverified_accounts
87
+ );
88
+
89
+ // Per-provider Facebook key (defaults to false — don't auto-link Facebook unverified)
90
+ const auto_link_unverified_accounts_facebook = get_config_boolean(
91
+ SECTION_NAME,
92
+ "auto_link_unverified_accounts_facebook",
93
+ false
94
+ );
95
+
63
96
  const google_button_text = get_config_value(
64
97
  SECTION_NAME,
65
98
  "google_button_text",
66
99
  DEFAULT_OAUTH.google_button_text
67
100
  );
68
101
 
102
+ const facebook_button_text = get_config_value(
103
+ SECTION_NAME,
104
+ "facebook_button_text",
105
+ "Continue with Facebook"
106
+ );
107
+
69
108
  const oauth_divider_text = get_config_value(
70
109
  SECTION_NAME,
71
110
  "oauth_divider_text",
@@ -108,11 +147,18 @@ export function get_oauth_config(): OAuthConfig {
108
147
  DEFAULT_OAUTH.no_scope_redirect
109
148
  );
110
149
 
150
+ const facebook_client_id = process.env.HAZO_AUTH_FACEBOOK_APP_ID;
151
+ const facebook_client_secret = process.env.HAZO_AUTH_FACEBOOK_APP_SECRET;
152
+
111
153
  return {
112
154
  enable_google,
155
+ enable_facebook,
113
156
  enable_email_password,
114
157
  auto_link_unverified_accounts,
158
+ auto_link_unverified_accounts_google,
159
+ auto_link_unverified_accounts_facebook,
115
160
  google_button_text,
161
+ facebook_button_text,
116
162
  oauth_divider_text,
117
163
  sign_in_page,
118
164
  error_page,
@@ -120,6 +166,8 @@ export function get_oauth_config(): OAuthConfig {
120
166
  default_redirect,
121
167
  skip_invitation_check,
122
168
  no_scope_redirect,
169
+ facebook_client_id,
170
+ facebook_client_secret,
123
171
  };
124
172
  }
125
173
 
@@ -142,3 +190,13 @@ export function is_email_password_enabled(): boolean {
142
190
  DEFAULT_OAUTH.enable_email_password
143
191
  );
144
192
  }
193
+
194
+ /**
195
+ * Helper to check if Facebook OAuth is enabled and credentials are present
196
+ * @returns true if Facebook OAuth is enabled and env vars are set
197
+ */
198
+ export function is_facebook_oauth_enabled(): boolean {
199
+ const enabled = get_config_boolean(SECTION_NAME, "enable_facebook", false);
200
+ if (!enabled) return false;
201
+ return !!(process.env.HAZO_AUTH_FACEBOOK_APP_ID && process.env.HAZO_AUTH_FACEBOOK_APP_SECRET);
202
+ }
@@ -9,11 +9,6 @@ import { get_already_logged_in_config } from "./already_logged_in_config.server.
9
9
  import { get_user_fields_config } from "./user_fields_config.server.js";
10
10
  import { get_oauth_config } from "./oauth_config.server.js";
11
11
 
12
- // Default image path - consuming apps should either:
13
- // 1. Configure their own image_src in hazo_auth_config.ini
14
- // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
15
- const DEFAULT_REGISTER_IMAGE_PATH = "/hazo_auth/images/register_default.jpg";
16
-
17
12
  // section: types
18
13
  export type RegisterConfig = {
19
14
  showNameField: boolean;
@@ -31,14 +26,13 @@ export type RegisterConfig = {
31
26
  returnHomePath: string;
32
27
  signInPath: string;
33
28
  signInLabel: string;
34
- imageSrc: string;
35
- imageAlt: string;
36
- imageBackgroundColor: string;
37
29
  /** OAuth configuration */
38
30
  oauth: {
39
31
  enable_google: boolean;
32
+ enable_facebook: boolean;
40
33
  enable_email_password: boolean;
41
34
  google_button_text: string;
35
+ facebook_button_text: string;
42
36
  oauth_divider_text: string;
43
37
  };
44
38
  };
@@ -77,26 +71,6 @@ export function get_register_config(): RegisterConfig {
77
71
  "Sign in"
78
72
  );
79
73
 
80
- // Read image configuration
81
- // If not set in config, falls back to default path-based image
82
- // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
83
- const imageSrc = get_config_value(
84
- "hazo_auth__register_layout",
85
- "image_src",
86
- DEFAULT_REGISTER_IMAGE_PATH
87
- );
88
-
89
- const imageAlt = get_config_value(
90
- "hazo_auth__register_layout",
91
- "image_alt",
92
- "Modern building representing user registration"
93
- );
94
- const imageBackgroundColor = get_config_value(
95
- "hazo_auth__register_layout",
96
- "image_background_color",
97
- "#e2e8f0"
98
- );
99
-
100
74
  // Get OAuth configuration (shared with login)
101
75
  const oauthConfig = get_oauth_config();
102
76
 
@@ -107,6 +81,13 @@ export function get_register_config(): RegisterConfig {
107
81
  "Sign up with Google"
108
82
  );
109
83
 
84
+ // For the register page, default Facebook button text to "Sign up with Facebook" unless overridden
85
+ const registerFacebookButtonText = get_config_value(
86
+ "hazo_auth__oauth",
87
+ "facebook_button_text_register",
88
+ "Sign up with Facebook"
89
+ );
90
+
110
91
  return {
111
92
  showNameField,
112
93
  passwordRequirements,
@@ -117,13 +98,12 @@ export function get_register_config(): RegisterConfig {
117
98
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
118
99
  signInPath,
119
100
  signInLabel,
120
- imageSrc,
121
- imageAlt,
122
- imageBackgroundColor,
123
101
  oauth: {
124
102
  enable_google: oauthConfig.enable_google,
103
+ enable_facebook: oauthConfig.enable_facebook,
125
104
  enable_email_password: oauthConfig.enable_email_password,
126
105
  google_button_text: registerGoogleButtonText,
106
+ facebook_button_text: registerFacebookButtonText,
127
107
  oauth_divider_text: oauthConfig.oauth_divider_text,
128
108
  },
129
109
  };
@@ -7,11 +7,6 @@ import { get_config_value } from "./config/config_loader.server.js";
7
7
  import { get_already_logged_in_config } from "./already_logged_in_config.server.js";
8
8
  import { get_password_requirements_config } from "./password_requirements_config.server.js";
9
9
 
10
- // Default image path - consuming apps should either:
11
- // 1. Configure their own image_src in hazo_auth_config.ini
12
- // 2. Copy the default images from node_modules/hazo_auth/public/hazo_auth/images/ to their public folder
13
- const DEFAULT_RESET_PASSWORD_IMAGE_PATH = "/hazo_auth/images/reset_password_default.jpg";
14
-
15
10
  // section: types
16
11
  export type ResetPasswordConfig = {
17
12
  errorMessage: string;
@@ -30,9 +25,6 @@ export type ResetPasswordConfig = {
30
25
  require_number: boolean;
31
26
  require_special: boolean;
32
27
  };
33
- imageSrc: string;
34
- imageAlt: string;
35
- imageBackgroundColor: string;
36
28
  };
37
29
 
38
30
  // section: helpers
@@ -70,26 +62,6 @@ export function get_reset_password_config(): ResetPasswordConfig {
70
62
  // Get shared password requirements
71
63
  const passwordRequirements = get_password_requirements_config();
72
64
 
73
- // Read image configuration
74
- // If not set in config, falls back to default path-based image
75
- // Consuming apps should copy images to public/hazo_auth/images/ or configure their own image_src
76
- const imageSrc = get_config_value(
77
- section,
78
- "image_src",
79
- DEFAULT_RESET_PASSWORD_IMAGE_PATH
80
- );
81
-
82
- const imageAlt = get_config_value(
83
- section,
84
- "image_alt",
85
- "Reset password illustration"
86
- );
87
- const imageBackgroundColor = get_config_value(
88
- section,
89
- "image_background_color",
90
- "#f1f5f9"
91
- );
92
-
93
65
  return {
94
66
  errorMessage,
95
67
  successMessage,
@@ -101,9 +73,6 @@ export function get_reset_password_config(): ResetPasswordConfig {
101
73
  returnHomeButtonLabel: alreadyLoggedInConfig.returnHomeButtonLabel,
102
74
  returnHomePath: alreadyLoggedInConfig.returnHomePath,
103
75
  passwordRequirements,
104
- imageSrc,
105
- imageAlt,
106
- imageBackgroundColor,
107
76
  };
108
77
  }
109
78
 
@@ -36,6 +36,18 @@ export type OAuthLoginResult = {
36
36
  error?: string;
37
37
  };
38
38
 
39
+ export type FacebookOAuthData = {
40
+ /** Facebook's unique user ID */
41
+ facebook_id: string;
42
+ /** User's email address from Facebook (may be null if user denied email permission) */
43
+ email: string | null;
44
+ /** User's full name from Facebook profile */
45
+ name?: string;
46
+ /** User's profile picture URL from Facebook */
47
+ profile_picture_url?: string;
48
+ // NOTE: no email_verified — we never trust Facebook's email_verified claim
49
+ };
50
+
39
51
  export type LinkGoogleResult = {
40
52
  success: boolean;
41
53
  error?: string;
@@ -241,6 +253,191 @@ export async function handle_google_oauth_login(
241
253
  }
242
254
  }
243
255
 
256
+ /**
257
+ * Handles Facebook OAuth login/registration flow
258
+ * 1. Check if user exists with facebook_id -> login
259
+ * 2. Check if user exists with email -> link Facebook account (respects auto_link_unverified)
260
+ * 3. Create new user with Facebook data (email_verified always false — never trust Facebook)
261
+ *
262
+ * @param adapter - The hazo_connect adapter instance
263
+ * @param data - Facebook OAuth user data
264
+ * @param opts - Options (auto_link_unverified: whether to link unverified accounts)
265
+ * @returns OAuth login result with user_id and status flags
266
+ */
267
+ export async function handle_facebook_oauth_login(
268
+ adapter: HazoConnectAdapter,
269
+ data: FacebookOAuthData,
270
+ opts?: { auto_link_unverified?: boolean }
271
+ ): Promise<OAuthLoginResult> {
272
+ const logger = create_app_logger();
273
+
274
+ try {
275
+ const { facebook_id, email, name, profile_picture_url } = data;
276
+
277
+ const users_service = createCrudService(adapter, "hazo_users");
278
+ const now = new Date().toISOString();
279
+
280
+ // Step 1: Check if user exists with this facebook_id
281
+ const users_by_facebook_id = await users_service.findBy({ facebook_id });
282
+
283
+ if (Array.isArray(users_by_facebook_id) && users_by_facebook_id.length > 0) {
284
+ const user = users_by_facebook_id[0];
285
+
286
+ await users_service.updateById(user.id, {
287
+ last_logon: now,
288
+ changed_at: now,
289
+ });
290
+
291
+ logger.info("oauth_service_facebook_login_existing_facebook_user", {
292
+ filename: "oauth_service.ts",
293
+ line_number: get_line_number(),
294
+ user_id: user.id,
295
+ email: user.email_address,
296
+ });
297
+
298
+ return {
299
+ success: true,
300
+ user_id: user.id as string,
301
+ is_new_user: false,
302
+ was_linked: false,
303
+ email: user.email_address as string,
304
+ name: user.name as string | undefined,
305
+ };
306
+ }
307
+
308
+ // Step 2: Check if user exists with this email
309
+ if (email) {
310
+ const users_by_email = await users_service.findBy({ email_address: email });
311
+
312
+ if (Array.isArray(users_by_email) && users_by_email.length > 0) {
313
+ const user = users_by_email[0];
314
+ const user_email_verified = user.email_verified as boolean;
315
+
316
+ if (!user_email_verified) {
317
+ if (!opts?.auto_link_unverified) {
318
+ return {
319
+ success: false,
320
+ error: "link_blocked_unverified",
321
+ };
322
+ }
323
+ // auto_link_unverified=true: link but do NOT change email_verified status
324
+ }
325
+
326
+ // Link Facebook account to existing user
327
+ const current_auth_providers = (user.auth_providers as string) || "email";
328
+ const new_auth_providers = current_auth_providers.includes("facebook")
329
+ ? current_auth_providers
330
+ : `${current_auth_providers},facebook`;
331
+
332
+ const update_data: Record<string, unknown> = {
333
+ facebook_id,
334
+ auth_providers: new_auth_providers,
335
+ last_logon: now,
336
+ changed_at: now,
337
+ };
338
+
339
+ // Update name if not set and Facebook provides one
340
+ if (!user.name && name) {
341
+ update_data.name = name;
342
+ }
343
+
344
+ // Update profile picture if not set and Facebook provides one
345
+ if (!user.profile_picture_url && profile_picture_url) {
346
+ update_data.profile_picture_url = profile_picture_url;
347
+ update_data.profile_source = "custom";
348
+ }
349
+
350
+ await users_service.updateById(user.id, update_data);
351
+
352
+ logger.info("oauth_service_facebook_linked_to_existing", {
353
+ filename: "oauth_service.ts",
354
+ line_number: get_line_number(),
355
+ user_id: user.id,
356
+ email,
357
+ was_unverified: !user_email_verified,
358
+ });
359
+
360
+ return {
361
+ success: true,
362
+ user_id: user.id as string,
363
+ is_new_user: false,
364
+ was_linked: true,
365
+ email: user.email_address as string,
366
+ name: (update_data.name as string) || (user.name as string | undefined),
367
+ };
368
+ }
369
+ }
370
+
371
+ // Step 3: Create new user with Facebook data
372
+ const user_id = randomUUID();
373
+
374
+ const insert_data: Record<string, unknown> = {
375
+ id: user_id,
376
+ email_address: email,
377
+ password_hash: "", // Empty string for Facebook-only users
378
+ email_verified: false, // Never trust Facebook's email_verified claim
379
+ status: "ACTIVE",
380
+ login_attempts: 0,
381
+ facebook_id,
382
+ auth_providers: "facebook",
383
+ created_at: now,
384
+ changed_at: now,
385
+ last_logon: now,
386
+ };
387
+
388
+ if (name) {
389
+ insert_data.name = name;
390
+ }
391
+
392
+ if (profile_picture_url) {
393
+ insert_data.profile_picture_url = profile_picture_url;
394
+ insert_data.profile_source = "custom";
395
+ }
396
+
397
+ const inserted_users = await users_service.insert(insert_data);
398
+
399
+ if (!Array.isArray(inserted_users) || inserted_users.length === 0) {
400
+ return {
401
+ success: false,
402
+ error: "Failed to create user account",
403
+ };
404
+ }
405
+
406
+ logger.info("oauth_service_facebook_new_user_created", {
407
+ filename: "oauth_service.ts",
408
+ line_number: get_line_number(),
409
+ user_id,
410
+ email,
411
+ });
412
+
413
+ return {
414
+ success: true,
415
+ user_id,
416
+ is_new_user: true,
417
+ was_linked: false,
418
+ email: email ?? undefined,
419
+ name,
420
+ };
421
+ } catch (error) {
422
+ const user_friendly_error = sanitize_error_for_user(error, {
423
+ logToConsole: true,
424
+ logToLogger: true,
425
+ logger,
426
+ context: {
427
+ filename: "oauth_service.ts",
428
+ line_number: get_line_number(),
429
+ email: data.email,
430
+ operation: "handle_facebook_oauth_login",
431
+ },
432
+ });
433
+
434
+ return {
435
+ success: false,
436
+ error: user_friendly_error,
437
+ };
438
+ }
439
+ }
440
+
244
441
  /**
245
442
  * Links a Google account to an existing user
246
443
  * @param adapter - The hazo_connect adapter instance
@@ -74,11 +74,9 @@ enable_admin_ui = true
74
74
  # image_alt = Illustration of a globe representing secure authentication workflows
75
75
  # image_background_color = #e2e8f0
76
76
 
77
- # Labels
78
- # heading = Create your hazo account
79
- # sub_heading = Secure your access with editable fields powered by shadcn components.
80
- # submit_button = Register
81
- # cancel_button = Cancel
77
+ ; Page text is no longer configured via INI.
78
+ ; Use HazoAuthStringsProvider or per-page props instead.
79
+ ; See MIGRATION.md for details.
82
80
 
83
81
  # Field labels (defaults shown, uncomment to override)
84
82
  # name_label = Full name
@@ -129,11 +127,9 @@ enable_admin_ui = true
129
127
  # image_alt = Illustration of a globe representing secure authentication workflows
130
128
  # image_background_color = #e2e8f0
131
129
 
132
- # Labels
133
- # heading = Sign in to your account
134
- # sub_heading = Enter your credentials to access your secure workspace.
135
- # submit_button = Login
136
- # cancel_button = Cancel
130
+ ; Page text is no longer configured via INI.
131
+ ; Use HazoAuthStringsProvider or per-page props instead.
132
+ ; See MIGRATION.md for details.
137
133
 
138
134
  # Field labels (defaults shown, uncomment to override)
139
135
  # email_label = Email address
@@ -170,11 +166,9 @@ otp_signin_href = /hazo_auth/otp
170
166
  # image_alt = Illustration of a globe representing secure authentication workflows
171
167
  # image_background_color = #e2e8f0
172
168
 
173
- # Labels
174
- # heading = Forgot your password?
175
- # sub_heading = Enter your email address and we'll send you a link to reset your password.
176
- # submit_button = Send reset link
177
- # cancel_button = Cancel
169
+ ; Page text is no longer configured via INI.
170
+ ; Use HazoAuthStringsProvider or per-page props instead.
171
+ ; See MIGRATION.md for details.
178
172
 
179
173
  [hazo_auth__reset_password_layout]
180
174
  # Image configuration
@@ -182,11 +176,9 @@ otp_signin_href = /hazo_auth/otp
182
176
  # image_alt = Illustration of a globe representing secure authentication workflows
183
177
  # image_background_color = #e2e8f0
184
178
 
185
- # Labels
186
- # heading = Reset your password
187
- # sub_heading = Enter your new password below.
188
- # submit_button = Reset password
189
- # cancel_button = Cancel
179
+ ; Page text is no longer configured via INI.
180
+ ; Use HazoAuthStringsProvider or per-page props instead.
181
+ ; See MIGRATION.md for details.
190
182
 
191
183
  # Field labels (defaults shown, uncomment to override)
192
184
  # password_label = New password
@@ -218,22 +210,9 @@ otp_signin_href = /hazo_auth/otp
218
210
  # image_alt = Illustration of a globe representing secure authentication workflows
219
211
  # image_background_color = #e2e8f0
220
212
 
221
- # Labels (verifying state)
222
- # heading = Email verification
223
- # sub_heading = Verifying your email address...
224
- # submit_button = Resend verification email
225
- # cancel_button = Cancel
226
-
227
- # Success labels
228
- # success_heading = Email verified successfully
229
- # success_message = Your email address has been verified. You can now log in to your account.
230
- # success_redirect_message = Redirecting to login page in
231
- # success_go_to_login_button = Go to login
232
-
233
- # Error labels
234
- # error_heading = Verification failed
235
- # error_message = The verification link is invalid or has expired.
236
- # error_resend_form_heading = Resend verification email
213
+ ; Page text is no longer configured via INI.
214
+ ; Use HazoAuthStringsProvider or per-page props instead.
215
+ ; See MIGRATION.md for details.
237
216
 
238
217
  # Field labels (defaults shown, uncomment to override)
239
218
  # email_label = Email address
@@ -275,11 +254,9 @@ otp_signin_href = /hazo_auth/otp
275
254
  # - [hazo_auth__password_requirements] for password validation rules
276
255
  # - [hazo_auth__profile_picture] for profile picture settings and prioritization
277
256
 
278
- # Heading
279
- # heading = Account Settings
280
-
281
- # Sub heading
282
- # sub_heading = Manage your profile, password, and email preferences.
257
+ ; Page heading text is no longer configured via INI.
258
+ ; Use HazoAuthStringsProvider or the title prop on MySettingsPage instead.
259
+ ; See MIGRATION.md for details.
283
260
 
284
261
  # Profile Photo section
285
262
  # profile_photo_label = Profile Photo
@@ -602,6 +579,26 @@ application_permission_list_defaults = admin_user_management,admin_role_manageme
602
579
  # Redirect when skip_invitation_check=true and user has no scope
603
580
  # no_scope_redirect = /
604
581
 
582
+ [hazo_auth__create_firm]
583
+ ; heading, sub_heading, and submit_button_label are no longer configured via INI in v7.0.0.
584
+ ; Use HazoAuthStringsProvider or the title/subtitle/ctaText props on CreateFirmPage instead.
585
+ ; See MIGRATION.md for details.
586
+
587
+ ; Firm name field label
588
+ # firm_name_label = Firm Name
589
+
590
+ ; Organisation structure field label
591
+ # org_structure_label = Organisation Structure
592
+
593
+ ; Default organisation structure value
594
+ # org_structure_default = Headquarters
595
+
596
+ ; Success message shown after firm creation
597
+ # success_message = Your firm has been created successfully!
598
+
599
+ ; Route to redirect to after firm creation
600
+ # redirect_route = /
601
+
605
602
  [hazo_auth__multi_tenancy]
606
603
  # Multi-tenancy configuration for organization hierarchy
607
604
  # Enables hierarchical organization structures for company-wide access control
@@ -1,12 +1,8 @@
1
- import type { StaticImageData } from "next/image";
2
1
  import { type ButtonPaletteOverrides } from "../shared/config/layout_customization.js";
2
+ import type { HazoAuthTheme } from "../../../theme/theme_types";
3
3
  export type CreateFirmLayoutProps = {
4
- /** Image source for the left panel */
5
- image_src: string | StaticImageData;
6
- /** Alt text for the image */
7
- image_alt?: string;
8
- /** Background color for the image panel */
9
- image_background_color?: string;
4
+ /** Theme that controls visual appearance and layout mode. */
5
+ theme?: HazoAuthTheme;
10
6
  /** Page heading */
11
7
  heading?: string;
12
8
  /** Page sub-heading */
@@ -39,6 +35,6 @@ export type CreateFirmLayoutProps = {
39
35
  error: (message: string, data?: Record<string, unknown>) => void;
40
36
  };
41
37
  };
42
- export default function CreateFirmLayout({ image_src, image_alt, image_background_color, heading, sub_heading, firm_name_label, firm_name_placeholder, org_structure_label, org_structure_placeholder, default_org_structure, submit_button_label, success_message, redirect_route, apiBasePath, onSuccess, button_colors, logger, }: CreateFirmLayoutProps): import("react/jsx-runtime").JSX.Element;
38
+ export default function CreateFirmLayout({ theme, heading, sub_heading, firm_name_label, firm_name_placeholder, org_structure_label, org_structure_placeholder, default_org_structure, submit_button_label, success_message, redirect_route, apiBasePath, onSuccess, button_colors, logger, }: CreateFirmLayoutProps): import("react/jsx-runtime").JSX.Element;
43
39
  export { CreateFirmLayout };
44
40
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/create_firm/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAOlD,OAAO,EACL,KAAK,sBAAsB,EAC5B,MAAM,uCAAuC,CAAC;AAQ/C,MAAM,MAAM,qBAAqB,GAAG;IAClC,sCAAsC;IACtC,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,6BAA6B;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2CAA2C;IAC3C,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oCAAoC;IACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8BAA8B;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,6BAA6B;IAC7B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAClE,CAAC;CACH,CAAC;AAGF,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,SAAS,EACT,SAA8B,EAC9B,sBAAkC,EAClC,OAA4B,EAC5B,WAAuD,EACvD,eAA6B,EAC7B,qBAA8C,EAC9C,mBAA8C,EAC9C,yBAA6D,EAC7D,qBAAsC,EACtC,mBAAmC,EACnC,eAA4D,EAC5D,cAAoB,EACpB,WAAW,EACX,SAAS,EACT,aAAa,EACb,MAAM,GACP,EAAE,qBAAqB,2CA2JvB;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/components/layouts/create_firm/index.tsx"],"names":[],"mappings":"AAYA,OAAO,EACL,KAAK,sBAAsB,EAC5B,MAAM,uCAAuC,CAAC;AAM/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAC;AAGhE,MAAM,MAAM,qBAAqB,GAAG;IAClC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,mBAAmB;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oCAAoC;IACpC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,0CAA0C;IAC1C,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,sCAAsC;IACtC,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,8BAA8B;IAC9B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,gDAAgD;IAChD,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,sCAAsC;IACtC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iDAAiD;IACjD,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,6BAA6B;IAC7B,aAAa,CAAC,EAAE,sBAAsB,CAAC;IACvC,2BAA2B;IAC3B,MAAM,CAAC,EAAE;QACP,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;QAChE,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;KAClE,CAAC;CACH,CAAC;AAGF,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,EACvC,KAAK,EACL,OAA4B,EAC5B,WAAuD,EACvD,eAA6B,EAC7B,qBAA8C,EAC9C,mBAA8C,EAC9C,yBAA6D,EAC7D,qBAAsC,EACtC,mBAAmC,EACnC,eAA4D,EAC5D,cAAoB,EACpB,WAAW,EACX,SAAS,EACT,aAAa,EACb,MAAM,GACP,EAAE,qBAAqB,2CAuJvB;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC"}