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.
- package/hazo_auth_config.example.ini +39 -0
- package/instrumentation.ts +1 -1
- package/next.config.mjs +1 -1
- package/package.json +3 -1
- package/src/app/api/{auth → hazo_auth/auth}/upload_profile_picture/route.ts +2 -2
- package/src/app/api/{auth → hazo_auth}/change_password/route.ts +23 -0
- package/src/app/api/hazo_auth/get_auth/route.ts +89 -0
- package/src/app/api/hazo_auth/invalidate_cache/route.ts +139 -0
- package/src/app/api/{auth → hazo_auth}/logout/route.ts +27 -0
- package/src/app/api/hazo_auth/upload_profile_picture/route.ts +268 -0
- package/src/app/api/hazo_auth/user_management/permissions/route.ts +367 -0
- package/src/app/api/hazo_auth/user_management/roles/route.ts +442 -0
- package/src/app/api/hazo_auth/user_management/users/roles/route.ts +367 -0
- package/src/app/api/hazo_auth/user_management/users/route.ts +239 -0
- package/src/app/api/{auth → hazo_auth}/validate_reset_token/route.ts +3 -0
- package/src/app/api/{auth → hazo_auth}/verify_email/route.ts +3 -0
- package/src/app/globals.css +1 -1
- package/src/app/hazo_auth/user_management/page.tsx +14 -0
- package/src/app/hazo_auth/user_management/user_management_page_client.tsx +16 -0
- package/src/app/hazo_connect/api/sqlite/data/route.ts +7 -1
- package/src/app/hazo_connect/api/sqlite/schema/route.ts +14 -4
- package/src/app/hazo_connect/api/sqlite/tables/route.ts +14 -4
- package/src/app/hazo_connect/sqlite_admin/sqlite-admin-client.tsx +40 -3
- package/src/app/layout.tsx +1 -1
- package/src/app/page.tsx +4 -4
- package/src/components/layouts/email_verification/hooks/use_email_verification.ts +4 -4
- package/src/components/layouts/email_verification/index.tsx +1 -1
- package/src/components/layouts/forgot_password/hooks/use_forgot_password_form.ts +1 -1
- package/src/components/layouts/login/hooks/use_login_form.ts +2 -2
- package/src/components/layouts/my_settings/components/profile_picture_dialog.tsx +1 -1
- package/src/components/layouts/my_settings/components/profile_picture_library_tab.tsx +2 -2
- package/src/components/layouts/my_settings/hooks/use_my_settings.ts +5 -5
- package/src/components/layouts/my_settings/index.tsx +1 -1
- package/src/components/layouts/register/hooks/use_register_form.ts +1 -1
- package/src/components/layouts/reset_password/hooks/use_reset_password_form.ts +3 -3
- package/src/components/layouts/reset_password/index.tsx +2 -2
- package/src/components/layouts/shared/components/logout_button.tsx +1 -1
- package/src/components/layouts/shared/components/profile_pic_menu.tsx +4 -4
- package/src/components/layouts/shared/components/sidebar_layout_wrapper.tsx +19 -7
- package/src/components/layouts/shared/components/unauthorized_guard.tsx +1 -1
- package/src/components/layouts/shared/hooks/use_auth_status.ts +1 -1
- package/src/components/layouts/shared/hooks/use_hazo_auth.ts +158 -0
- package/src/components/layouts/user_management/components/roles_matrix.tsx +607 -0
- package/src/components/layouts/user_management/index.tsx +1295 -0
- package/src/components/ui/alert-dialog.tsx +141 -0
- package/src/components/ui/checkbox.tsx +30 -0
- package/src/components/ui/table.tsx +120 -0
- package/src/lib/auth/auth_cache.ts +220 -0
- package/src/lib/auth/auth_rate_limiter.ts +121 -0
- package/src/lib/auth/auth_types.ts +65 -0
- package/src/lib/auth/hazo_get_auth.server.ts +333 -0
- package/src/lib/auth_utility_config.server.ts +136 -0
- package/src/lib/hazo_connect_setup.server.ts +2 -3
- package/src/lib/my_settings_config.server.ts +1 -1
- package/src/lib/profile_pic_menu_config.server.ts +4 -4
- package/src/lib/reset_password_config.server.ts +5 -5
- package/src/lib/services/email_service.ts +2 -2
- package/src/lib/services/profile_picture_remove_service.ts +1 -1
- package/src/lib/services/token_service.ts +2 -2
- package/src/lib/user_management_config.server.ts +40 -0
- package/src/lib/utils.ts +1 -1
- package/src/middleware.ts +15 -13
- package/src/server/types/express.d.ts +1 -0
- package/src/stories/project_overview.stories.tsx +1 -1
- package/tailwind.config.ts +1 -1
- /package/src/app/api/{auth → hazo_auth}/forgot_password/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/library_photos/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/login/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/me/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/profile_picture/[filename]/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/register/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/remove_profile_picture/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/resend_verification/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/reset_password/route.ts +0 -0
- /package/src/app/api/{auth → hazo_auth}/update_user/route.ts +0 -0
- /package/src/app/{forgot_password → hazo_auth/forgot_password}/forgot_password_page_client.tsx +0 -0
- /package/src/app/{forgot_password → hazo_auth/forgot_password}/page.tsx +0 -0
- /package/src/app/{login → hazo_auth/login}/login_page_client.tsx +0 -0
- /package/src/app/{login → hazo_auth/login}/page.tsx +0 -0
- /package/src/app/{my_settings → hazo_auth/my_settings}/my_settings_page_client.tsx +0 -0
- /package/src/app/{my_settings → hazo_auth/my_settings}/page.tsx +0 -0
- /package/src/app/{register → hazo_auth/register}/page.tsx +0 -0
- /package/src/app/{register → hazo_auth/register}/register_page_client.tsx +0 -0
- /package/src/app/{reset_password → hazo_auth/reset_password}/page.tsx +0 -0
- /package/src/app/{reset_password → hazo_auth/reset_password}/reset_password_page_client.tsx +0 -0
- /package/src/app/{verify_email → hazo_auth/verify_email}/page.tsx +0 -0
- /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
|
|
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(
|
|
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/
|
|
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/
|
|
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
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/
|
|
44
|
-
"/api/
|
|
45
|
-
"/api/
|
|
46
|
-
"/api/
|
|
47
|
-
"/api/
|
|
48
|
-
"/api/
|
|
49
|
-
"/api/
|
|
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
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// file_description: provide a high level story describing the purpose of the
|
|
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
|
package/tailwind.config.ts
CHANGED
|
File without changes
|