hazo_auth 4.2.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.
- 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 +110 -0
- package/cli-src/lib/auth/auth_utils.server.ts +196 -0
- package/cli-src/lib/auth/hazo_get_auth.server.ts +512 -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/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 +91 -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 +199 -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/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/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 +177 -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/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/shared/config/layout_customization.d.ts +2 -7
- package/dist/components/layouts/shared/config/layout_customization.d.ts.map +1 -1
- package/dist/lib/utils/password_validator.d.ts +7 -1
- package/dist/lib/utils/password_validator.d.ts.map +1 -1
- package/package.json +5 -2
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
// file_description: server-side implementation of hazo_get_auth utility for API routes
|
|
2
|
+
// section: imports
|
|
3
|
+
import { NextRequest } from "next/server";
|
|
4
|
+
import { get_hazo_connect_instance } from "../hazo_connect_instance.server";
|
|
5
|
+
import { createCrudService } from "hazo_connect/server";
|
|
6
|
+
import { create_app_logger } from "../app_logger";
|
|
7
|
+
import { get_filename, get_line_number } from "../utils/api_route_helpers";
|
|
8
|
+
import type { HazoAuthResult, HazoAuthUser, HazoAuthOptions, ScopeAccessInfo } from "./auth_types";
|
|
9
|
+
import { PermissionError, ScopeAccessError } from "./auth_types";
|
|
10
|
+
import { get_auth_cache } from "./auth_cache";
|
|
11
|
+
import { get_scope_cache, type UserScopeEntry } from "./scope_cache";
|
|
12
|
+
import { get_rate_limiter } from "./auth_rate_limiter";
|
|
13
|
+
import { get_auth_utility_config } from "../auth_utility_config.server";
|
|
14
|
+
import { validate_session_token } from "../services/session_token_service";
|
|
15
|
+
import { is_hrbac_enabled, get_scope_hierarchy_config } from "../scope_hierarchy_config.server";
|
|
16
|
+
import { check_user_scope_access, get_user_scopes, type UserScope } from "../services/user_scope_service";
|
|
17
|
+
import { is_valid_scope_level, type ScopeLevel } from "../services/scope_service";
|
|
18
|
+
|
|
19
|
+
// section: helpers
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gets client IP address from request
|
|
23
|
+
* @param request - NextRequest object
|
|
24
|
+
* @returns IP address string
|
|
25
|
+
*/
|
|
26
|
+
function get_client_ip(request: NextRequest): string {
|
|
27
|
+
const forwarded = request.headers.get("x-forwarded-for");
|
|
28
|
+
if (forwarded) {
|
|
29
|
+
return forwarded.split(",")[0].trim();
|
|
30
|
+
}
|
|
31
|
+
const real_ip = request.headers.get("x-real-ip");
|
|
32
|
+
if (real_ip) {
|
|
33
|
+
return real_ip;
|
|
34
|
+
}
|
|
35
|
+
return "unknown";
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Fetches user data and permissions from database
|
|
40
|
+
* @param user_id - User ID
|
|
41
|
+
* @returns Object with user, permissions, and role_ids
|
|
42
|
+
*/
|
|
43
|
+
async function fetch_user_data_from_db(user_id: string): Promise<{
|
|
44
|
+
user: HazoAuthUser;
|
|
45
|
+
permissions: string[];
|
|
46
|
+
role_ids: number[];
|
|
47
|
+
}> {
|
|
48
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
49
|
+
const users_service = createCrudService(hazoConnect, "hazo_users");
|
|
50
|
+
const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
|
|
51
|
+
const role_permissions_service = createCrudService(
|
|
52
|
+
hazoConnect,
|
|
53
|
+
"hazo_role_permissions",
|
|
54
|
+
);
|
|
55
|
+
const permissions_service = createCrudService(
|
|
56
|
+
hazoConnect,
|
|
57
|
+
"hazo_permissions",
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Fetch user
|
|
61
|
+
const users = await users_service.findBy({ id: user_id });
|
|
62
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
63
|
+
throw new Error("User not found");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const user_db = users[0];
|
|
67
|
+
|
|
68
|
+
// Check if user is active
|
|
69
|
+
if (user_db.is_active === false) {
|
|
70
|
+
throw new Error("User is inactive");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Build user object
|
|
74
|
+
const user: HazoAuthUser = {
|
|
75
|
+
id: user_db.id as string,
|
|
76
|
+
name: (user_db.name as string | null) || null,
|
|
77
|
+
email_address: user_db.email_address as string,
|
|
78
|
+
is_active: user_db.is_active === true,
|
|
79
|
+
profile_picture_url:
|
|
80
|
+
(user_db.profile_picture_url as string | null) || null,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Fetch user roles
|
|
84
|
+
const user_roles = await user_roles_service.findBy({ user_id });
|
|
85
|
+
const role_ids: number[] = [];
|
|
86
|
+
if (Array.isArray(user_roles)) {
|
|
87
|
+
for (const ur of user_roles) {
|
|
88
|
+
const role_id = ur.role_id as number | undefined;
|
|
89
|
+
if (role_id !== undefined) {
|
|
90
|
+
role_ids.push(role_id);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Fetch role permissions
|
|
96
|
+
const permissions_set = new Set<string>();
|
|
97
|
+
if (role_ids.length > 0) {
|
|
98
|
+
const role_permissions = await role_permissions_service.findBy({});
|
|
99
|
+
if (Array.isArray(role_permissions)) {
|
|
100
|
+
// Filter role_permissions for user's roles
|
|
101
|
+
const user_role_permissions = role_permissions.filter((rp) =>
|
|
102
|
+
role_ids.includes(rp.role_id as number),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Get permission IDs
|
|
106
|
+
const permission_ids = new Set<number>();
|
|
107
|
+
for (const rp of user_role_permissions) {
|
|
108
|
+
const perm_id = rp.permission_id as number | undefined;
|
|
109
|
+
if (perm_id !== undefined) {
|
|
110
|
+
permission_ids.add(perm_id);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Fetch permission names
|
|
115
|
+
if (permission_ids.size > 0) {
|
|
116
|
+
const permissions = await permissions_service.findBy({});
|
|
117
|
+
if (Array.isArray(permissions)) {
|
|
118
|
+
for (const perm of permissions) {
|
|
119
|
+
const perm_id = perm.id as number | undefined;
|
|
120
|
+
if (perm_id !== undefined && permission_ids.has(perm_id)) {
|
|
121
|
+
const perm_name = perm.permission_name as string | undefined;
|
|
122
|
+
if (perm_name) {
|
|
123
|
+
permissions_set.add(perm_name);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const permissions = Array.from(permissions_set);
|
|
133
|
+
|
|
134
|
+
return { user, permissions, role_ids };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Checks if user has required permissions
|
|
139
|
+
* @param user_permissions - User's permissions
|
|
140
|
+
* @param required_permissions - Required permissions
|
|
141
|
+
* @returns Object with permission_ok and missing_permissions
|
|
142
|
+
*/
|
|
143
|
+
function check_permissions(
|
|
144
|
+
user_permissions: string[],
|
|
145
|
+
required_permissions: string[],
|
|
146
|
+
): { permission_ok: boolean; missing_permissions: string[] } {
|
|
147
|
+
const user_perms_set = new Set(user_permissions);
|
|
148
|
+
const missing = required_permissions.filter(
|
|
149
|
+
(perm) => !user_perms_set.has(perm),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
permission_ok: missing.length === 0,
|
|
154
|
+
missing_permissions: missing,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Gets user-friendly error message for missing permissions
|
|
160
|
+
* @param missing_permissions - Array of missing permission names
|
|
161
|
+
* @param config - Auth utility config
|
|
162
|
+
* @returns User-friendly message or undefined
|
|
163
|
+
*/
|
|
164
|
+
function get_friendly_error_message(
|
|
165
|
+
missing_permissions: string[],
|
|
166
|
+
config: ReturnType<typeof get_auth_utility_config>,
|
|
167
|
+
): string | undefined {
|
|
168
|
+
if (!config.enable_friendly_error_messages) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Try to get messages for each missing permission
|
|
173
|
+
const messages: string[] = [];
|
|
174
|
+
for (const perm of missing_permissions) {
|
|
175
|
+
const message = config.permission_error_messages.get(perm);
|
|
176
|
+
if (message) {
|
|
177
|
+
messages.push(message);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (messages.length > 0) {
|
|
182
|
+
return messages.join(". ");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Default message if no specific mapping
|
|
186
|
+
return "You don't have the required permissions to perform this action. Please contact your administrator.";
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Gets user scopes with caching
|
|
191
|
+
* @param user_id - User ID
|
|
192
|
+
* @returns Array of user scope entries
|
|
193
|
+
*/
|
|
194
|
+
async function get_user_scopes_cached(user_id: string): Promise<UserScopeEntry[]> {
|
|
195
|
+
const scope_config = get_scope_hierarchy_config();
|
|
196
|
+
const scope_cache = get_scope_cache(
|
|
197
|
+
scope_config.scope_cache_max_entries,
|
|
198
|
+
scope_config.scope_cache_ttl_minutes,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Check cache
|
|
202
|
+
const cached = scope_cache.get(user_id);
|
|
203
|
+
if (cached) {
|
|
204
|
+
return cached.scopes;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Fetch from database
|
|
208
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
209
|
+
const result = await get_user_scopes(hazoConnect, user_id);
|
|
210
|
+
|
|
211
|
+
if (!result.success || !result.scopes) {
|
|
212
|
+
return [];
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Convert to cache entry format and cache
|
|
216
|
+
const scopes: UserScopeEntry[] = result.scopes.map((s: UserScope) => ({
|
|
217
|
+
scope_type: s.scope_type as ScopeLevel,
|
|
218
|
+
scope_id: s.scope_id,
|
|
219
|
+
scope_seq: s.scope_seq,
|
|
220
|
+
}));
|
|
221
|
+
|
|
222
|
+
scope_cache.set(user_id, scopes);
|
|
223
|
+
|
|
224
|
+
return scopes;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Checks if user has access to a specific scope
|
|
229
|
+
* @param user_id - User ID
|
|
230
|
+
* @param scope_type - Scope level
|
|
231
|
+
* @param scope_id - Scope ID (optional)
|
|
232
|
+
* @param scope_seq - Scope seq (optional)
|
|
233
|
+
* @returns Object with scope_ok and access_via info
|
|
234
|
+
*/
|
|
235
|
+
async function check_scope_access(
|
|
236
|
+
user_id: string,
|
|
237
|
+
scope_type: string,
|
|
238
|
+
scope_id?: string,
|
|
239
|
+
scope_seq?: string,
|
|
240
|
+
): Promise<{
|
|
241
|
+
scope_ok: boolean;
|
|
242
|
+
scope_access_via?: ScopeAccessInfo;
|
|
243
|
+
user_scopes: Array<{ scope_type: string; scope_id: string; scope_seq: string }>;
|
|
244
|
+
}> {
|
|
245
|
+
const logger = create_app_logger();
|
|
246
|
+
|
|
247
|
+
// Validate scope_type
|
|
248
|
+
if (!is_valid_scope_level(scope_type)) {
|
|
249
|
+
logger.warn("auth_utility_invalid_scope_type", {
|
|
250
|
+
filename: get_filename(),
|
|
251
|
+
line_number: get_line_number(),
|
|
252
|
+
scope_type,
|
|
253
|
+
user_id,
|
|
254
|
+
});
|
|
255
|
+
return { scope_ok: false, user_scopes: [] };
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
259
|
+
const result = await check_user_scope_access(
|
|
260
|
+
hazoConnect,
|
|
261
|
+
user_id,
|
|
262
|
+
scope_type as ScopeLevel,
|
|
263
|
+
scope_id,
|
|
264
|
+
scope_seq,
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
const user_scopes = (result.user_scopes || []).map((s) => ({
|
|
268
|
+
scope_type: s.scope_type,
|
|
269
|
+
scope_id: s.scope_id,
|
|
270
|
+
scope_seq: s.scope_seq,
|
|
271
|
+
}));
|
|
272
|
+
|
|
273
|
+
if (result.has_access && result.access_via) {
|
|
274
|
+
return {
|
|
275
|
+
scope_ok: true,
|
|
276
|
+
scope_access_via: {
|
|
277
|
+
scope_type: result.access_via.scope_type,
|
|
278
|
+
scope_id: result.access_via.scope_id,
|
|
279
|
+
scope_seq: result.access_via.scope_seq,
|
|
280
|
+
},
|
|
281
|
+
user_scopes,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return { scope_ok: false, user_scopes };
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// section: main_function
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Main hazo_get_auth function for server-side use in API routes
|
|
292
|
+
* Returns user details, permissions, and checks required permissions
|
|
293
|
+
* Optionally checks HRBAC scope access when scope options are provided
|
|
294
|
+
* @param request - NextRequest object
|
|
295
|
+
* @param options - Optional parameters for permission checking and HRBAC scope checking
|
|
296
|
+
* @returns HazoAuthResult with user data, permissions, and optional scope access info
|
|
297
|
+
* @throws PermissionError if strict mode and permissions are missing
|
|
298
|
+
* @throws ScopeAccessError if strict mode and scope access is denied
|
|
299
|
+
*/
|
|
300
|
+
export async function hazo_get_auth(
|
|
301
|
+
request: NextRequest,
|
|
302
|
+
options?: HazoAuthOptions,
|
|
303
|
+
): Promise<HazoAuthResult> {
|
|
304
|
+
const logger = create_app_logger();
|
|
305
|
+
const config = get_auth_utility_config();
|
|
306
|
+
const cache = get_auth_cache(
|
|
307
|
+
config.cache_max_users,
|
|
308
|
+
config.cache_ttl_minutes,
|
|
309
|
+
config.cache_max_age_minutes,
|
|
310
|
+
);
|
|
311
|
+
const rate_limiter = get_rate_limiter();
|
|
312
|
+
|
|
313
|
+
// Fast path: Check for authentication cookies
|
|
314
|
+
// Priority: 1. JWT session token (new), 2. Simple cookies (backward compatibility)
|
|
315
|
+
let user_id: string | undefined;
|
|
316
|
+
let user_email: string | undefined;
|
|
317
|
+
|
|
318
|
+
// Check for JWT session token first
|
|
319
|
+
const session_token = request.cookies.get("hazo_auth_session")?.value;
|
|
320
|
+
if (session_token) {
|
|
321
|
+
try {
|
|
322
|
+
const token_result = await validate_session_token(session_token);
|
|
323
|
+
if (token_result.valid && token_result.user_id && token_result.email) {
|
|
324
|
+
user_id = token_result.user_id;
|
|
325
|
+
user_email = token_result.email;
|
|
326
|
+
}
|
|
327
|
+
} catch (token_error) {
|
|
328
|
+
// If token validation fails, fall back to simple cookies
|
|
329
|
+
const token_error_message = token_error instanceof Error ? token_error.message : "Unknown error";
|
|
330
|
+
logger.debug("auth_utility_jwt_validation_failed", {
|
|
331
|
+
filename: get_filename(),
|
|
332
|
+
line_number: get_line_number(),
|
|
333
|
+
error: token_error_message,
|
|
334
|
+
note: "Falling back to simple cookie check",
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Fall back to simple cookies if JWT not present or invalid (backward compatibility)
|
|
340
|
+
if (!user_id || !user_email) {
|
|
341
|
+
user_id = request.cookies.get("hazo_auth_user_id")?.value;
|
|
342
|
+
user_email = request.cookies.get("hazo_auth_user_email")?.value;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (!user_id || !user_email) {
|
|
346
|
+
// Unauthenticated - check rate limit by IP
|
|
347
|
+
const client_ip = get_client_ip(request);
|
|
348
|
+
const ip_key = `ip:${client_ip}`;
|
|
349
|
+
if (!rate_limiter.check(ip_key, config.rate_limit_per_ip)) {
|
|
350
|
+
logger.warn("auth_utility_rate_limit_exceeded_ip", {
|
|
351
|
+
filename: get_filename(),
|
|
352
|
+
line_number: get_line_number(),
|
|
353
|
+
ip: client_ip,
|
|
354
|
+
});
|
|
355
|
+
throw new Error("Rate limit exceeded. Please try again later.");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
authenticated: false,
|
|
360
|
+
user: null,
|
|
361
|
+
permissions: [],
|
|
362
|
+
permission_ok: false,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Authenticated - check rate limit by user
|
|
367
|
+
const user_key = `user:${user_id}`;
|
|
368
|
+
if (!rate_limiter.check(user_key, config.rate_limit_per_user)) {
|
|
369
|
+
logger.warn("auth_utility_rate_limit_exceeded_user", {
|
|
370
|
+
filename: get_filename(),
|
|
371
|
+
line_number: get_line_number(),
|
|
372
|
+
user_id,
|
|
373
|
+
});
|
|
374
|
+
throw new Error("Rate limit exceeded. Please try again later.");
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// Check cache
|
|
378
|
+
let cached_entry = cache.get(user_id);
|
|
379
|
+
let user: HazoAuthUser;
|
|
380
|
+
let permissions: string[];
|
|
381
|
+
let role_ids: number[];
|
|
382
|
+
|
|
383
|
+
if (cached_entry) {
|
|
384
|
+
// Cache hit
|
|
385
|
+
user = cached_entry.user;
|
|
386
|
+
permissions = cached_entry.permissions;
|
|
387
|
+
role_ids = cached_entry.role_ids;
|
|
388
|
+
} else {
|
|
389
|
+
// Cache miss - fetch from database
|
|
390
|
+
try {
|
|
391
|
+
const user_data = await fetch_user_data_from_db(user_id);
|
|
392
|
+
user = user_data.user;
|
|
393
|
+
permissions = user_data.permissions;
|
|
394
|
+
role_ids = user_data.role_ids;
|
|
395
|
+
|
|
396
|
+
// Update cache
|
|
397
|
+
cache.set(user_id, user, permissions, role_ids);
|
|
398
|
+
} catch (error) {
|
|
399
|
+
const error_message =
|
|
400
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
401
|
+
logger.error("auth_utility_fetch_user_failed", {
|
|
402
|
+
filename: get_filename(),
|
|
403
|
+
line_number: get_line_number(),
|
|
404
|
+
user_id,
|
|
405
|
+
error: error_message,
|
|
406
|
+
});
|
|
407
|
+
|
|
408
|
+
return {
|
|
409
|
+
authenticated: false,
|
|
410
|
+
user: null,
|
|
411
|
+
permissions: [],
|
|
412
|
+
permission_ok: false,
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Check permissions if required
|
|
418
|
+
let permission_ok = true;
|
|
419
|
+
let missing_permissions: string[] | undefined;
|
|
420
|
+
|
|
421
|
+
if (options?.required_permissions && options.required_permissions.length > 0) {
|
|
422
|
+
const check_result = check_permissions(
|
|
423
|
+
permissions,
|
|
424
|
+
options.required_permissions,
|
|
425
|
+
);
|
|
426
|
+
permission_ok = check_result.permission_ok;
|
|
427
|
+
missing_permissions = check_result.missing_permissions;
|
|
428
|
+
|
|
429
|
+
// Log permission denial if enabled
|
|
430
|
+
if (!permission_ok && config.log_permission_denials) {
|
|
431
|
+
const client_ip = get_client_ip(request);
|
|
432
|
+
logger.warn("auth_utility_permission_denied", {
|
|
433
|
+
filename: get_filename(),
|
|
434
|
+
line_number: get_line_number(),
|
|
435
|
+
user_id,
|
|
436
|
+
requested_permissions: options.required_permissions,
|
|
437
|
+
missing_permissions,
|
|
438
|
+
user_permissions: permissions,
|
|
439
|
+
ip: client_ip,
|
|
440
|
+
});
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// Throw error if strict mode
|
|
444
|
+
if (!permission_ok && options.strict) {
|
|
445
|
+
const friendly_message = get_friendly_error_message(
|
|
446
|
+
missing_permissions,
|
|
447
|
+
config,
|
|
448
|
+
);
|
|
449
|
+
|
|
450
|
+
throw new PermissionError(
|
|
451
|
+
missing_permissions,
|
|
452
|
+
permissions,
|
|
453
|
+
options.required_permissions,
|
|
454
|
+
friendly_message,
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Check HRBAC scope access if enabled and scope options provided
|
|
460
|
+
let scope_ok: boolean | undefined;
|
|
461
|
+
let scope_access_via: ScopeAccessInfo | undefined;
|
|
462
|
+
|
|
463
|
+
const hrbac_enabled = is_hrbac_enabled();
|
|
464
|
+
const has_scope_options = options?.scope_type && (options?.scope_id || options?.scope_seq);
|
|
465
|
+
|
|
466
|
+
if (hrbac_enabled && has_scope_options) {
|
|
467
|
+
const scope_result = await check_scope_access(
|
|
468
|
+
user.id,
|
|
469
|
+
options!.scope_type!,
|
|
470
|
+
options?.scope_id,
|
|
471
|
+
options?.scope_seq,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
scope_ok = scope_result.scope_ok;
|
|
475
|
+
scope_access_via = scope_result.scope_access_via;
|
|
476
|
+
|
|
477
|
+
// Log scope denial if permission logging is enabled
|
|
478
|
+
if (!scope_ok && config.log_permission_denials) {
|
|
479
|
+
const client_ip = get_client_ip(request);
|
|
480
|
+
logger.warn("auth_utility_scope_access_denied", {
|
|
481
|
+
filename: get_filename(),
|
|
482
|
+
line_number: get_line_number(),
|
|
483
|
+
user_id: user.id,
|
|
484
|
+
scope_type: options!.scope_type,
|
|
485
|
+
scope_id: options?.scope_id,
|
|
486
|
+
scope_seq: options?.scope_seq,
|
|
487
|
+
user_scopes: scope_result.user_scopes,
|
|
488
|
+
ip: client_ip,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// Throw error if strict mode and scope access denied
|
|
493
|
+
if (!scope_ok && options?.strict) {
|
|
494
|
+
throw new ScopeAccessError(
|
|
495
|
+
options!.scope_type!,
|
|
496
|
+
options?.scope_id || options?.scope_seq || "unknown",
|
|
497
|
+
scope_result.user_scopes,
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return {
|
|
503
|
+
authenticated: true,
|
|
504
|
+
user,
|
|
505
|
+
permissions,
|
|
506
|
+
permission_ok,
|
|
507
|
+
missing_permissions,
|
|
508
|
+
scope_ok,
|
|
509
|
+
scope_access_via,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// file_description: barrel export for auth utilities
|
|
2
|
+
// section: type_exports
|
|
3
|
+
export * from "./auth_types";
|
|
4
|
+
|
|
5
|
+
// section: server_exports
|
|
6
|
+
export { hazo_get_auth } from "./hazo_get_auth.server";
|
|
7
|
+
export {
|
|
8
|
+
get_authenticated_user,
|
|
9
|
+
require_auth,
|
|
10
|
+
is_authenticated,
|
|
11
|
+
} from "./auth_utils.server";
|
|
12
|
+
export type { AuthResult, AuthUser } from "./auth_utils.server";
|
|
13
|
+
|
|
14
|
+
// section: client_exports
|
|
15
|
+
export { get_server_auth_user } from "./server_auth";
|
|
16
|
+
export type { ServerAuthResult } from "./server_auth";
|
|
17
|
+
|
|
18
|
+
// section: cache_exports
|
|
19
|
+
export { get_auth_cache, reset_auth_cache } from "./auth_cache";
|
|
20
|
+
|
|
21
|
+
// section: rate_limiter_exports
|
|
22
|
+
export { get_rate_limiter, reset_rate_limiter } from "./auth_rate_limiter";
|
|
23
|
+
|