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,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
+