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,367 @@
1
+ // file_description: API route for permissions management operations (list, migrate from config, update, delete)
2
+ // section: imports
3
+ import { NextRequest, NextResponse } from "next/server";
4
+ import { get_hazo_connect_instance } from "@/lib/hazo_connect_instance.server";
5
+ import { createCrudService } from "hazo_connect/server";
6
+ import { create_app_logger } from "@/lib/app_logger";
7
+ import { get_filename, get_line_number } from "@/lib/utils/api_route_helpers";
8
+ import { get_user_management_config } from "@/lib/user_management_config.server";
9
+
10
+ // section: route_config
11
+ export const dynamic = 'force-dynamic';
12
+
13
+ // section: api_handler
14
+ /**
15
+ * GET - Fetch all permissions from database and config
16
+ */
17
+ export async function GET(request: NextRequest) {
18
+ const logger = create_app_logger();
19
+
20
+ try {
21
+ const hazoConnect = get_hazo_connect_instance();
22
+ const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
23
+
24
+ // Fetch all permissions from database (empty object means no filter - get all records)
25
+ const db_permissions = await permissions_service.findBy({});
26
+
27
+ if (!Array.isArray(db_permissions)) {
28
+ return NextResponse.json(
29
+ { error: "Failed to fetch permissions" },
30
+ { status: 500 }
31
+ );
32
+ }
33
+
34
+ // Get config permissions
35
+ const config = get_user_management_config();
36
+ const config_permission_names = config.application_permission_list_defaults || [];
37
+
38
+ // Get DB permission names
39
+ const db_permission_names = db_permissions.map((p) => p.permission_name as string);
40
+
41
+ // Find config permissions not in DB
42
+ const config_only_permissions = config_permission_names.filter(
43
+ (name) => !db_permission_names.includes(name)
44
+ );
45
+
46
+ logger.info("user_management_permissions_fetched", {
47
+ filename: get_filename(),
48
+ line_number: get_line_number(),
49
+ db_count: db_permissions.length,
50
+ config_count: config_permission_names.length,
51
+ });
52
+
53
+ return NextResponse.json(
54
+ {
55
+ success: true,
56
+ db_permissions: db_permissions.map((p) => ({
57
+ id: p.id,
58
+ permission_name: p.permission_name,
59
+ description: p.description || "",
60
+ })),
61
+ config_permissions: config_only_permissions,
62
+ },
63
+ { status: 200 }
64
+ );
65
+ } catch (error) {
66
+ const error_message = error instanceof Error ? error.message : "Unknown error";
67
+ const error_stack = error instanceof Error ? error.stack : undefined;
68
+
69
+ logger.error("user_management_permissions_fetch_error", {
70
+ filename: get_filename(),
71
+ line_number: get_line_number(),
72
+ error_message,
73
+ error_stack,
74
+ });
75
+
76
+ return NextResponse.json(
77
+ { error: "Failed to fetch permissions" },
78
+ { status: 500 }
79
+ );
80
+ }
81
+ }
82
+
83
+ /**
84
+ * POST - Create new permission or migrate config permissions to database
85
+ */
86
+ export async function POST(request: NextRequest) {
87
+ const logger = create_app_logger();
88
+
89
+ try {
90
+ const { searchParams } = new URL(request.url);
91
+ const action = searchParams.get("action");
92
+
93
+ // Handle migrate action
94
+ if (action === "migrate") {
95
+ const hazoConnect = get_hazo_connect_instance();
96
+ const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
97
+
98
+ // Get config permissions
99
+ const config = get_user_management_config();
100
+ const config_permission_names = config.application_permission_list_defaults || [];
101
+
102
+ if (config_permission_names.length === 0) {
103
+ return NextResponse.json(
104
+ {
105
+ success: true,
106
+ message: "No permissions to migrate",
107
+ created: [],
108
+ skipped: [],
109
+ },
110
+ { status: 200 }
111
+ );
112
+ }
113
+
114
+ // Get existing permissions from DB (empty object means no filter - get all records)
115
+ const db_permissions = await permissions_service.findBy({});
116
+ const db_permission_names = Array.isArray(db_permissions)
117
+ ? db_permissions.map((p) => p.permission_name as string)
118
+ : [];
119
+
120
+ const now = new Date().toISOString();
121
+ const created: string[] = [];
122
+ const skipped: string[] = [];
123
+
124
+ // Migrate each config permission
125
+ for (const permission_name of config_permission_names) {
126
+ if (db_permission_names.includes(permission_name)) {
127
+ // Skip if already exists
128
+ skipped.push(permission_name);
129
+ continue;
130
+ }
131
+
132
+ // Create new permission
133
+ await permissions_service.insert({
134
+ permission_name: permission_name.trim(),
135
+ description: "",
136
+ created_at: now,
137
+ changed_at: now,
138
+ });
139
+
140
+ created.push(permission_name);
141
+ }
142
+
143
+ logger.info("user_management_permissions_migrated", {
144
+ filename: get_filename(),
145
+ line_number: get_line_number(),
146
+ created_count: created.length,
147
+ skipped_count: skipped.length,
148
+ });
149
+
150
+ return NextResponse.json(
151
+ {
152
+ success: true,
153
+ created,
154
+ skipped,
155
+ },
156
+ { status: 200 }
157
+ );
158
+ }
159
+
160
+ // Handle create new permission
161
+ const body = await request.json();
162
+ const { permission_name, description } = body;
163
+
164
+ if (!permission_name || typeof permission_name !== "string" || permission_name.trim().length === 0) {
165
+ return NextResponse.json(
166
+ { error: "permission_name is required and must be a non-empty string" },
167
+ { status: 400 }
168
+ );
169
+ }
170
+
171
+ const hazoConnect = get_hazo_connect_instance();
172
+ const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
173
+
174
+ // Check if permission already exists
175
+ const existing_permissions = await permissions_service.findBy({
176
+ permission_name: permission_name.trim(),
177
+ });
178
+
179
+ if (Array.isArray(existing_permissions) && existing_permissions.length > 0) {
180
+ return NextResponse.json(
181
+ { error: "Permission with this name already exists" },
182
+ { status: 409 }
183
+ );
184
+ }
185
+
186
+ // Create new permission
187
+ const now = new Date().toISOString();
188
+ const new_permission_result = await permissions_service.insert({
189
+ permission_name: permission_name.trim(),
190
+ description: (description || "").trim(),
191
+ created_at: now,
192
+ changed_at: now,
193
+ });
194
+
195
+ // insert() returns an array, get the first element
196
+ if (!Array.isArray(new_permission_result) || new_permission_result.length === 0) {
197
+ return NextResponse.json(
198
+ { error: "Failed to create permission - no record returned" },
199
+ { status: 500 }
200
+ );
201
+ }
202
+
203
+ const new_permission = new_permission_result[0] as { id: number; permission_name: string; description: string };
204
+
205
+ logger.info("user_management_permission_created", {
206
+ filename: get_filename(),
207
+ line_number: get_line_number(),
208
+ permission_id: new_permission.id,
209
+ permission_name: permission_name.trim(),
210
+ });
211
+
212
+ return NextResponse.json(
213
+ {
214
+ success: true,
215
+ permission: {
216
+ id: new_permission.id,
217
+ permission_name: permission_name.trim(),
218
+ description: (description || "").trim(),
219
+ },
220
+ },
221
+ { status: 201 }
222
+ );
223
+ } catch (error) {
224
+ const error_message = error instanceof Error ? error.message : "Unknown error";
225
+ const error_stack = error instanceof Error ? error.stack : undefined;
226
+
227
+ logger.error("user_management_permissions_post_error", {
228
+ filename: get_filename(),
229
+ line_number: get_line_number(),
230
+ error_message,
231
+ error_stack,
232
+ });
233
+
234
+ return NextResponse.json(
235
+ { error: "Failed to create permission" },
236
+ { status: 500 }
237
+ );
238
+ }
239
+ }
240
+
241
+ /**
242
+ * PUT - Update permission description
243
+ */
244
+ export async function PUT(request: NextRequest) {
245
+ const logger = create_app_logger();
246
+
247
+ try {
248
+ const body = await request.json();
249
+ const { permission_id, description } = body;
250
+
251
+ if (!permission_id || typeof description !== "string") {
252
+ return NextResponse.json(
253
+ { error: "permission_id and description are required" },
254
+ { status: 400 }
255
+ );
256
+ }
257
+
258
+ const hazoConnect = get_hazo_connect_instance();
259
+ const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
260
+
261
+ // Update permission with changed_at timestamp
262
+ const now = new Date().toISOString();
263
+ await permissions_service.updateById(permission_id, {
264
+ description: description.trim(),
265
+ changed_at: now,
266
+ });
267
+
268
+ logger.info("user_management_permission_updated", {
269
+ filename: get_filename(),
270
+ line_number: get_line_number(),
271
+ permission_id,
272
+ });
273
+
274
+ return NextResponse.json(
275
+ { success: true },
276
+ { status: 200 }
277
+ );
278
+ } catch (error) {
279
+ const error_message = error instanceof Error ? error.message : "Unknown error";
280
+ const error_stack = error instanceof Error ? error.stack : undefined;
281
+
282
+ logger.error("user_management_permission_update_error", {
283
+ filename: get_filename(),
284
+ line_number: get_line_number(),
285
+ error_message,
286
+ error_stack,
287
+ });
288
+
289
+ return NextResponse.json(
290
+ { error: "Failed to update permission" },
291
+ { status: 500 }
292
+ );
293
+ }
294
+ }
295
+
296
+ /**
297
+ * DELETE - Delete permission from database
298
+ */
299
+ export async function DELETE(request: NextRequest) {
300
+ const logger = create_app_logger();
301
+
302
+ try {
303
+ const { searchParams } = new URL(request.url);
304
+ const permission_id = searchParams.get("permission_id");
305
+
306
+ if (!permission_id) {
307
+ return NextResponse.json(
308
+ { error: "permission_id is required" },
309
+ { status: 400 }
310
+ );
311
+ }
312
+
313
+ const permission_id_num = parseInt(permission_id, 10);
314
+ if (isNaN(permission_id_num)) {
315
+ return NextResponse.json(
316
+ { error: "permission_id must be a number" },
317
+ { status: 400 }
318
+ );
319
+ }
320
+
321
+ const hazoConnect = get_hazo_connect_instance();
322
+ const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
323
+ const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
324
+
325
+ // Check if permission is used in any role
326
+ const role_permissions = await role_permissions_service.findBy({
327
+ permission_id: permission_id_num,
328
+ });
329
+
330
+ if (Array.isArray(role_permissions) && role_permissions.length > 0) {
331
+ return NextResponse.json(
332
+ { error: "Cannot delete permission that is assigned to roles" },
333
+ { status: 409 }
334
+ );
335
+ }
336
+
337
+ // Delete permission
338
+ await permissions_service.deleteById(permission_id_num);
339
+
340
+ logger.info("user_management_permission_deleted", {
341
+ filename: get_filename(),
342
+ line_number: get_line_number(),
343
+ permission_id: permission_id_num,
344
+ });
345
+
346
+ return NextResponse.json(
347
+ { success: true },
348
+ { status: 200 }
349
+ );
350
+ } catch (error) {
351
+ const error_message = error instanceof Error ? error.message : "Unknown error";
352
+ const error_stack = error instanceof Error ? error.stack : undefined;
353
+
354
+ logger.error("user_management_permission_delete_error", {
355
+ filename: get_filename(),
356
+ line_number: get_line_number(),
357
+ error_message,
358
+ error_stack,
359
+ });
360
+
361
+ return NextResponse.json(
362
+ { error: "Failed to delete permission" },
363
+ { status: 500 }
364
+ );
365
+ }
366
+ }
367
+