hazo_auth 4.1.0 → 4.3.0

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 (157) hide show
  1. package/README.md +230 -0
  2. package/SETUP_CHECKLIST.md +202 -0
  3. package/bin/hazo_auth.mjs +35 -0
  4. package/cli-src/assets/images/forgot_password_default.jpg +0 -0
  5. package/cli-src/assets/images/login_default.jpg +0 -0
  6. package/cli-src/assets/images/register_default.jpg +0 -0
  7. package/cli-src/assets/images/reset_password_default.jpg +0 -0
  8. package/cli-src/assets/images/verify_email_default.jpg +0 -0
  9. package/cli-src/cli/generate.ts +276 -0
  10. package/cli-src/cli/index.ts +207 -0
  11. package/cli-src/cli/init.ts +254 -0
  12. package/cli-src/cli/init_users.ts +376 -0
  13. package/cli-src/cli/validate.ts +581 -0
  14. package/cli-src/lib/already_logged_in_config.server.ts +46 -0
  15. package/cli-src/lib/app_logger.ts +24 -0
  16. package/cli-src/lib/auth/auth_cache.ts +220 -0
  17. package/cli-src/lib/auth/auth_rate_limiter.ts +121 -0
  18. package/cli-src/lib/auth/auth_types.ts +110 -0
  19. package/cli-src/lib/auth/auth_utils.server.ts +196 -0
  20. package/cli-src/lib/auth/hazo_get_auth.server.ts +512 -0
  21. package/cli-src/lib/auth/index.ts +23 -0
  22. package/cli-src/lib/auth/nextauth_config.ts +227 -0
  23. package/cli-src/lib/auth/scope_cache.ts +233 -0
  24. package/cli-src/lib/auth/server_auth.ts +88 -0
  25. package/cli-src/lib/auth/session_token_validator.edge.ts +91 -0
  26. package/cli-src/lib/auth_utility_config.server.ts +136 -0
  27. package/cli-src/lib/config/config_loader.server.ts +164 -0
  28. package/cli-src/lib/config/default_config.ts +199 -0
  29. package/cli-src/lib/email_verification_config.server.ts +63 -0
  30. package/cli-src/lib/file_types_config.server.ts +25 -0
  31. package/cli-src/lib/forgot_password_config.server.ts +63 -0
  32. package/cli-src/lib/hazo_connect_instance.server.ts +101 -0
  33. package/cli-src/lib/hazo_connect_setup.server.ts +194 -0
  34. package/cli-src/lib/hazo_connect_setup.ts +54 -0
  35. package/cli-src/lib/index.ts +46 -0
  36. package/cli-src/lib/login_config.server.ts +106 -0
  37. package/cli-src/lib/messages_config.server.ts +45 -0
  38. package/cli-src/lib/migrations/apply_migration.ts +105 -0
  39. package/cli-src/lib/my_settings_config.server.ts +135 -0
  40. package/cli-src/lib/oauth_config.server.ts +87 -0
  41. package/cli-src/lib/password_requirements_config.server.ts +40 -0
  42. package/cli-src/lib/profile_pic_menu_config.server.ts +138 -0
  43. package/cli-src/lib/profile_picture_config.server.ts +56 -0
  44. package/cli-src/lib/register_config.server.ts +101 -0
  45. package/cli-src/lib/reset_password_config.server.ts +103 -0
  46. package/cli-src/lib/scope_hierarchy_config.server.ts +151 -0
  47. package/cli-src/lib/services/email_service.ts +587 -0
  48. package/cli-src/lib/services/email_verification_service.ts +270 -0
  49. package/cli-src/lib/services/index.ts +16 -0
  50. package/cli-src/lib/services/login_service.ts +150 -0
  51. package/cli-src/lib/services/oauth_service.ts +494 -0
  52. package/cli-src/lib/services/password_change_service.ts +154 -0
  53. package/cli-src/lib/services/password_reset_service.ts +418 -0
  54. package/cli-src/lib/services/profile_picture_remove_service.ts +120 -0
  55. package/cli-src/lib/services/profile_picture_service.ts +451 -0
  56. package/cli-src/lib/services/profile_picture_source_mapper.ts +62 -0
  57. package/cli-src/lib/services/registration_service.ts +185 -0
  58. package/cli-src/lib/services/scope_labels_service.ts +348 -0
  59. package/cli-src/lib/services/scope_service.ts +778 -0
  60. package/cli-src/lib/services/session_token_service.ts +177 -0
  61. package/cli-src/lib/services/token_service.ts +240 -0
  62. package/cli-src/lib/services/user_profiles_cache.ts +189 -0
  63. package/cli-src/lib/services/user_profiles_service.ts +264 -0
  64. package/cli-src/lib/services/user_scope_service.ts +554 -0
  65. package/cli-src/lib/services/user_update_service.ts +141 -0
  66. package/cli-src/lib/ui_shell_config.server.ts +73 -0
  67. package/cli-src/lib/ui_sizes_config.server.ts +37 -0
  68. package/cli-src/lib/user_fields_config.server.ts +31 -0
  69. package/cli-src/lib/user_management_config.server.ts +39 -0
  70. package/cli-src/lib/user_profiles_config.server.ts +55 -0
  71. package/cli-src/lib/utils/api_route_helpers.ts +60 -0
  72. package/cli-src/lib/utils/error_sanitizer.ts +75 -0
  73. package/cli-src/lib/utils/password_validator.ts +65 -0
  74. package/cli-src/lib/utils.ts +11 -0
  75. package/cli-src/server/logging/logger_service.ts +56 -0
  76. package/dist/app/api/hazo_auth/forgot_password/route.d.ts.map +1 -1
  77. package/dist/app/api/hazo_auth/forgot_password/route.js +15 -0
  78. package/dist/app/api/hazo_auth/logout/route.d.ts.map +1 -1
  79. package/dist/app/api/hazo_auth/logout/route.js +31 -0
  80. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  81. package/dist/app/api/hazo_auth/me/route.js +10 -0
  82. package/dist/cli/index.js +18 -0
  83. package/dist/cli/init_users.d.ts +17 -0
  84. package/dist/cli/init_users.d.ts.map +1 -0
  85. package/dist/cli/init_users.js +307 -0
  86. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts +2 -0
  87. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.d.ts.map +1 -1
  88. package/dist/components/layouts/forgot_password/hooks/use_forgot_password_form.js +8 -0
  89. package/dist/components/layouts/forgot_password/index.d.ts +7 -1
  90. package/dist/components/layouts/forgot_password/index.d.ts.map +1 -1
  91. package/dist/components/layouts/forgot_password/index.js +7 -2
  92. package/dist/components/layouts/login/index.d.ts +13 -1
  93. package/dist/components/layouts/login/index.d.ts.map +1 -1
  94. package/dist/components/layouts/login/index.js +11 -2
  95. package/dist/components/layouts/my_settings/components/connected_accounts_section.d.ts +17 -0
  96. package/dist/components/layouts/my_settings/components/connected_accounts_section.d.ts.map +1 -0
  97. package/dist/components/layouts/my_settings/components/connected_accounts_section.js +17 -0
  98. package/dist/components/layouts/my_settings/components/set_password_section.d.ts +26 -0
  99. package/dist/components/layouts/my_settings/components/set_password_section.d.ts.map +1 -0
  100. package/dist/components/layouts/my_settings/components/set_password_section.js +127 -0
  101. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts +3 -0
  102. package/dist/components/layouts/my_settings/hooks/use_my_settings.d.ts.map +1 -1
  103. package/dist/components/layouts/my_settings/hooks/use_my_settings.js +9 -0
  104. package/dist/components/layouts/my_settings/index.d.ts.map +1 -1
  105. package/dist/components/layouts/my_settings/index.js +4 -2
  106. package/dist/components/layouts/shared/components/google_icon.d.ts +12 -0
  107. package/dist/components/layouts/shared/components/google_icon.d.ts.map +1 -0
  108. package/dist/components/layouts/shared/components/google_icon.js +9 -0
  109. package/dist/components/layouts/shared/components/google_sign_in_button.d.ts +21 -0
  110. package/dist/components/layouts/shared/components/google_sign_in_button.d.ts.map +1 -0
  111. package/dist/components/layouts/shared/components/google_sign_in_button.js +50 -0
  112. package/dist/components/layouts/shared/components/oauth_divider.d.ts +13 -0
  113. package/dist/components/layouts/shared/components/oauth_divider.d.ts.map +1 -0
  114. package/dist/components/layouts/shared/components/oauth_divider.js +13 -0
  115. package/dist/components/layouts/shared/config/layout_customization.d.ts +2 -7
  116. package/dist/components/layouts/shared/config/layout_customization.d.ts.map +1 -1
  117. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +3 -0
  118. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
  119. package/dist/components/layouts/shared/hooks/use_auth_status.js +4 -0
  120. package/dist/components/layouts/shared/index.d.ts +5 -0
  121. package/dist/components/layouts/shared/index.d.ts.map +1 -1
  122. package/dist/components/layouts/shared/index.js +3 -0
  123. package/dist/components/ui/button.d.ts +1 -1
  124. package/dist/lib/auth/nextauth_config.d.ts +34 -0
  125. package/dist/lib/auth/nextauth_config.d.ts.map +1 -0
  126. package/dist/lib/auth/nextauth_config.js +171 -0
  127. package/dist/lib/config/default_config.d.ts +24 -0
  128. package/dist/lib/config/default_config.d.ts.map +1 -1
  129. package/dist/lib/config/default_config.js +14 -0
  130. package/dist/lib/index.d.ts +2 -0
  131. package/dist/lib/index.d.ts.map +1 -1
  132. package/dist/lib/index.js +1 -0
  133. package/dist/lib/login_config.server.d.ts +3 -0
  134. package/dist/lib/login_config.server.d.ts.map +1 -1
  135. package/dist/lib/login_config.server.js +4 -0
  136. package/dist/lib/oauth_config.server.d.ts +29 -0
  137. package/dist/lib/oauth_config.server.d.ts.map +1 -0
  138. package/dist/lib/oauth_config.server.js +40 -0
  139. package/dist/lib/services/login_service.d.ts.map +1 -1
  140. package/dist/lib/services/login_service.js +16 -1
  141. package/dist/lib/services/oauth_service.d.ts +88 -0
  142. package/dist/lib/services/oauth_service.d.ts.map +1 -0
  143. package/dist/lib/services/oauth_service.js +376 -0
  144. package/dist/lib/services/password_reset_service.d.ts +2 -0
  145. package/dist/lib/services/password_reset_service.d.ts.map +1 -1
  146. package/dist/lib/services/password_reset_service.js +10 -0
  147. package/dist/lib/services/registration_service.d.ts.map +1 -1
  148. package/dist/lib/services/registration_service.js +1 -0
  149. package/dist/lib/utils/password_validator.d.ts +19 -0
  150. package/dist/lib/utils/password_validator.d.ts.map +1 -0
  151. package/dist/lib/utils/password_validator.js +36 -0
  152. package/dist/server_pages/login.d.ts.map +1 -1
  153. package/dist/server_pages/login.js +6 -1
  154. package/dist/server_pages/login_client_wrapper.d.ts +5 -2
  155. package/dist/server_pages/login_client_wrapper.d.ts.map +1 -1
  156. package/dist/server_pages/login_client_wrapper.js +2 -2
  157. package/package.json +6 -2
@@ -0,0 +1,376 @@
1
+ import { createCrudService } from "hazo_connect/server";
2
+ import { randomUUID } from "crypto";
3
+ import { create_app_logger } from "../app_logger";
4
+ import { sanitize_error_for_user } from "../utils/error_sanitizer";
5
+ import { get_line_number } from "../utils/api_route_helpers";
6
+ import { get_oauth_config } from "../oauth_config.server";
7
+ // section: helpers
8
+ /**
9
+ * Handles Google OAuth login/registration flow
10
+ * 1. Check if user exists with google_id -> login
11
+ * 2. Check if user exists with email -> link Google account
12
+ * 3. Create new user with Google data
13
+ *
14
+ * @param adapter - The hazo_connect adapter instance
15
+ * @param data - Google OAuth user data
16
+ * @returns OAuth login result with user_id and status flags
17
+ */
18
+ export async function handle_google_oauth_login(adapter, data) {
19
+ const logger = create_app_logger();
20
+ try {
21
+ const { google_id, email, name, profile_picture_url, email_verified } = data;
22
+ const oauth_config = get_oauth_config();
23
+ const users_service = createCrudService(adapter, "hazo_users");
24
+ const now = new Date().toISOString();
25
+ // Step 1: Check if user exists with this google_id
26
+ const users_by_google_id = await users_service.findBy({ google_id });
27
+ if (Array.isArray(users_by_google_id) && users_by_google_id.length > 0) {
28
+ const user = users_by_google_id[0];
29
+ // Update last_logon timestamp
30
+ await users_service.updateById(user.id, {
31
+ last_logon: now,
32
+ changed_at: now,
33
+ });
34
+ logger.info("oauth_service_google_login_existing_google_user", {
35
+ filename: "oauth_service.ts",
36
+ line_number: get_line_number(),
37
+ user_id: user.id,
38
+ email: user.email_address,
39
+ });
40
+ return {
41
+ success: true,
42
+ user_id: user.id,
43
+ is_new_user: false,
44
+ was_linked: false,
45
+ email: user.email_address,
46
+ name: user.name,
47
+ };
48
+ }
49
+ // Step 2: Check if user exists with this email
50
+ const users_by_email = await users_service.findBy({ email_address: email });
51
+ if (Array.isArray(users_by_email) && users_by_email.length > 0) {
52
+ const user = users_by_email[0];
53
+ const user_email_verified = user.email_verified;
54
+ // Check if auto-linking is enabled for unverified accounts
55
+ if (!user_email_verified && !oauth_config.auto_link_unverified_accounts) {
56
+ return {
57
+ success: false,
58
+ error: "An account with this email exists but is not verified. Please verify your email first.",
59
+ };
60
+ }
61
+ // Link Google account to existing user
62
+ const current_auth_providers = user.auth_providers || "email";
63
+ const new_auth_providers = current_auth_providers.includes("google")
64
+ ? current_auth_providers
65
+ : `${current_auth_providers},google`;
66
+ const update_data = {
67
+ google_id,
68
+ auth_providers: new_auth_providers,
69
+ last_logon: now,
70
+ changed_at: now,
71
+ };
72
+ // If user was unverified and Google verified the email, mark as verified
73
+ if (!user_email_verified && email_verified) {
74
+ update_data.email_verified = true;
75
+ logger.info("oauth_service_auto_verified_email", {
76
+ filename: "oauth_service.ts",
77
+ line_number: get_line_number(),
78
+ user_id: user.id,
79
+ email,
80
+ });
81
+ }
82
+ // Update name if not set and Google provides one
83
+ if (!user.name && name) {
84
+ update_data.name = name;
85
+ }
86
+ // Update profile picture if not set and Google provides one
87
+ if (!user.profile_picture_url && profile_picture_url) {
88
+ update_data.profile_picture_url = profile_picture_url;
89
+ update_data.profile_source = "custom"; // Use 'custom' for external URLs (Google profile pics)
90
+ }
91
+ await users_service.updateById(user.id, update_data);
92
+ logger.info("oauth_service_google_linked_to_existing", {
93
+ filename: "oauth_service.ts",
94
+ line_number: get_line_number(),
95
+ user_id: user.id,
96
+ email,
97
+ was_unverified: !user_email_verified,
98
+ });
99
+ return {
100
+ success: true,
101
+ user_id: user.id,
102
+ is_new_user: false,
103
+ was_linked: true,
104
+ email: user.email_address,
105
+ name: update_data.name || user.name,
106
+ };
107
+ }
108
+ // Step 3: Create new user with Google data
109
+ const user_id = randomUUID();
110
+ const insert_data = {
111
+ id: user_id,
112
+ email_address: email,
113
+ password_hash: "", // Empty string for Google-only users
114
+ email_verified: email_verified, // Trust Google's verification
115
+ is_active: true,
116
+ login_attempts: 0,
117
+ google_id,
118
+ auth_providers: "google",
119
+ created_at: now,
120
+ changed_at: now,
121
+ last_logon: now,
122
+ };
123
+ if (name) {
124
+ insert_data.name = name;
125
+ }
126
+ if (profile_picture_url) {
127
+ insert_data.profile_picture_url = profile_picture_url;
128
+ insert_data.profile_source = "custom"; // Use 'custom' for external URLs (Google profile pics)
129
+ }
130
+ const inserted_users = await users_service.insert(insert_data);
131
+ if (!Array.isArray(inserted_users) || inserted_users.length === 0) {
132
+ return {
133
+ success: false,
134
+ error: "Failed to create user account",
135
+ };
136
+ }
137
+ logger.info("oauth_service_google_new_user_created", {
138
+ filename: "oauth_service.ts",
139
+ line_number: get_line_number(),
140
+ user_id,
141
+ email,
142
+ });
143
+ return {
144
+ success: true,
145
+ user_id,
146
+ is_new_user: true,
147
+ was_linked: false,
148
+ email,
149
+ name,
150
+ };
151
+ }
152
+ catch (error) {
153
+ const user_friendly_error = sanitize_error_for_user(error, {
154
+ logToConsole: true,
155
+ logToLogger: true,
156
+ logger,
157
+ context: {
158
+ filename: "oauth_service.ts",
159
+ line_number: get_line_number(),
160
+ email: data.email,
161
+ operation: "handle_google_oauth_login",
162
+ },
163
+ });
164
+ return {
165
+ success: false,
166
+ error: user_friendly_error,
167
+ };
168
+ }
169
+ }
170
+ /**
171
+ * Links a Google account to an existing user
172
+ * @param adapter - The hazo_connect adapter instance
173
+ * @param user_id - The user's ID
174
+ * @param google_id - Google's unique user ID
175
+ * @returns Result indicating success or failure
176
+ */
177
+ export async function link_google_account(adapter, user_id, google_id) {
178
+ const logger = create_app_logger();
179
+ try {
180
+ const users_service = createCrudService(adapter, "hazo_users");
181
+ const now = new Date().toISOString();
182
+ // Get current user
183
+ const users = await users_service.findBy({ id: user_id });
184
+ if (!Array.isArray(users) || users.length === 0) {
185
+ return {
186
+ success: false,
187
+ error: "User not found",
188
+ };
189
+ }
190
+ const user = users[0];
191
+ // Check if Google is already linked
192
+ if (user.google_id) {
193
+ return {
194
+ success: false,
195
+ error: "Google account is already linked",
196
+ };
197
+ }
198
+ // Update auth_providers
199
+ const current_auth_providers = user.auth_providers || "email";
200
+ const new_auth_providers = current_auth_providers.includes("google")
201
+ ? current_auth_providers
202
+ : `${current_auth_providers},google`;
203
+ await users_service.updateById(user_id, {
204
+ google_id,
205
+ auth_providers: new_auth_providers,
206
+ changed_at: now,
207
+ });
208
+ logger.info("oauth_service_google_account_linked", {
209
+ filename: "oauth_service.ts",
210
+ line_number: get_line_number(),
211
+ user_id,
212
+ });
213
+ return { success: true };
214
+ }
215
+ catch (error) {
216
+ const user_friendly_error = sanitize_error_for_user(error, {
217
+ logToConsole: true,
218
+ logToLogger: true,
219
+ logger,
220
+ context: {
221
+ filename: "oauth_service.ts",
222
+ line_number: get_line_number(),
223
+ user_id,
224
+ operation: "link_google_account",
225
+ },
226
+ });
227
+ return {
228
+ success: false,
229
+ error: user_friendly_error,
230
+ };
231
+ }
232
+ }
233
+ /**
234
+ * Checks if a user has a password set (non-empty password_hash)
235
+ * @param adapter - The hazo_connect adapter instance
236
+ * @param user_id - The user's ID
237
+ * @returns True if user has a password set
238
+ */
239
+ export async function user_has_password(adapter, user_id) {
240
+ try {
241
+ const users_service = createCrudService(adapter, "hazo_users");
242
+ const users = await users_service.findBy({ id: user_id });
243
+ if (!Array.isArray(users) || users.length === 0) {
244
+ return false;
245
+ }
246
+ const password_hash = users[0].password_hash;
247
+ return password_hash !== null && password_hash !== undefined && password_hash !== "";
248
+ }
249
+ catch (_a) {
250
+ return false;
251
+ }
252
+ }
253
+ /**
254
+ * Checks if a user has a password set by email
255
+ * @param adapter - The hazo_connect adapter instance
256
+ * @param email - The user's email address
257
+ * @returns True if user has a password set
258
+ */
259
+ export async function user_has_password_by_email(adapter, email) {
260
+ try {
261
+ const users_service = createCrudService(adapter, "hazo_users");
262
+ const users = await users_service.findBy({ email_address: email });
263
+ if (!Array.isArray(users) || users.length === 0) {
264
+ return false;
265
+ }
266
+ const password_hash = users[0].password_hash;
267
+ return password_hash !== null && password_hash !== undefined && password_hash !== "";
268
+ }
269
+ catch (_a) {
270
+ return false;
271
+ }
272
+ }
273
+ /**
274
+ * Gets a user's authentication providers and password status
275
+ * @param adapter - The hazo_connect adapter instance
276
+ * @param user_id - The user's ID
277
+ * @returns Auth providers array and has_password flag
278
+ */
279
+ export async function get_user_auth_providers(adapter, user_id) {
280
+ try {
281
+ const users_service = createCrudService(adapter, "hazo_users");
282
+ const users = await users_service.findBy({ id: user_id });
283
+ if (!Array.isArray(users) || users.length === 0) {
284
+ return {
285
+ success: false,
286
+ error: "User not found",
287
+ };
288
+ }
289
+ const user = users[0];
290
+ const auth_providers_str = user.auth_providers || "email";
291
+ const auth_providers = auth_providers_str.split(",").map((p) => p.trim());
292
+ const password_hash = user.password_hash;
293
+ const has_password = password_hash !== null && password_hash !== undefined && password_hash !== "";
294
+ return {
295
+ success: true,
296
+ auth_providers,
297
+ has_password,
298
+ };
299
+ }
300
+ catch (error) {
301
+ const logger = create_app_logger();
302
+ const user_friendly_error = sanitize_error_for_user(error, {
303
+ logToConsole: true,
304
+ logToLogger: true,
305
+ logger,
306
+ context: {
307
+ filename: "oauth_service.ts",
308
+ line_number: get_line_number(),
309
+ user_id,
310
+ operation: "get_user_auth_providers",
311
+ },
312
+ });
313
+ return {
314
+ success: false,
315
+ error: user_friendly_error,
316
+ };
317
+ }
318
+ }
319
+ /**
320
+ * Sets a password for a user who doesn't have one (e.g., Google-only users)
321
+ * @param adapter - The hazo_connect adapter instance
322
+ * @param user_id - The user's ID
323
+ * @param password_hash - The hashed password to set
324
+ * @returns Result indicating success or failure
325
+ */
326
+ export async function set_user_password(adapter, user_id, password_hash) {
327
+ const logger = create_app_logger();
328
+ try {
329
+ const users_service = createCrudService(adapter, "hazo_users");
330
+ const now = new Date().toISOString();
331
+ // Get current user
332
+ const users = await users_service.findBy({ id: user_id });
333
+ if (!Array.isArray(users) || users.length === 0) {
334
+ return {
335
+ success: false,
336
+ error: "User not found",
337
+ };
338
+ }
339
+ const user = users[0];
340
+ // Update password and auth_providers
341
+ const current_auth_providers = user.auth_providers || "";
342
+ const new_auth_providers = current_auth_providers.includes("email")
343
+ ? current_auth_providers
344
+ : current_auth_providers
345
+ ? `${current_auth_providers},email`
346
+ : "email";
347
+ await users_service.updateById(user_id, {
348
+ password_hash,
349
+ auth_providers: new_auth_providers,
350
+ changed_at: now,
351
+ });
352
+ logger.info("oauth_service_password_set", {
353
+ filename: "oauth_service.ts",
354
+ line_number: get_line_number(),
355
+ user_id,
356
+ });
357
+ return { success: true };
358
+ }
359
+ catch (error) {
360
+ const user_friendly_error = sanitize_error_for_user(error, {
361
+ logToConsole: true,
362
+ logToLogger: true,
363
+ logger,
364
+ context: {
365
+ filename: "oauth_service.ts",
366
+ line_number: get_line_number(),
367
+ user_id,
368
+ operation: "set_user_password",
369
+ },
370
+ });
371
+ return {
372
+ success: false,
373
+ error: user_friendly_error,
374
+ };
375
+ }
376
+ }
@@ -5,6 +5,8 @@ export type PasswordResetRequestData = {
5
5
  export type PasswordResetRequestResult = {
6
6
  success: boolean;
7
7
  error?: string;
8
+ /** True if the user doesn't have a password set (Google-only user) */
9
+ no_password_set?: boolean;
8
10
  };
9
11
  export type PasswordResetData = {
10
12
  token: string;
@@ -1 +1 @@
1
- {"version":3,"file":"password_reset_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/password_reset_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CAqErC;AAED;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gCAAgC,GACrC,OAAO,CAAC,kCAAkC,CAAC,CAgG7C;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAiK9B"}
1
+ {"version":3,"file":"password_reset_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/password_reset_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAQvD,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,0BAA0B,GAAG;IACvC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,kCAAkC,GAAG;IAC/C,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;;;GAOG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,wBAAwB,GAC7B,OAAO,CAAC,0BAA0B,CAAC,CAgFrC;AAED;;;;;;GAMG;AACH,wBAAsB,6BAA6B,CACjD,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gCAAgC,GACrC,OAAO,CAAC,kCAAkC,CAAC,CAgG7C;AAED;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,iBAAiB,GACtB,OAAO,CAAC,mBAAmB,CAAC,CAiK9B"}
@@ -29,6 +29,16 @@ export async function request_password_reset(adapter, data) {
29
29
  }
30
30
  const user = users[0];
31
31
  const user_id = user.id;
32
+ // Check if user has a password set (Google-only users don't have a password)
33
+ const password_hash = user.password_hash;
34
+ if (!password_hash || password_hash === "") {
35
+ // User doesn't have a password - they need to set one via My Settings after logging in with Google
36
+ // Return success to prevent email enumeration, but include flag for UI handling
37
+ return {
38
+ success: true,
39
+ no_password_set: true,
40
+ };
41
+ }
32
42
  // Create password reset token using shared token service
33
43
  const token_result = await create_token({
34
44
  adapter,
@@ -1 +1 @@
1
- {"version":3,"file":"registration_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/registration_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAcvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,kBAAkB,CAAC,CA+I7B"}
1
+ {"version":3,"file":"registration_service.d.ts","sourceRoot":"","sources":["../../../src/lib/services/registration_service.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAcvD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAGF;;;;;GAKG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,kBAAkB,EAC3B,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,kBAAkB,CAAC,CAgJ7B"}
@@ -44,6 +44,7 @@ export async function register_user(adapter, data) {
44
44
  email_verified: false,
45
45
  is_active: true,
46
46
  login_attempts: 0,
47
+ auth_providers: "email", // Track that this user registered with email/password
47
48
  created_at: now,
48
49
  changed_at: now,
49
50
  };
@@ -0,0 +1,19 @@
1
+ export type PasswordRequirementOptions = {
2
+ minimum_length: number;
3
+ require_uppercase: boolean;
4
+ require_lowercase: boolean;
5
+ require_number: boolean;
6
+ require_special: boolean;
7
+ };
8
+ export type PasswordValidationResult = {
9
+ valid: boolean;
10
+ errors: string[];
11
+ };
12
+ /**
13
+ * Validates a password against specified requirements (server-side version)
14
+ * @param password - The password to validate
15
+ * @param requirements - Password requirement options
16
+ * @returns Validation result with valid flag and error messages
17
+ */
18
+ export declare function validate_password(password: string, requirements: PasswordRequirementOptions): PasswordValidationResult;
19
+ //# sourceMappingURL=password_validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"password_validator.d.ts","sourceRoot":"","sources":["../../../src/lib/utils/password_validator.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,0BAA0B,GAAG;IACvC,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB,CAAC;AAGF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,0BAA0B,GACvC,wBAAwB,CAuC1B"}
@@ -0,0 +1,36 @@
1
+ // section: helpers
2
+ /**
3
+ * Validates a password against specified requirements (server-side version)
4
+ * @param password - The password to validate
5
+ * @param requirements - Password requirement options
6
+ * @returns Validation result with valid flag and error messages
7
+ */
8
+ export function validate_password(password, requirements) {
9
+ const errors = [];
10
+ if (!password || password.trim().length === 0) {
11
+ return {
12
+ valid: false,
13
+ errors: ["Password is required"],
14
+ };
15
+ }
16
+ if (password.length < requirements.minimum_length) {
17
+ errors.push(`Password must be at least ${requirements.minimum_length} characters`);
18
+ }
19
+ if (requirements.require_uppercase && !/[A-Z]/.test(password)) {
20
+ errors.push("Password must include at least one uppercase letter");
21
+ }
22
+ if (requirements.require_lowercase && !/[a-z]/.test(password)) {
23
+ errors.push("Password must include at least one lowercase letter");
24
+ }
25
+ if (requirements.require_number && !/\d/.test(password)) {
26
+ errors.push("Password must include at least one number");
27
+ }
28
+ if (requirements.require_special &&
29
+ !/[!@#$%^&*(),.?":{}|<>\-_+=\[\];'/\\]/.test(password)) {
30
+ errors.push("Password must include at least one special character");
31
+ }
32
+ return {
33
+ valid: errors.length === 0,
34
+ errors,
35
+ };
36
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/server_pages/login.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,SAAS,EACT,SAAS,EACT,sBAAsB,GACvB,GAAE,cAAmB,2CA4BrB;AAGD,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/server_pages/login.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,cAAc,GAAG;IAC3B;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,eAAe,CAAC;IAErC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC,CAAC;AAGF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,OAAO,UAAU,SAAS,CAAC,EAChC,SAAS,EACT,SAAS,EACT,sBAAsB,GACvB,GAAE,cAAmB,2CAkCrB;AAGD,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -35,7 +35,12 @@ export default function LoginPage({ image_src, image_alt, image_background_color
35
35
  const finalImageAlt = image_alt || config.imageAlt;
36
36
  const finalImageBackgroundColor = image_background_color || config.imageBackgroundColor;
37
37
  // Pass serializable config to client wrapper
38
- return (_jsx(LoginClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, redirectRoute: config.redirectRoute, successMessage: config.successMessage, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, forgotPasswordPath: config.forgotPasswordPath, forgotPasswordLabel: config.forgotPasswordLabel, createAccountPath: config.createAccountPath, createAccountLabel: config.createAccountLabel }));
38
+ return (_jsx(LoginClientWrapper, { image_src: finalImageSrc, image_alt: finalImageAlt, image_background_color: finalImageBackgroundColor, redirectRoute: config.redirectRoute, successMessage: config.successMessage, alreadyLoggedInMessage: config.alreadyLoggedInMessage, showLogoutButton: config.showLogoutButton, showReturnHomeButton: config.showReturnHomeButton, returnHomeButtonLabel: config.returnHomeButtonLabel, returnHomePath: config.returnHomePath, forgotPasswordPath: config.forgotPasswordPath, forgotPasswordLabel: config.forgotPasswordLabel, createAccountPath: config.createAccountPath, createAccountLabel: config.createAccountLabel, oauth: {
39
+ enable_google: config.oauth.enable_google,
40
+ enable_email_password: config.oauth.enable_email_password,
41
+ google_button_text: config.oauth.google_button_text,
42
+ oauth_divider_text: config.oauth.oauth_divider_text,
43
+ } }));
39
44
  }
40
45
  // Named export for direct imports
41
46
  export { LoginPage };
@@ -1,13 +1,16 @@
1
1
  import type { LoginConfig } from "../lib/login_config.server";
2
+ import type { OAuthLayoutConfig } from "../components/layouts/login";
2
3
  import type { StaticImageData } from "next/image";
3
- export type LoginClientWrapperProps = Omit<LoginConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor'> & {
4
+ export type LoginClientWrapperProps = Omit<LoginConfig, 'imageSrc' | 'imageAlt' | 'imageBackgroundColor' | 'oauth'> & {
4
5
  image_src: string | StaticImageData;
5
6
  image_alt: string;
6
7
  image_background_color: string;
8
+ /** OAuth configuration */
9
+ oauth?: OAuthLayoutConfig;
7
10
  };
8
11
  /**
9
12
  * Client wrapper for LoginLayout
10
13
  * Initializes hazo_connect data client on client side and passes config from server
11
14
  */
12
- export declare function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, }: LoginClientWrapperProps): import("react/jsx-runtime").JSX.Element;
15
+ export declare function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, oauth, }: LoginClientWrapperProps): import("react/jsx-runtime").JSX.Element;
13
16
  //# sourceMappingURL=login_client_wrapper.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"login_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/login_client_wrapper.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,CAAC,GAAG;IAC1G,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;CAChC,CAAC;AAGF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,GACnB,EAAE,uBAAuB,2CA0CzB"}
1
+ {"version":3,"file":"login_client_wrapper.d.ts","sourceRoot":"","sources":["../../src/server_pages/login_client_wrapper.tsx"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,4BAA4B,CAAC;AAC9D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAGrE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,UAAU,GAAG,sBAAsB,GAAG,OAAO,CAAC,GAAG;IACpH,SAAS,EAAE,MAAM,GAAG,eAAe,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB,EAAE,MAAM,CAAC;IAC/B,0BAA0B;IAC1B,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B,CAAC;AAGF;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,SAAS,EACT,SAAS,EACT,sBAAsB,EACtB,aAAa,EACb,cAAc,EACd,sBAAsB,EACtB,gBAAgB,EAChB,oBAAoB,EACpB,qBAAqB,EACrB,cAAc,EACd,kBAAkB,EAClB,mBAAmB,EACnB,iBAAiB,EACjB,kBAAkB,EAClB,KAAK,GACN,EAAE,uBAAuB,2CA2CzB"}
@@ -12,7 +12,7 @@ import { create_app_logger } from "../lib/app_logger";
12
12
  * Client wrapper for LoginLayout
13
13
  * Initializes hazo_connect data client on client side and passes config from server
14
14
  */
15
- export function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, }) {
15
+ export function LoginClientWrapper({ image_src, image_alt, image_background_color, redirectRoute, successMessage, alreadyLoggedInMessage, showLogoutButton, showReturnHomeButton, returnHomeButtonLabel, returnHomePath, forgotPasswordPath, forgotPasswordLabel, createAccountPath, createAccountLabel, oauth, }) {
16
16
  const [dataClient, setDataClient] = useState(null);
17
17
  useEffect(() => {
18
18
  // Initialize hazo_connect on client side
@@ -25,5 +25,5 @@ export function LoginClientWrapper({ image_src, image_alt, image_background_colo
25
25
  return (_jsx("div", { className: "cls_login_page_loading flex items-center justify-center min-h-screen", children: _jsx("div", { className: "text-slate-600 animate-pulse", children: "Loading..." }) }));
26
26
  }
27
27
  const logger = create_app_logger();
28
- return (_jsx(LoginLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, logger: logger, redirectRoute: redirectRoute, successMessage: successMessage, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, forgot_password_path: forgotPasswordPath, forgot_password_label: forgotPasswordLabel, create_account_path: createAccountPath, create_account_label: createAccountLabel }));
28
+ return (_jsx(LoginLayout, { image_src: image_src, image_alt: image_alt, image_background_color: image_background_color, data_client: dataClient, logger: logger, redirectRoute: redirectRoute, successMessage: successMessage, alreadyLoggedInMessage: alreadyLoggedInMessage, showLogoutButton: showLogoutButton, showReturnHomeButton: showReturnHomeButton, returnHomeButtonLabel: returnHomeButtonLabel, returnHomePath: returnHomePath, forgot_password_path: forgotPasswordPath, forgot_password_label: forgotPasswordLabel, create_account_path: createAccountPath, create_account_label: createAccountLabel, oauth: oauth }));
29
29
  }
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "hazo_auth",
3
- "version": "4.1.0",
3
+ "version": "4.3.0",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "type": "module",
7
7
  "bin": {
8
- "hazo_auth": "./dist/cli/index.js"
8
+ "hazo_auth": "./bin/hazo_auth.mjs"
9
9
  },
10
10
  "exports": {
11
11
  ".": {
@@ -119,6 +119,8 @@
119
119
  },
120
120
  "files": [
121
121
  "dist/**/*",
122
+ "bin/**/*",
123
+ "cli-src/**/*",
122
124
  "public/profile_pictures/library/**/*",
123
125
  "hazo_auth_config.example.ini",
124
126
  "hazo_notify_config.example.ini",
@@ -183,12 +185,14 @@
183
185
  "morgan": "^1.10.1",
184
186
  "multer": "^2.0.2",
185
187
  "next": "^16.0.7",
188
+ "next-auth": "^4.24.13",
186
189
  "next-themes": "^0.4.6",
187
190
  "react": "^18.3.1",
188
191
  "react-dom": "^18.3.1",
189
192
  "sonner": "^2.0.7",
190
193
  "tailwind-merge": "^3.4.0",
191
194
  "tailwindcss-animate": "^1.0.7",
195
+ "tsx": "^4.20.6",
192
196
  "zod": "^4.1.12"
193
197
  },
194
198
  "devDependencies": {