hazo_auth 4.2.0 → 4.4.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.
- package/bin/hazo_auth.mjs +35 -0
- package/cli-src/assets/images/forgot_password_default.jpg +0 -0
- package/cli-src/assets/images/login_default.jpg +0 -0
- package/cli-src/assets/images/register_default.jpg +0 -0
- package/cli-src/assets/images/reset_password_default.jpg +0 -0
- package/cli-src/assets/images/verify_email_default.jpg +0 -0
- package/cli-src/cli/generate.ts +276 -0
- package/cli-src/cli/index.ts +207 -0
- package/cli-src/cli/init.ts +254 -0
- package/cli-src/cli/init_users.ts +376 -0
- package/cli-src/cli/validate.ts +581 -0
- package/cli-src/lib/already_logged_in_config.server.ts +46 -0
- package/cli-src/lib/app_logger.ts +24 -0
- package/cli-src/lib/auth/auth_cache.ts +220 -0
- package/cli-src/lib/auth/auth_rate_limiter.ts +121 -0
- package/cli-src/lib/auth/auth_types.ts +117 -0
- package/cli-src/lib/auth/auth_utils.server.ts +196 -0
- package/cli-src/lib/auth/dev_lock_validator.edge.ts +171 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +583 -0
- package/cli-src/lib/auth/index.ts +23 -0
- package/cli-src/lib/auth/nextauth_config.ts +227 -0
- package/cli-src/lib/auth/org_cache.ts +148 -0
- package/cli-src/lib/auth/scope_cache.ts +233 -0
- package/cli-src/lib/auth/server_auth.ts +88 -0
- package/cli-src/lib/auth/session_token_validator.edge.ts +92 -0
- package/cli-src/lib/auth_utility_config.server.ts +136 -0
- package/cli-src/lib/config/config_loader.server.ts +164 -0
- package/cli-src/lib/config/default_config.ts +243 -0
- package/cli-src/lib/dev_lock_config.server.ts +148 -0
- package/cli-src/lib/email_verification_config.server.ts +63 -0
- package/cli-src/lib/file_types_config.server.ts +25 -0
- package/cli-src/lib/forgot_password_config.server.ts +63 -0
- package/cli-src/lib/hazo_connect_instance.server.ts +101 -0
- package/cli-src/lib/hazo_connect_setup.server.ts +194 -0
- package/cli-src/lib/hazo_connect_setup.ts +54 -0
- package/cli-src/lib/index.ts +46 -0
- package/cli-src/lib/login_config.server.ts +106 -0
- package/cli-src/lib/messages_config.server.ts +45 -0
- package/cli-src/lib/migrations/apply_migration.ts +105 -0
- package/cli-src/lib/multi_tenancy_config.server.ts +94 -0
- package/cli-src/lib/my_settings_config.server.ts +135 -0
- package/cli-src/lib/oauth_config.server.ts +87 -0
- package/cli-src/lib/password_requirements_config.server.ts +40 -0
- package/cli-src/lib/profile_pic_menu_config.server.ts +138 -0
- package/cli-src/lib/profile_picture_config.server.ts +56 -0
- package/cli-src/lib/register_config.server.ts +101 -0
- package/cli-src/lib/reset_password_config.server.ts +103 -0
- package/cli-src/lib/scope_hierarchy_config.server.ts +151 -0
- package/cli-src/lib/services/email_service.ts +587 -0
- package/cli-src/lib/services/email_verification_service.ts +270 -0
- package/cli-src/lib/services/index.ts +16 -0
- package/cli-src/lib/services/login_service.ts +150 -0
- package/cli-src/lib/services/oauth_service.ts +494 -0
- package/cli-src/lib/services/org_service.ts +965 -0
- package/cli-src/lib/services/password_change_service.ts +154 -0
- package/cli-src/lib/services/password_reset_service.ts +418 -0
- package/cli-src/lib/services/profile_picture_remove_service.ts +120 -0
- package/cli-src/lib/services/profile_picture_service.ts +451 -0
- package/cli-src/lib/services/profile_picture_source_mapper.ts +62 -0
- package/cli-src/lib/services/registration_service.ts +185 -0
- package/cli-src/lib/services/scope_labels_service.ts +348 -0
- package/cli-src/lib/services/scope_service.ts +778 -0
- package/cli-src/lib/services/session_token_service.ts +178 -0
- package/cli-src/lib/services/token_service.ts +240 -0
- package/cli-src/lib/services/user_profiles_cache.ts +189 -0
- package/cli-src/lib/services/user_profiles_service.ts +264 -0
- package/cli-src/lib/services/user_scope_service.ts +554 -0
- package/cli-src/lib/services/user_update_service.ts +141 -0
- package/cli-src/lib/ui_shell_config.server.ts +73 -0
- package/cli-src/lib/ui_sizes_config.server.ts +37 -0
- package/cli-src/lib/user_fields_config.server.ts +31 -0
- package/cli-src/lib/user_management_config.server.ts +39 -0
- package/cli-src/lib/user_profiles_config.server.ts +55 -0
- package/cli-src/lib/utils/api_route_helpers.ts +60 -0
- package/cli-src/lib/utils/error_sanitizer.ts +75 -0
- package/cli-src/lib/utils/password_validator.ts +65 -0
- package/cli-src/lib/utils.ts +11 -0
- package/cli-src/server/logging/logger_service.ts +56 -0
- package/cli-src/server/types/app_types.ts +74 -0
- package/cli-src/server/types/express.d.ts +16 -0
- package/dist/cli/index.js +18 -0
- package/dist/cli/init_users.d.ts +17 -0
- package/dist/cli/init_users.d.ts.map +1 -0
- package/dist/cli/init_users.js +307 -0
- package/dist/components/layouts/dev_lock/index.d.ts +29 -0
- package/dist/components/layouts/dev_lock/index.d.ts.map +1 -0
- package/dist/components/layouts/dev_lock/index.js +60 -0
- package/dist/components/layouts/index.d.ts +2 -0
- package/dist/components/layouts/index.d.ts.map +1 -1
- package/dist/components/layouts/index.js +1 -0
- package/dist/components/layouts/org_management/index.d.ts +26 -0
- package/dist/components/layouts/org_management/index.d.ts.map +1 -0
- package/dist/components/layouts/org_management/index.js +75 -0
- package/dist/components/layouts/shared/config/layout_customization.d.ts +2 -7
- package/dist/components/layouts/shared/config/layout_customization.d.ts.map +1 -1
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts +13 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.d.ts.map +1 -0
- package/dist/components/layouts/user_management/components/org_hierarchy_tab.js +276 -0
- package/dist/components/layouts/user_management/index.d.ts +3 -1
- package/dist/components/layouts/user_management/index.d.ts.map +1 -1
- package/dist/components/layouts/user_management/index.js +10 -4
- package/dist/lib/auth/auth_types.d.ts +6 -0
- package/dist/lib/auth/auth_types.d.ts.map +1 -1
- package/dist/lib/auth/dev_lock_validator.edge.d.ts +38 -0
- package/dist/lib/auth/dev_lock_validator.edge.d.ts.map +1 -0
- package/dist/lib/auth/dev_lock_validator.edge.js +122 -0
- package/dist/lib/auth/hazo_get_auth.server.d.ts.map +1 -1
- package/dist/lib/auth/hazo_get_auth.server.js +61 -1
- package/dist/lib/auth/org_cache.d.ts +65 -0
- package/dist/lib/auth/org_cache.d.ts.map +1 -0
- package/dist/lib/auth/org_cache.js +103 -0
- package/dist/lib/config/default_config.d.ts +76 -0
- package/dist/lib/config/default_config.d.ts.map +1 -1
- package/dist/lib/config/default_config.js +42 -0
- package/dist/lib/dev_lock_config.server.d.ts +41 -0
- package/dist/lib/dev_lock_config.server.d.ts.map +1 -0
- package/dist/lib/dev_lock_config.server.js +50 -0
- package/dist/lib/multi_tenancy_config.server.d.ts +30 -0
- package/dist/lib/multi_tenancy_config.server.d.ts.map +1 -0
- package/dist/lib/multi_tenancy_config.server.js +41 -0
- package/dist/lib/services/org_service.d.ts +191 -0
- package/dist/lib/services/org_service.d.ts.map +1 -0
- package/dist/lib/services/org_service.js +746 -0
- package/dist/lib/utils/password_validator.d.ts +7 -1
- package/dist/lib/utils/password_validator.d.ts.map +1 -1
- package/dist/page_components/dev_lock.d.ts +11 -0
- package/dist/page_components/dev_lock.d.ts.map +1 -0
- package/dist/page_components/dev_lock.js +17 -0
- package/dist/page_components/index.d.ts +1 -0
- package/dist/page_components/index.d.ts.map +1 -1
- package/dist/page_components/index.js +1 -0
- package/dist/page_components/org_management.d.ts +27 -0
- package/dist/page_components/org_management.d.ts.map +1 -0
- package/dist/page_components/org_management.js +18 -0
- package/hazo_auth_config.example.ini +30 -0
- package/package.json +27 -3
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
// file_description: service for OAuth authentication operations using hazo_connect
|
|
2
|
+
// section: imports
|
|
3
|
+
import type { HazoConnectAdapter } from "hazo_connect";
|
|
4
|
+
import { createCrudService } from "hazo_connect/server";
|
|
5
|
+
import { randomUUID } from "crypto";
|
|
6
|
+
import { create_app_logger } from "../app_logger.js";
|
|
7
|
+
import { sanitize_error_for_user } from "../utils/error_sanitizer.js";
|
|
8
|
+
import { get_line_number } from "../utils/api_route_helpers.js";
|
|
9
|
+
import { get_oauth_config } from "../oauth_config.server.js";
|
|
10
|
+
|
|
11
|
+
// section: types
|
|
12
|
+
export type GoogleOAuthData = {
|
|
13
|
+
/** Google's unique user ID (sub claim from JWT) */
|
|
14
|
+
google_id: string;
|
|
15
|
+
/** User's email address from Google */
|
|
16
|
+
email: string;
|
|
17
|
+
/** User's full name from Google profile */
|
|
18
|
+
name?: string;
|
|
19
|
+
/** User's profile picture URL from Google */
|
|
20
|
+
profile_picture_url?: string;
|
|
21
|
+
/** Whether Google has verified this email */
|
|
22
|
+
email_verified: boolean;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type OAuthLoginResult = {
|
|
26
|
+
success: boolean;
|
|
27
|
+
user_id?: string;
|
|
28
|
+
/** True if this was a newly created account */
|
|
29
|
+
is_new_user?: boolean;
|
|
30
|
+
/** True if Google was linked to an existing account */
|
|
31
|
+
was_linked?: boolean;
|
|
32
|
+
/** The user's email address */
|
|
33
|
+
email?: string;
|
|
34
|
+
/** The user's name */
|
|
35
|
+
name?: string;
|
|
36
|
+
error?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type LinkGoogleResult = {
|
|
40
|
+
success: boolean;
|
|
41
|
+
error?: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type AuthProvidersResult = {
|
|
45
|
+
success: boolean;
|
|
46
|
+
auth_providers?: string[];
|
|
47
|
+
has_password?: boolean;
|
|
48
|
+
error?: string;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// section: helpers
|
|
52
|
+
/**
|
|
53
|
+
* Handles Google OAuth login/registration flow
|
|
54
|
+
* 1. Check if user exists with google_id -> login
|
|
55
|
+
* 2. Check if user exists with email -> link Google account
|
|
56
|
+
* 3. Create new user with Google data
|
|
57
|
+
*
|
|
58
|
+
* @param adapter - The hazo_connect adapter instance
|
|
59
|
+
* @param data - Google OAuth user data
|
|
60
|
+
* @returns OAuth login result with user_id and status flags
|
|
61
|
+
*/
|
|
62
|
+
export async function handle_google_oauth_login(
|
|
63
|
+
adapter: HazoConnectAdapter,
|
|
64
|
+
data: GoogleOAuthData
|
|
65
|
+
): Promise<OAuthLoginResult> {
|
|
66
|
+
const logger = create_app_logger();
|
|
67
|
+
|
|
68
|
+
try {
|
|
69
|
+
const { google_id, email, name, profile_picture_url, email_verified } = data;
|
|
70
|
+
const oauth_config = get_oauth_config();
|
|
71
|
+
|
|
72
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
73
|
+
const now = new Date().toISOString();
|
|
74
|
+
|
|
75
|
+
// Step 1: Check if user exists with this google_id
|
|
76
|
+
const users_by_google_id = await users_service.findBy({ google_id });
|
|
77
|
+
|
|
78
|
+
if (Array.isArray(users_by_google_id) && users_by_google_id.length > 0) {
|
|
79
|
+
const user = users_by_google_id[0];
|
|
80
|
+
|
|
81
|
+
// Update last_logon timestamp
|
|
82
|
+
await users_service.updateById(user.id, {
|
|
83
|
+
last_logon: now,
|
|
84
|
+
changed_at: now,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
logger.info("oauth_service_google_login_existing_google_user", {
|
|
88
|
+
filename: "oauth_service.ts",
|
|
89
|
+
line_number: get_line_number(),
|
|
90
|
+
user_id: user.id,
|
|
91
|
+
email: user.email_address,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
user_id: user.id as string,
|
|
97
|
+
is_new_user: false,
|
|
98
|
+
was_linked: false,
|
|
99
|
+
email: user.email_address as string,
|
|
100
|
+
name: user.name as string | undefined,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Step 2: Check if user exists with this email
|
|
105
|
+
const users_by_email = await users_service.findBy({ email_address: email });
|
|
106
|
+
|
|
107
|
+
if (Array.isArray(users_by_email) && users_by_email.length > 0) {
|
|
108
|
+
const user = users_by_email[0];
|
|
109
|
+
const user_email_verified = user.email_verified as boolean;
|
|
110
|
+
|
|
111
|
+
// Check if auto-linking is enabled for unverified accounts
|
|
112
|
+
if (!user_email_verified && !oauth_config.auto_link_unverified_accounts) {
|
|
113
|
+
return {
|
|
114
|
+
success: false,
|
|
115
|
+
error: "An account with this email exists but is not verified. Please verify your email first.",
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Link Google account to existing user
|
|
120
|
+
const current_auth_providers = (user.auth_providers as string) || "email";
|
|
121
|
+
const new_auth_providers = current_auth_providers.includes("google")
|
|
122
|
+
? current_auth_providers
|
|
123
|
+
: `${current_auth_providers},google`;
|
|
124
|
+
|
|
125
|
+
const update_data: Record<string, unknown> = {
|
|
126
|
+
google_id,
|
|
127
|
+
auth_providers: new_auth_providers,
|
|
128
|
+
last_logon: now,
|
|
129
|
+
changed_at: now,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// If user was unverified and Google verified the email, mark as verified
|
|
133
|
+
if (!user_email_verified && email_verified) {
|
|
134
|
+
update_data.email_verified = true;
|
|
135
|
+
logger.info("oauth_service_auto_verified_email", {
|
|
136
|
+
filename: "oauth_service.ts",
|
|
137
|
+
line_number: get_line_number(),
|
|
138
|
+
user_id: user.id,
|
|
139
|
+
email,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Update name if not set and Google provides one
|
|
144
|
+
if (!user.name && name) {
|
|
145
|
+
update_data.name = name;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Update profile picture if not set and Google provides one
|
|
149
|
+
if (!user.profile_picture_url && profile_picture_url) {
|
|
150
|
+
update_data.profile_picture_url = profile_picture_url;
|
|
151
|
+
update_data.profile_source = "custom"; // Use 'custom' for external URLs (Google profile pics)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await users_service.updateById(user.id, update_data);
|
|
155
|
+
|
|
156
|
+
logger.info("oauth_service_google_linked_to_existing", {
|
|
157
|
+
filename: "oauth_service.ts",
|
|
158
|
+
line_number: get_line_number(),
|
|
159
|
+
user_id: user.id,
|
|
160
|
+
email,
|
|
161
|
+
was_unverified: !user_email_verified,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
return {
|
|
165
|
+
success: true,
|
|
166
|
+
user_id: user.id as string,
|
|
167
|
+
is_new_user: false,
|
|
168
|
+
was_linked: true,
|
|
169
|
+
email: user.email_address as string,
|
|
170
|
+
name: (update_data.name as string) || (user.name as string | undefined),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 3: Create new user with Google data
|
|
175
|
+
const user_id = randomUUID();
|
|
176
|
+
|
|
177
|
+
const insert_data: Record<string, unknown> = {
|
|
178
|
+
id: user_id,
|
|
179
|
+
email_address: email,
|
|
180
|
+
password_hash: "", // Empty string for Google-only users
|
|
181
|
+
email_verified: email_verified, // Trust Google's verification
|
|
182
|
+
is_active: true,
|
|
183
|
+
login_attempts: 0,
|
|
184
|
+
google_id,
|
|
185
|
+
auth_providers: "google",
|
|
186
|
+
created_at: now,
|
|
187
|
+
changed_at: now,
|
|
188
|
+
last_logon: now,
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
if (name) {
|
|
192
|
+
insert_data.name = name;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (profile_picture_url) {
|
|
196
|
+
insert_data.profile_picture_url = profile_picture_url;
|
|
197
|
+
insert_data.profile_source = "custom"; // Use 'custom' for external URLs (Google profile pics)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const inserted_users = await users_service.insert(insert_data);
|
|
201
|
+
|
|
202
|
+
if (!Array.isArray(inserted_users) || inserted_users.length === 0) {
|
|
203
|
+
return {
|
|
204
|
+
success: false,
|
|
205
|
+
error: "Failed to create user account",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
logger.info("oauth_service_google_new_user_created", {
|
|
210
|
+
filename: "oauth_service.ts",
|
|
211
|
+
line_number: get_line_number(),
|
|
212
|
+
user_id,
|
|
213
|
+
email,
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
success: true,
|
|
218
|
+
user_id,
|
|
219
|
+
is_new_user: true,
|
|
220
|
+
was_linked: false,
|
|
221
|
+
email,
|
|
222
|
+
name,
|
|
223
|
+
};
|
|
224
|
+
} catch (error) {
|
|
225
|
+
const user_friendly_error = sanitize_error_for_user(error, {
|
|
226
|
+
logToConsole: true,
|
|
227
|
+
logToLogger: true,
|
|
228
|
+
logger,
|
|
229
|
+
context: {
|
|
230
|
+
filename: "oauth_service.ts",
|
|
231
|
+
line_number: get_line_number(),
|
|
232
|
+
email: data.email,
|
|
233
|
+
operation: "handle_google_oauth_login",
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
success: false,
|
|
239
|
+
error: user_friendly_error,
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Links a Google account to an existing user
|
|
246
|
+
* @param adapter - The hazo_connect adapter instance
|
|
247
|
+
* @param user_id - The user's ID
|
|
248
|
+
* @param google_id - Google's unique user ID
|
|
249
|
+
* @returns Result indicating success or failure
|
|
250
|
+
*/
|
|
251
|
+
export async function link_google_account(
|
|
252
|
+
adapter: HazoConnectAdapter,
|
|
253
|
+
user_id: string,
|
|
254
|
+
google_id: string
|
|
255
|
+
): Promise<LinkGoogleResult> {
|
|
256
|
+
const logger = create_app_logger();
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
260
|
+
const now = new Date().toISOString();
|
|
261
|
+
|
|
262
|
+
// Get current user
|
|
263
|
+
const users = await users_service.findBy({ id: user_id });
|
|
264
|
+
|
|
265
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
266
|
+
return {
|
|
267
|
+
success: false,
|
|
268
|
+
error: "User not found",
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const user = users[0];
|
|
273
|
+
|
|
274
|
+
// Check if Google is already linked
|
|
275
|
+
if (user.google_id) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
error: "Google account is already linked",
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Update auth_providers
|
|
283
|
+
const current_auth_providers = (user.auth_providers as string) || "email";
|
|
284
|
+
const new_auth_providers = current_auth_providers.includes("google")
|
|
285
|
+
? current_auth_providers
|
|
286
|
+
: `${current_auth_providers},google`;
|
|
287
|
+
|
|
288
|
+
await users_service.updateById(user_id, {
|
|
289
|
+
google_id,
|
|
290
|
+
auth_providers: new_auth_providers,
|
|
291
|
+
changed_at: now,
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
logger.info("oauth_service_google_account_linked", {
|
|
295
|
+
filename: "oauth_service.ts",
|
|
296
|
+
line_number: get_line_number(),
|
|
297
|
+
user_id,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return { success: true };
|
|
301
|
+
} catch (error) {
|
|
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: "link_google_account",
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: false,
|
|
316
|
+
error: user_friendly_error,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Checks if a user has a password set (non-empty password_hash)
|
|
323
|
+
* @param adapter - The hazo_connect adapter instance
|
|
324
|
+
* @param user_id - The user's ID
|
|
325
|
+
* @returns True if user has a password set
|
|
326
|
+
*/
|
|
327
|
+
export async function user_has_password(
|
|
328
|
+
adapter: HazoConnectAdapter,
|
|
329
|
+
user_id: string
|
|
330
|
+
): Promise<boolean> {
|
|
331
|
+
try {
|
|
332
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
333
|
+
const users = await users_service.findBy({ id: user_id });
|
|
334
|
+
|
|
335
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const password_hash = users[0].password_hash as string;
|
|
340
|
+
return password_hash !== null && password_hash !== undefined && password_hash !== "";
|
|
341
|
+
} catch {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Checks if a user has a password set by email
|
|
348
|
+
* @param adapter - The hazo_connect adapter instance
|
|
349
|
+
* @param email - The user's email address
|
|
350
|
+
* @returns True if user has a password set
|
|
351
|
+
*/
|
|
352
|
+
export async function user_has_password_by_email(
|
|
353
|
+
adapter: HazoConnectAdapter,
|
|
354
|
+
email: string
|
|
355
|
+
): Promise<boolean> {
|
|
356
|
+
try {
|
|
357
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
358
|
+
const users = await users_service.findBy({ email_address: email });
|
|
359
|
+
|
|
360
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const password_hash = users[0].password_hash as string;
|
|
365
|
+
return password_hash !== null && password_hash !== undefined && password_hash !== "";
|
|
366
|
+
} catch {
|
|
367
|
+
return false;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* Gets a user's authentication providers and password status
|
|
373
|
+
* @param adapter - The hazo_connect adapter instance
|
|
374
|
+
* @param user_id - The user's ID
|
|
375
|
+
* @returns Auth providers array and has_password flag
|
|
376
|
+
*/
|
|
377
|
+
export async function get_user_auth_providers(
|
|
378
|
+
adapter: HazoConnectAdapter,
|
|
379
|
+
user_id: string
|
|
380
|
+
): Promise<AuthProvidersResult> {
|
|
381
|
+
try {
|
|
382
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
383
|
+
const users = await users_service.findBy({ id: user_id });
|
|
384
|
+
|
|
385
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
386
|
+
return {
|
|
387
|
+
success: false,
|
|
388
|
+
error: "User not found",
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const user = users[0];
|
|
393
|
+
const auth_providers_str = (user.auth_providers as string) || "email";
|
|
394
|
+
const auth_providers = auth_providers_str.split(",").map((p) => p.trim());
|
|
395
|
+
|
|
396
|
+
const password_hash = user.password_hash as string;
|
|
397
|
+
const has_password = password_hash !== null && password_hash !== undefined && password_hash !== "";
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
success: true,
|
|
401
|
+
auth_providers,
|
|
402
|
+
has_password,
|
|
403
|
+
};
|
|
404
|
+
} catch (error) {
|
|
405
|
+
const logger = create_app_logger();
|
|
406
|
+
const user_friendly_error = sanitize_error_for_user(error, {
|
|
407
|
+
logToConsole: true,
|
|
408
|
+
logToLogger: true,
|
|
409
|
+
logger,
|
|
410
|
+
context: {
|
|
411
|
+
filename: "oauth_service.ts",
|
|
412
|
+
line_number: get_line_number(),
|
|
413
|
+
user_id,
|
|
414
|
+
operation: "get_user_auth_providers",
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
success: false,
|
|
420
|
+
error: user_friendly_error,
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Sets a password for a user who doesn't have one (e.g., Google-only users)
|
|
427
|
+
* @param adapter - The hazo_connect adapter instance
|
|
428
|
+
* @param user_id - The user's ID
|
|
429
|
+
* @param password_hash - The hashed password to set
|
|
430
|
+
* @returns Result indicating success or failure
|
|
431
|
+
*/
|
|
432
|
+
export async function set_user_password(
|
|
433
|
+
adapter: HazoConnectAdapter,
|
|
434
|
+
user_id: string,
|
|
435
|
+
password_hash: string
|
|
436
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
437
|
+
const logger = create_app_logger();
|
|
438
|
+
|
|
439
|
+
try {
|
|
440
|
+
const users_service = createCrudService(adapter, "hazo_users");
|
|
441
|
+
const now = new Date().toISOString();
|
|
442
|
+
|
|
443
|
+
// Get current user
|
|
444
|
+
const users = await users_service.findBy({ id: user_id });
|
|
445
|
+
|
|
446
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
447
|
+
return {
|
|
448
|
+
success: false,
|
|
449
|
+
error: "User not found",
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const user = users[0];
|
|
454
|
+
|
|
455
|
+
// Update password and auth_providers
|
|
456
|
+
const current_auth_providers = (user.auth_providers as string) || "";
|
|
457
|
+
const new_auth_providers = current_auth_providers.includes("email")
|
|
458
|
+
? current_auth_providers
|
|
459
|
+
: current_auth_providers
|
|
460
|
+
? `${current_auth_providers},email`
|
|
461
|
+
: "email";
|
|
462
|
+
|
|
463
|
+
await users_service.updateById(user_id, {
|
|
464
|
+
password_hash,
|
|
465
|
+
auth_providers: new_auth_providers,
|
|
466
|
+
changed_at: now,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
logger.info("oauth_service_password_set", {
|
|
470
|
+
filename: "oauth_service.ts",
|
|
471
|
+
line_number: get_line_number(),
|
|
472
|
+
user_id,
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
return { success: true };
|
|
476
|
+
} catch (error) {
|
|
477
|
+
const user_friendly_error = sanitize_error_for_user(error, {
|
|
478
|
+
logToConsole: true,
|
|
479
|
+
logToLogger: true,
|
|
480
|
+
logger,
|
|
481
|
+
context: {
|
|
482
|
+
filename: "oauth_service.ts",
|
|
483
|
+
line_number: get_line_number(),
|
|
484
|
+
user_id,
|
|
485
|
+
operation: "set_user_password",
|
|
486
|
+
},
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
return {
|
|
490
|
+
success: false,
|
|
491
|
+
error: user_friendly_error,
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
}
|