hazo_auth 0.3.0 → 1.0.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 (87) hide show
  1. package/hazo_auth_config.example.ini +39 -0
  2. package/instrumentation.ts +1 -1
  3. package/next.config.mjs +1 -1
  4. package/package.json +3 -1
  5. package/src/app/api/{auth → hazo_auth/auth}/upload_profile_picture/route.ts +2 -2
  6. package/src/app/api/{auth → hazo_auth}/change_password/route.ts +23 -0
  7. package/src/app/api/hazo_auth/get_auth/route.ts +89 -0
  8. package/src/app/api/hazo_auth/invalidate_cache/route.ts +139 -0
  9. package/src/app/api/{auth → hazo_auth}/logout/route.ts +27 -0
  10. package/src/app/api/hazo_auth/upload_profile_picture/route.ts +268 -0
  11. package/src/app/api/hazo_auth/user_management/permissions/route.ts +367 -0
  12. package/src/app/api/hazo_auth/user_management/roles/route.ts +442 -0
  13. package/src/app/api/hazo_auth/user_management/users/roles/route.ts +367 -0
  14. package/src/app/api/hazo_auth/user_management/users/route.ts +239 -0
  15. package/src/app/api/{auth → hazo_auth}/validate_reset_token/route.ts +3 -0
  16. package/src/app/api/{auth → hazo_auth}/verify_email/route.ts +3 -0
  17. package/src/app/globals.css +1 -1
  18. package/src/app/hazo_auth/user_management/page.tsx +14 -0
  19. package/src/app/hazo_auth/user_management/user_management_page_client.tsx +16 -0
  20. package/src/app/hazo_connect/api/sqlite/data/route.ts +7 -1
  21. package/src/app/hazo_connect/api/sqlite/schema/route.ts +14 -4
  22. package/src/app/hazo_connect/api/sqlite/tables/route.ts +14 -4
  23. package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +40 -3
  24. package/src/app/layout.tsx +1 -1
  25. package/src/app/page.tsx +4 -4
  26. package/src/components/layouts/email_verification/hooks/use_email_verification.ts +4 -4
  27. package/src/components/layouts/email_verification/index.tsx +1 -1
  28. package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +1 -1
  29. package/src/components/layouts/login/hooks/use_login_form.ts +2 -2
  30. package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +1 -1
  31. package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +2 -2
  32. package/src/components/layouts/my_settings/hooks/use_my_settings.ts +5 -5
  33. package/src/components/layouts/my_settings/index.tsx +1 -1
  34. package/src/components/layouts/register/hooks/use_register_form.ts +1 -1
  35. package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +3 -3
  36. package/src/components/layouts/reset_password/index.tsx +2 -2
  37. package/src/components/layouts/shared/components/logout_button.tsx +1 -1
  38. package/src/components/layouts/shared/components/profile_pic_menu.tsx +4 -4
  39. package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +19 -7
  40. package/src/components/layouts/shared/components/unauthorized_guard.tsx +1 -1
  41. package/src/components/layouts/shared/hooks/use_auth_status.ts +1 -1
  42. package/src/components/layouts/shared/hooks/use_hazo_auth.ts +158 -0
  43. package/src/components/layouts/user_management/components/roles_matrix.tsx +607 -0
  44. package/src/components/layouts/user_management/index.tsx +1295 -0
  45. package/src/components/ui/alert-dialog.tsx +141 -0
  46. package/src/components/ui/checkbox.tsx +30 -0
  47. package/src/components/ui/table.tsx +120 -0
  48. package/src/lib/auth/auth_cache.ts +220 -0
  49. package/src/lib/auth/auth_rate_limiter.ts +121 -0
  50. package/src/lib/auth/auth_types.ts +65 -0
  51. package/src/lib/auth/hazo_get_auth.server.ts +333 -0
  52. package/src/lib/auth_utility_config.server.ts +136 -0
  53. package/src/lib/hazo_connect_setup.server.ts +2 -3
  54. package/src/lib/my_settings_config.server.ts +1 -1
  55. package/src/lib/profile_pic_menu_config.server.ts +4 -4
  56. package/src/lib/reset_password_config.server.ts +5 -5
  57. package/src/lib/services/email_service.ts +2 -2
  58. package/src/lib/services/profile_picture_remove_service.ts +1 -1
  59. package/src/lib/services/token_service.ts +2 -2
  60. package/src/lib/user_management_config.server.ts +40 -0
  61. package/src/lib/utils.ts +1 -1
  62. package/src/middleware.ts +15 -13
  63. package/src/server/types/express.d.ts +1 -0
  64. package/src/stories/project_overview.stories.tsx +1 -1
  65. package/tailwind.config.ts +1 -1
  66. /package/src/app/api/{auth → hazo_auth}/forgot_password/route.ts +0 -0
  67. /package/src/app/api/{auth → hazo_auth}/library_photos/route.ts +0 -0
  68. /package/src/app/api/{auth → hazo_auth}/login/route.ts +0 -0
  69. /package/src/app/api/{auth → hazo_auth}/me/route.ts +0 -0
  70. /package/src/app/api/{auth → hazo_auth}/profile_picture/[filename]/route.ts +0 -0
  71. /package/src/app/api/{auth → hazo_auth}/register/route.ts +0 -0
  72. /package/src/app/api/{auth → hazo_auth}/remove_profile_picture/route.ts +0 -0
  73. /package/src/app/api/{auth → hazo_auth}/resend_verification/route.ts +0 -0
  74. /package/src/app/api/{auth → hazo_auth}/reset_password/route.ts +0 -0
  75. /package/src/app/api/{auth → hazo_auth}/update_user/route.ts +0 -0
  76. /package/src/app/{forgot_password → hazo_auth/forgot_password}/forgot_password_page_client.tsx +0 -0
  77. /package/src/app/{forgot_password → hazo_auth/forgot_password}/page.tsx +0 -0
  78. /package/src/app/{login → hazo_auth/login}/login_page_client.tsx +0 -0
  79. /package/src/app/{login → hazo_auth/login}/page.tsx +0 -0
  80. /package/src/app/{my_settings → hazo_auth/my_settings}/my_settings_page_client.tsx +0 -0
  81. /package/src/app/{my_settings → hazo_auth/my_settings}/page.tsx +0 -0
  82. /package/src/app/{register → hazo_auth/register}/page.tsx +0 -0
  83. /package/src/app/{register → hazo_auth/register}/register_page_client.tsx +0 -0
  84. /package/src/app/{reset_password → hazo_auth/reset_password}/page.tsx +0 -0
  85. /package/src/app/{reset_password → hazo_auth/reset_password}/reset_password_page_client.tsx +0 -0
  86. /package/src/app/{verify_email → hazo_auth/verify_email}/page.tsx +0 -0
  87. /package/src/app/{verify_email → hazo_auth/verify_email}/verify_email_page_client.tsx +0 -0
@@ -0,0 +1,333 @@
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 } from "./auth_types";
9
+ import { PermissionError } from "./auth_types";
10
+ import { get_auth_cache } from "./auth_cache";
11
+ import { get_rate_limiter } from "./auth_rate_limiter";
12
+ import { get_auth_utility_config } from "../auth_utility_config.server";
13
+
14
+ // section: helpers
15
+
16
+ /**
17
+ * Gets client IP address from request
18
+ * @param request - NextRequest object
19
+ * @returns IP address string
20
+ */
21
+ function get_client_ip(request: NextRequest): string {
22
+ const forwarded = request.headers.get("x-forwarded-for");
23
+ if (forwarded) {
24
+ return forwarded.split(",")[0].trim();
25
+ }
26
+ const real_ip = request.headers.get("x-real-ip");
27
+ if (real_ip) {
28
+ return real_ip;
29
+ }
30
+ return "unknown";
31
+ }
32
+
33
+ /**
34
+ * Fetches user data and permissions from database
35
+ * @param user_id - User ID
36
+ * @returns Object with user, permissions, and role_ids
37
+ */
38
+ async function fetch_user_data_from_db(user_id: string): Promise<{
39
+ user: HazoAuthUser;
40
+ permissions: string[];
41
+ role_ids: number[];
42
+ }> {
43
+ const hazoConnect = get_hazo_connect_instance();
44
+ const users_service = createCrudService(hazoConnect, "hazo_users");
45
+ const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
46
+ const role_permissions_service = createCrudService(
47
+ hazoConnect,
48
+ "hazo_role_permissions",
49
+ );
50
+ const permissions_service = createCrudService(
51
+ hazoConnect,
52
+ "hazo_permissions",
53
+ );
54
+
55
+ // Fetch user
56
+ const users = await users_service.findBy({ id: user_id });
57
+ if (!Array.isArray(users) || users.length === 0) {
58
+ throw new Error("User not found");
59
+ }
60
+
61
+ const user_db = users[0];
62
+
63
+ // Check if user is active
64
+ if (user_db.is_active === false) {
65
+ throw new Error("User is inactive");
66
+ }
67
+
68
+ // Build user object
69
+ const user: HazoAuthUser = {
70
+ id: user_db.id as string,
71
+ name: (user_db.name as string | null) || null,
72
+ email_address: user_db.email_address as string,
73
+ is_active: user_db.is_active === true,
74
+ profile_picture_url:
75
+ (user_db.profile_picture_url as string | null) || null,
76
+ };
77
+
78
+ // Fetch user roles
79
+ const user_roles = await user_roles_service.findBy({ user_id });
80
+ const role_ids: number[] = [];
81
+ if (Array.isArray(user_roles)) {
82
+ for (const ur of user_roles) {
83
+ const role_id = ur.role_id as number | undefined;
84
+ if (role_id !== undefined) {
85
+ role_ids.push(role_id);
86
+ }
87
+ }
88
+ }
89
+
90
+ // Fetch role permissions
91
+ const permissions_set = new Set<string>();
92
+ if (role_ids.length > 0) {
93
+ const role_permissions = await role_permissions_service.findBy({});
94
+ if (Array.isArray(role_permissions)) {
95
+ // Filter role_permissions for user's roles
96
+ const user_role_permissions = role_permissions.filter((rp) =>
97
+ role_ids.includes(rp.role_id as number),
98
+ );
99
+
100
+ // Get permission IDs
101
+ const permission_ids = new Set<number>();
102
+ for (const rp of user_role_permissions) {
103
+ const perm_id = rp.permission_id as number | undefined;
104
+ if (perm_id !== undefined) {
105
+ permission_ids.add(perm_id);
106
+ }
107
+ }
108
+
109
+ // Fetch permission names
110
+ if (permission_ids.size > 0) {
111
+ const permissions = await permissions_service.findBy({});
112
+ if (Array.isArray(permissions)) {
113
+ for (const perm of permissions) {
114
+ const perm_id = perm.id as number | undefined;
115
+ if (perm_id !== undefined && permission_ids.has(perm_id)) {
116
+ const perm_name = perm.permission_name as string | undefined;
117
+ if (perm_name) {
118
+ permissions_set.add(perm_name);
119
+ }
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+
127
+ const permissions = Array.from(permissions_set);
128
+
129
+ return { user, permissions, role_ids };
130
+ }
131
+
132
+ /**
133
+ * Checks if user has required permissions
134
+ * @param user_permissions - User's permissions
135
+ * @param required_permissions - Required permissions
136
+ * @returns Object with permission_ok and missing_permissions
137
+ */
138
+ function check_permissions(
139
+ user_permissions: string[],
140
+ required_permissions: string[],
141
+ ): { permission_ok: boolean; missing_permissions: string[] } {
142
+ const user_perms_set = new Set(user_permissions);
143
+ const missing = required_permissions.filter(
144
+ (perm) => !user_perms_set.has(perm),
145
+ );
146
+
147
+ return {
148
+ permission_ok: missing.length === 0,
149
+ missing_permissions: missing,
150
+ };
151
+ }
152
+
153
+ /**
154
+ * Gets user-friendly error message for missing permissions
155
+ * @param missing_permissions - Array of missing permission names
156
+ * @param config - Auth utility config
157
+ * @returns User-friendly message or undefined
158
+ */
159
+ function get_friendly_error_message(
160
+ missing_permissions: string[],
161
+ config: ReturnType<typeof get_auth_utility_config>,
162
+ ): string | undefined {
163
+ if (!config.enable_friendly_error_messages) {
164
+ return undefined;
165
+ }
166
+
167
+ // Try to get messages for each missing permission
168
+ const messages: string[] = [];
169
+ for (const perm of missing_permissions) {
170
+ const message = config.permission_error_messages.get(perm);
171
+ if (message) {
172
+ messages.push(message);
173
+ }
174
+ }
175
+
176
+ if (messages.length > 0) {
177
+ return messages.join(". ");
178
+ }
179
+
180
+ // Default message if no specific mapping
181
+ return "You don't have the required permissions to perform this action. Please contact your administrator.";
182
+ }
183
+
184
+ // section: main_function
185
+
186
+ /**
187
+ * Main hazo_get_auth function for server-side use in API routes
188
+ * Returns user details, permissions, and checks required permissions
189
+ * @param request - NextRequest object
190
+ * @param options - Optional parameters for permission checking
191
+ * @returns HazoAuthResult with user data and permissions
192
+ * @throws PermissionError if strict mode and permissions are missing
193
+ */
194
+ export async function hazo_get_auth(
195
+ request: NextRequest,
196
+ options?: HazoAuthOptions,
197
+ ): Promise<HazoAuthResult> {
198
+ const logger = create_app_logger();
199
+ const config = get_auth_utility_config();
200
+ const cache = get_auth_cache(
201
+ config.cache_max_users,
202
+ config.cache_ttl_minutes,
203
+ config.cache_max_age_minutes,
204
+ );
205
+ const rate_limiter = get_rate_limiter();
206
+
207
+ // Fast path: Check for authentication cookies
208
+ const user_id = request.cookies.get("hazo_auth_user_id")?.value;
209
+ const user_email = request.cookies.get("hazo_auth_user_email")?.value;
210
+
211
+ if (!user_id || !user_email) {
212
+ // Unauthenticated - check rate limit by IP
213
+ const client_ip = get_client_ip(request);
214
+ const ip_key = `ip:${client_ip}`;
215
+ if (!rate_limiter.check(ip_key, config.rate_limit_per_ip)) {
216
+ logger.warn("auth_utility_rate_limit_exceeded_ip", {
217
+ filename: get_filename(),
218
+ line_number: get_line_number(),
219
+ ip: client_ip,
220
+ });
221
+ throw new Error("Rate limit exceeded. Please try again later.");
222
+ }
223
+
224
+ return {
225
+ authenticated: false,
226
+ user: null,
227
+ permissions: [],
228
+ permission_ok: false,
229
+ };
230
+ }
231
+
232
+ // Authenticated - check rate limit by user
233
+ const user_key = `user:${user_id}`;
234
+ if (!rate_limiter.check(user_key, config.rate_limit_per_user)) {
235
+ logger.warn("auth_utility_rate_limit_exceeded_user", {
236
+ filename: get_filename(),
237
+ line_number: get_line_number(),
238
+ user_id,
239
+ });
240
+ throw new Error("Rate limit exceeded. Please try again later.");
241
+ }
242
+
243
+ // Check cache
244
+ let cached_entry = cache.get(user_id);
245
+ let user: HazoAuthUser;
246
+ let permissions: string[];
247
+ let role_ids: number[];
248
+
249
+ if (cached_entry) {
250
+ // Cache hit
251
+ user = cached_entry.user;
252
+ permissions = cached_entry.permissions;
253
+ role_ids = cached_entry.role_ids;
254
+ } else {
255
+ // Cache miss - fetch from database
256
+ try {
257
+ const user_data = await fetch_user_data_from_db(user_id);
258
+ user = user_data.user;
259
+ permissions = user_data.permissions;
260
+ role_ids = user_data.role_ids;
261
+
262
+ // Update cache
263
+ cache.set(user_id, user, permissions, role_ids);
264
+ } catch (error) {
265
+ const error_message =
266
+ error instanceof Error ? error.message : "Unknown error";
267
+ logger.error("auth_utility_fetch_user_failed", {
268
+ filename: get_filename(),
269
+ line_number: get_line_number(),
270
+ user_id,
271
+ error: error_message,
272
+ });
273
+
274
+ return {
275
+ authenticated: false,
276
+ user: null,
277
+ permissions: [],
278
+ permission_ok: false,
279
+ };
280
+ }
281
+ }
282
+
283
+ // Check permissions if required
284
+ let permission_ok = true;
285
+ let missing_permissions: string[] | undefined;
286
+
287
+ if (options?.required_permissions && options.required_permissions.length > 0) {
288
+ const check_result = check_permissions(
289
+ permissions,
290
+ options.required_permissions,
291
+ );
292
+ permission_ok = check_result.permission_ok;
293
+ missing_permissions = check_result.missing_permissions;
294
+
295
+ // Log permission denial if enabled
296
+ if (!permission_ok && config.log_permission_denials) {
297
+ const client_ip = get_client_ip(request);
298
+ logger.warn("auth_utility_permission_denied", {
299
+ filename: get_filename(),
300
+ line_number: get_line_number(),
301
+ user_id,
302
+ requested_permissions: options.required_permissions,
303
+ missing_permissions,
304
+ user_permissions: permissions,
305
+ ip: client_ip,
306
+ });
307
+ }
308
+
309
+ // Throw error if strict mode
310
+ if (!permission_ok && options.strict) {
311
+ const friendly_message = get_friendly_error_message(
312
+ missing_permissions,
313
+ config,
314
+ );
315
+
316
+ throw new PermissionError(
317
+ missing_permissions,
318
+ permissions,
319
+ options.required_permissions,
320
+ friendly_message,
321
+ );
322
+ }
323
+ }
324
+
325
+ return {
326
+ authenticated: true,
327
+ user,
328
+ permissions,
329
+ permission_ok,
330
+ missing_permissions,
331
+ };
332
+ }
333
+
@@ -0,0 +1,136 @@
1
+ // file_description: server-only helper to read auth utility configuration from hazo_auth_config.ini
2
+ // section: imports
3
+ import {
4
+ get_config_value,
5
+ get_config_number,
6
+ get_config_boolean,
7
+ get_config_array,
8
+ } from "./config/config_loader.server";
9
+
10
+ // section: types
11
+
12
+ /**
13
+ * Auth utility configuration options
14
+ */
15
+ export type AuthUtilityConfig = {
16
+ cache_max_users: number;
17
+ cache_ttl_minutes: number;
18
+ cache_max_age_minutes: number;
19
+ rate_limit_per_user: number;
20
+ rate_limit_per_ip: number;
21
+ log_permission_denials: boolean;
22
+ enable_friendly_error_messages: boolean;
23
+ permission_error_messages: Map<string, string>; // permission -> user-friendly message
24
+ };
25
+
26
+ // section: helpers
27
+
28
+ /**
29
+ * Parses permission error messages from config string
30
+ * Format: "permission1:message1,permission2:message2"
31
+ * @param config_value - Config string value
32
+ * @returns Map of permission to user-friendly message
33
+ */
34
+ function parse_permission_messages(
35
+ config_value: string,
36
+ ): Map<string, string> {
37
+ const messages = new Map<string, string>();
38
+
39
+ if (!config_value || config_value.trim().length === 0) {
40
+ return messages;
41
+ }
42
+
43
+ const pairs = config_value.split(",");
44
+ for (const pair of pairs) {
45
+ const trimmed = pair.trim();
46
+ if (trimmed.length === 0) {
47
+ continue;
48
+ }
49
+
50
+ const colon_index = trimmed.indexOf(":");
51
+ if (colon_index === -1) {
52
+ continue; // Skip invalid format
53
+ }
54
+
55
+ const permission = trimmed.substring(0, colon_index).trim();
56
+ const message = trimmed.substring(colon_index + 1).trim();
57
+
58
+ if (permission.length > 0 && message.length > 0) {
59
+ messages.set(permission, message);
60
+ }
61
+ }
62
+
63
+ return messages;
64
+ }
65
+
66
+ /**
67
+ * Reads auth utility configuration from hazo_auth_config.ini file
68
+ * Falls back to defaults if hazo_auth_config.ini is not found or section is missing
69
+ * @returns Auth utility configuration options
70
+ */
71
+ export function get_auth_utility_config(): AuthUtilityConfig {
72
+ const section_name = "hazo_auth__auth_utility";
73
+
74
+ // Cache settings
75
+ const cache_max_users = get_config_number(
76
+ section_name,
77
+ "cache_max_users",
78
+ 10000,
79
+ );
80
+ const cache_ttl_minutes = get_config_number(
81
+ section_name,
82
+ "cache_ttl_minutes",
83
+ 15,
84
+ );
85
+ const cache_max_age_minutes = get_config_number(
86
+ section_name,
87
+ "cache_max_age_minutes",
88
+ 30,
89
+ );
90
+
91
+ // Rate limiting
92
+ const rate_limit_per_user = get_config_number(
93
+ section_name,
94
+ "rate_limit_per_user",
95
+ 100,
96
+ );
97
+ const rate_limit_per_ip = get_config_number(
98
+ section_name,
99
+ "rate_limit_per_ip",
100
+ 200,
101
+ );
102
+
103
+ // Permission check behavior
104
+ const log_permission_denials = get_config_boolean(
105
+ section_name,
106
+ "log_permission_denials",
107
+ true,
108
+ );
109
+ const enable_friendly_error_messages = get_config_boolean(
110
+ section_name,
111
+ "enable_friendly_error_messages",
112
+ true,
113
+ );
114
+
115
+ // Permission message mappings
116
+ const permission_messages_str = get_config_value(
117
+ section_name,
118
+ "permission_error_messages",
119
+ "",
120
+ );
121
+ const permission_error_messages = parse_permission_messages(
122
+ permission_messages_str,
123
+ );
124
+
125
+ return {
126
+ cache_max_users,
127
+ cache_ttl_minutes,
128
+ cache_max_age_minutes,
129
+ rate_limit_per_user,
130
+ rate_limit_per_ip,
131
+ log_permission_denials,
132
+ enable_friendly_error_messages,
133
+ permission_error_messages,
134
+ };
135
+ }
136
+
@@ -68,14 +68,13 @@ function get_hazo_connect_config(): {
68
68
  sqlite_path = path.normalize(sqlite_path);
69
69
  } else {
70
70
  // Fallback to test fixture database
71
- const ui_component_path = path.resolve(
71
+ const fallback_sqlite_path = path.resolve(
72
72
  process.cwd(),
73
- "ui_component",
74
73
  "__tests__",
75
74
  "fixtures",
76
75
  "hazo_auth.sqlite"
77
76
  );
78
- sqlite_path = path.normalize(ui_component_path);
77
+ sqlite_path = path.normalize(fallback_sqlite_path);
79
78
  }
80
79
 
81
80
  // Log the resolved path for debugging
@@ -106,7 +106,7 @@ export function get_my_settings_config(): MySettingsConfig {
106
106
  const savePasswordButtonLabel = get_config_value(section, "save_password_button_label", "Save Password");
107
107
  const unauthorizedMessage = get_config_value(section, "unauthorized_message", "You must be logged in to access this page.");
108
108
  const loginButtonLabel = get_config_value(section, "login_button_label", "Go to login");
109
- const loginPath = get_config_value(section, "login_path", "/login");
109
+ const loginPath = get_config_value(section, "login_path", "/hazo_auth/login");
110
110
 
111
111
  return {
112
112
  userFields,
@@ -113,12 +113,12 @@ export function get_profile_pic_menu_config(): ProfilePicMenuConfig {
113
113
  const show_single_button = get_config_boolean(section, "show_single_button", false);
114
114
  const sign_up_label = get_config_value(section, "sign_up_label", "Sign Up");
115
115
  const sign_in_label = get_config_value(section, "sign_in_label", "Sign In");
116
- const register_path = get_config_value(section, "register_path", "/register");
117
- const login_path = get_config_value(section, "login_path", "/login");
116
+ const register_path = get_config_value(section, "register_path", "/hazo_auth/register");
117
+ const login_path = get_config_value(section, "login_path", "/hazo_auth/login");
118
118
 
119
119
  // Read menu paths
120
- const settings_path = get_config_value(section, "settings_path", "/my_settings");
121
- const logout_path = get_config_value(section, "logout_path", "/api/auth/logout");
120
+ const settings_path = get_config_value(section, "settings_path", "/hazo_auth/my_settings");
121
+ const logout_path = get_config_value(section, "logout_path", "/api/hazo_auth/logout");
122
122
 
123
123
  // Read custom menu items
124
124
  const custom_items_string = get_config_array(section, "custom_menu_items", []);
@@ -50,11 +50,11 @@ export function get_reset_password_config(): ResetPasswordConfig {
50
50
  "Password reset successfully. Redirecting to login..."
51
51
  );
52
52
 
53
- // Read login path (defaults to "/login")
54
- const loginPath = get_config_value(section, "login_path", "/login");
55
-
56
- // Read forgot password path (defaults to "/forgot_password")
57
- const forgotPasswordPath = get_config_value(section, "forgot_password_path", "/forgot_password");
53
+ // Read login path (defaults to "/hazo_auth/login")
54
+ const loginPath = get_config_value(section, "login_path", "/hazo_auth/login");
55
+
56
+ // Read forgot password path (defaults to "/hazo_auth/forgot_password")
57
+ const forgotPasswordPath = get_config_value(section, "forgot_password_path", "/hazo_auth/forgot_password");
58
58
 
59
59
  // Get shared password requirements
60
60
  const passwordRequirements = get_password_requirements_config();
@@ -176,7 +176,7 @@ function get_base_url(): string {
176
176
  */
177
177
  function get_verification_url(token: string): string {
178
178
  const base_url = get_base_url();
179
- const path = "/verify_email";
179
+ const path = "/hazo_auth/verify_email";
180
180
  const url = base_url ? `${base_url}${path}?token=${encodeURIComponent(token)}` : `${path}?token=${encodeURIComponent(token)}`;
181
181
  return url;
182
182
  }
@@ -188,7 +188,7 @@ function get_verification_url(token: string): string {
188
188
  */
189
189
  function get_reset_password_url(token: string): string {
190
190
  const base_url = get_base_url();
191
- const path = "/reset_password";
191
+ const path = "/hazo_auth/reset_password";
192
192
  const url = base_url ? `${base_url}${path}?token=${encodeURIComponent(token)}` : `${path}?token=${encodeURIComponent(token)}`;
193
193
  return url;
194
194
  }
@@ -62,7 +62,7 @@ export async function remove_user_profile_picture(
62
62
  const config = get_profile_picture_config();
63
63
 
64
64
  if (config.upload_photo_path) {
65
- // Extract filename from URL (e.g., /api/auth/profile_picture/user_id.jpg)
65
+ // Extract filename from URL (e.g., /api/hazo_auth/profile_picture/user_id.jpg)
66
66
  const fileName = profile_picture_url.split("/").pop();
67
67
 
68
68
  if (fileName && fileName.startsWith(user_id)) {
@@ -206,9 +206,9 @@ export async function create_token(
206
206
  token_type,
207
207
  raw_token,
208
208
  test_url: token_type === "email_verification"
209
- ? `/verify_email?token=${raw_token}`
209
+ ? `/hazo_auth/verify_email?token=${raw_token}`
210
210
  : token_type === "password_reset"
211
- ? `/reset_password?token=${raw_token}`
211
+ ? `/hazo_auth/reset_password?token=${raw_token}`
212
212
  : undefined,
213
213
  });
214
214
 
@@ -0,0 +1,40 @@
1
+ // file_description: server-only helper to read user management configuration from hazo_auth_config.ini
2
+ // section: imports
3
+ import { get_config_value, get_config_array } from "./config/config_loader.server";
4
+ import { read_config_section } from "./config/config_loader.server";
5
+
6
+ // section: types
7
+ export type UserManagementConfig = {
8
+ application_permission_list_defaults: string[];
9
+ };
10
+
11
+ // section: helpers
12
+ /**
13
+ * Reads user management configuration from hazo_auth_config.ini file
14
+ * Falls back to defaults if hazo_auth_config.ini is not found or section is missing
15
+ * @returns User management configuration options
16
+ */
17
+ export function get_user_management_config(): UserManagementConfig {
18
+ // Try to read from hazo_auth__user_management section first
19
+ const user_management_section = read_config_section("hazo_auth__user_management");
20
+ const permissions_section = read_config_section("permissions");
21
+
22
+ // Try application_permission_list_defaults from user_management section
23
+ let permission_list: string[] = [];
24
+
25
+ if (user_management_section?.application_permission_list_defaults) {
26
+ permission_list = get_config_array(
27
+ "hazo_auth__user_management",
28
+ "application_permission_list_defaults",
29
+ []
30
+ );
31
+ } else if (permissions_section?.list) {
32
+ // Fallback to permissions section list key
33
+ permission_list = get_config_array("permissions", "list", []);
34
+ }
35
+
36
+ return {
37
+ application_permission_list_defaults: permission_list,
38
+ };
39
+ }
40
+
package/src/lib/utils.ts CHANGED
@@ -1,4 +1,4 @@
1
- // file_description: provide shared utility helpers for the ui_component project
1
+ // file_description: provide shared utility helpers for the hazo_auth project
2
2
  import { clsx, type ClassValue } from "clsx";
3
3
  import { twMerge } from "tailwind-merge";
4
4
 
package/src/middleware.ts CHANGED
@@ -35,18 +35,20 @@ export async function middleware(request: NextRequest) {
35
35
 
36
36
  // Public routes that don't require authentication
37
37
  const public_routes = [
38
- "/login",
39
- "/register",
40
- "/forgot_password",
41
- "/reset_password",
42
- "/verify_email",
43
- "/api/auth/login",
44
- "/api/auth/register",
45
- "/api/auth/forgot_password",
46
- "/api/auth/reset_password",
47
- "/api/auth/verify_email",
48
- "/api/auth/validate_reset_token",
49
- "/api/auth/me", // Allow /api/auth/me to be public (returns authenticated: false if not logged in)
38
+ "/hazo_auth/login",
39
+ "/hazo_auth/register",
40
+ "/hazo_auth/forgot_password",
41
+ "/hazo_auth/reset_password",
42
+ "/hazo_auth/verify_email",
43
+ "/api/hazo_auth/login",
44
+ "/api/hazo_auth/register",
45
+ "/api/hazo_auth/forgot_password",
46
+ "/api/hazo_auth/reset_password",
47
+ "/api/hazo_auth/verify_email",
48
+ "/api/hazo_auth/validate_reset_token",
49
+ "/api/hazo_auth/me", // Allow /api/hazo_auth/me to be public (returns authenticated: false if not logged in)
50
+ "/hazo_connect/api/sqlite", // SQLite Admin API routes (admin tool, should be accessible)
51
+ "/hazo_connect/sqlite_admin", // SQLite Admin UI page
50
52
  ];
51
53
 
52
54
  // Check if route is public
@@ -65,7 +67,7 @@ export async function middleware(request: NextRequest) {
65
67
 
66
68
  if (!has_cookies) {
67
69
  // Redirect to login if no cookies (not authenticated)
68
- const login_url = new URL("/login", request.url);
70
+ const login_url = new URL("/hazo_auth/login", request.url);
69
71
  login_url.searchParams.set("redirect", pathname);
70
72
  return NextResponse.redirect(login_url);
71
73
  }
@@ -13,3 +13,4 @@ export {};
13
13
 
14
14
 
15
15
 
16
+
@@ -1,4 +1,4 @@
1
- // file_description: provide a high level story describing the purpose of the ui_component workspace
1
+ // file_description: provide a high level story describing the purpose of the hazo_auth workspace
2
2
  import type { Meta, StoryObj } from "@storybook/nextjs";
3
3
 
4
4
  // section: story_configuration
@@ -1,4 +1,4 @@
1
- // file_description: configure tailwindcss for the ui_component project
1
+ // file_description: configure tailwindcss for the hazo_auth project
2
2
  import type { Config } from "tailwindcss";
3
3
 
4
4
  // section: tailwind_configuration