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