hazo_auth 1.6.2 → 1.6.5

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 (49) hide show
  1. package/README.md +179 -2
  2. package/SETUP_CHECKLIST.md +104 -6
  3. package/dist/app/api/hazo_auth/me/route.d.ts +30 -1
  4. package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
  5. package/dist/app/api/hazo_auth/me/route.js +76 -16
  6. package/dist/app/api/hazo_auth/user_management/permissions/route.d.ts +50 -0
  7. package/dist/app/api/hazo_auth/user_management/permissions/route.d.ts.map +1 -0
  8. package/dist/app/api/hazo_auth/user_management/permissions/route.js +257 -0
  9. package/dist/app/api/hazo_auth/user_management/roles/route.d.ts +40 -0
  10. package/dist/app/api/hazo_auth/user_management/roles/route.d.ts.map +1 -0
  11. package/dist/app/api/hazo_auth/user_management/roles/route.js +352 -0
  12. package/dist/app/api/hazo_auth/user_management/users/roles/route.d.ts +37 -0
  13. package/dist/app/api/hazo_auth/user_management/users/roles/route.d.ts.map +1 -0
  14. package/dist/app/api/hazo_auth/user_management/users/roles/route.js +276 -0
  15. package/dist/app/api/hazo_auth/user_management/users/route.d.ts +39 -0
  16. package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -0
  17. package/dist/app/api/hazo_auth/user_management/users/route.js +170 -0
  18. package/dist/cli/generate.d.ts.map +1 -1
  19. package/dist/cli/generate.js +38 -5
  20. package/dist/cli/validate.d.ts.map +1 -1
  21. package/dist/cli/validate.js +14 -0
  22. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts +6 -2
  23. package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
  24. package/dist/components/layouts/shared/components/profile_pic_menu.js +39 -4
  25. package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.d.ts +4 -2
  26. package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.d.ts.map +1 -1
  27. package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.js +3 -3
  28. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
  29. package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
  30. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +3 -0
  31. package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
  32. package/dist/components/layouts/shared/hooks/use_auth_status.js +4 -0
  33. package/dist/server/routes/index.d.ts +4 -0
  34. package/dist/server/routes/index.d.ts.map +1 -1
  35. package/dist/server/routes/index.js +5 -0
  36. package/dist/server/routes/user_management_permissions.d.ts +2 -0
  37. package/dist/server/routes/user_management_permissions.d.ts.map +1 -0
  38. package/dist/server/routes/user_management_permissions.js +2 -0
  39. package/dist/server/routes/user_management_roles.d.ts +2 -0
  40. package/dist/server/routes/user_management_roles.d.ts.map +1 -0
  41. package/dist/server/routes/user_management_roles.js +2 -0
  42. package/dist/server/routes/user_management_users.d.ts +2 -0
  43. package/dist/server/routes/user_management_users.d.ts.map +1 -0
  44. package/dist/server/routes/user_management_users.js +2 -0
  45. package/dist/server/routes/user_management_users_roles.d.ts +2 -0
  46. package/dist/server/routes/user_management_users_roles.d.ts.map +1 -0
  47. package/dist/server/routes/user_management_users_roles.js +2 -0
  48. package/hazo_auth_config.example.ini +1 -1
  49. package/package.json +1 -1
@@ -0,0 +1,37 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare const dynamic = "force-dynamic";
3
+ /**
4
+ * GET - Get roles assigned to a user
5
+ * Query params: user_id (string)
6
+ */
7
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
8
+ error: string;
9
+ }> | NextResponse<{
10
+ success: boolean;
11
+ role_ids: number[];
12
+ }>>;
13
+ /**
14
+ * POST - Assign a role to a user
15
+ * Body: { user_id: string, role_id: number }
16
+ */
17
+ export declare function POST(request: NextRequest): Promise<NextResponse<{
18
+ error: string;
19
+ }> | NextResponse<{
20
+ success: boolean;
21
+ assignment: {
22
+ user_id: string;
23
+ role_id: number;
24
+ };
25
+ }>>;
26
+ /**
27
+ * PUT - Update user roles (bulk assignment/removal)
28
+ * Body: { user_id: string, role_ids: number[] }
29
+ */
30
+ export declare function PUT(request: NextRequest): Promise<NextResponse<{
31
+ error: string;
32
+ }> | NextResponse<{
33
+ success: boolean;
34
+ added: number;
35
+ removed: number;
36
+ }>>;
37
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../../src/app/api/hazo_auth/user_management/users/roles/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;IAoD7C;AAED;;;GAGG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAiG9C;AAED;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;IA2L7C"}
@@ -0,0 +1,276 @@
1
+ // file_description: API route for managing user roles (assigning roles to users)
2
+ // section: imports
3
+ import { NextResponse } from "next/server";
4
+ import { get_hazo_connect_instance } from "../../../../../../lib/hazo_connect_instance.server";
5
+ import { createCrudService, getSqliteAdminService } 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_auth_cache } from "../../../../../../lib/auth/auth_cache";
9
+ import { get_auth_utility_config } from "../../../../../../lib/auth_utility_config.server";
10
+ // section: route_config
11
+ export const dynamic = 'force-dynamic';
12
+ // section: api_handler
13
+ /**
14
+ * GET - Get roles assigned to a user
15
+ * Query params: user_id (string)
16
+ */
17
+ export async function GET(request) {
18
+ const logger = create_app_logger();
19
+ try {
20
+ const { searchParams } = new URL(request.url);
21
+ const user_id = searchParams.get("user_id");
22
+ if (!user_id || typeof user_id !== "string") {
23
+ return NextResponse.json({ error: "user_id is required as a query parameter" }, { status: 400 });
24
+ }
25
+ const hazoConnect = get_hazo_connect_instance();
26
+ const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
27
+ // Get all roles assigned to this user
28
+ const user_roles = await user_roles_service.findBy({
29
+ user_id,
30
+ });
31
+ if (!Array.isArray(user_roles)) {
32
+ return NextResponse.json({ error: "Failed to fetch user roles" }, { status: 500 });
33
+ }
34
+ // Extract role IDs
35
+ const role_ids = user_roles.map((ur) => ur.role_id).filter((id) => id !== undefined);
36
+ return NextResponse.json({
37
+ success: true,
38
+ role_ids,
39
+ }, { status: 200 });
40
+ }
41
+ catch (error) {
42
+ const error_message = error instanceof Error ? error.message : "Unknown error";
43
+ logger.error("user_management_user_roles_fetch_failed", {
44
+ filename: get_filename(),
45
+ line_number: get_line_number(),
46
+ error: error_message,
47
+ });
48
+ return NextResponse.json({ error: "Failed to fetch user roles" }, { status: 500 });
49
+ }
50
+ }
51
+ /**
52
+ * POST - Assign a role to a user
53
+ * Body: { user_id: string, role_id: number }
54
+ */
55
+ export async function POST(request) {
56
+ const logger = create_app_logger();
57
+ try {
58
+ const body = await request.json();
59
+ const { user_id, role_id } = body;
60
+ if (!user_id || typeof user_id !== "string") {
61
+ return NextResponse.json({ error: "user_id is required and must be a string" }, { status: 400 });
62
+ }
63
+ if (!role_id || typeof role_id !== "number") {
64
+ return NextResponse.json({ error: "role_id is required and must be a number" }, { status: 400 });
65
+ }
66
+ const hazoConnect = get_hazo_connect_instance();
67
+ const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
68
+ // Check if user exists
69
+ const users_service = createCrudService(hazoConnect, "hazo_users");
70
+ const users = await users_service.findBy({ id: user_id });
71
+ if (!Array.isArray(users) || users.length === 0) {
72
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
73
+ }
74
+ // Check if role exists
75
+ const roles_service = createCrudService(hazoConnect, "hazo_roles");
76
+ const roles = await roles_service.findBy({ id: role_id });
77
+ if (!Array.isArray(roles) || roles.length === 0) {
78
+ return NextResponse.json({ error: "Role not found" }, { status: 404 });
79
+ }
80
+ // Check if role is already assigned to user
81
+ const existing_assignments = await user_roles_service.findBy({
82
+ user_id,
83
+ role_id,
84
+ });
85
+ if (Array.isArray(existing_assignments) && existing_assignments.length > 0) {
86
+ return NextResponse.json({ error: "Role is already assigned to this user" }, { status: 409 });
87
+ }
88
+ // Assign role to user
89
+ const now = new Date().toISOString();
90
+ const new_assignment = await user_roles_service.insert({
91
+ user_id,
92
+ role_id,
93
+ created_at: now,
94
+ changed_at: now,
95
+ });
96
+ logger.info("user_management_user_role_assigned", {
97
+ filename: get_filename(),
98
+ line_number: get_line_number(),
99
+ user_id,
100
+ role_id,
101
+ assignment_id: new_assignment.user_id,
102
+ });
103
+ return NextResponse.json({
104
+ success: true,
105
+ assignment: {
106
+ user_id,
107
+ role_id,
108
+ },
109
+ }, { status: 201 });
110
+ }
111
+ catch (error) {
112
+ const error_message = error instanceof Error ? error.message : "Unknown error";
113
+ logger.error("user_management_user_role_assign_failed", {
114
+ filename: get_filename(),
115
+ line_number: get_line_number(),
116
+ error: error_message,
117
+ });
118
+ return NextResponse.json({ error: "Failed to assign role to user" }, { status: 500 });
119
+ }
120
+ }
121
+ /**
122
+ * PUT - Update user roles (bulk assignment/removal)
123
+ * Body: { user_id: string, role_ids: number[] }
124
+ */
125
+ export async function PUT(request) {
126
+ const logger = create_app_logger();
127
+ try {
128
+ const body = await request.json();
129
+ const { user_id, role_ids } = body;
130
+ if (!user_id || typeof user_id !== "string") {
131
+ return NextResponse.json({ error: "user_id is required and must be a string" }, { status: 400 });
132
+ }
133
+ if (!Array.isArray(role_ids)) {
134
+ return NextResponse.json({ error: "role_ids is required and must be an array" }, { status: 400 });
135
+ }
136
+ const hazoConnect = get_hazo_connect_instance();
137
+ const user_roles_service = createCrudService(hazoConnect, "hazo_user_roles");
138
+ // Check if user exists
139
+ const users_service = createCrudService(hazoConnect, "hazo_users");
140
+ const users = await users_service.findBy({ id: user_id });
141
+ if (!Array.isArray(users) || users.length === 0) {
142
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
143
+ }
144
+ // Get current user roles
145
+ const current_user_roles = await user_roles_service.findBy({
146
+ user_id,
147
+ });
148
+ if (!Array.isArray(current_user_roles)) {
149
+ return NextResponse.json({ error: "Failed to fetch current user roles" }, { status: 500 });
150
+ }
151
+ const current_role_ids = current_user_roles.map((ur) => ur.role_id).filter((id) => id !== undefined);
152
+ const target_role_ids = role_ids.filter((id) => typeof id === "number");
153
+ // Find roles to add and remove
154
+ const to_add = target_role_ids.filter((id) => !current_role_ids.includes(id));
155
+ const to_remove = current_role_ids.filter((id) => !target_role_ids.includes(id));
156
+ const now = new Date().toISOString();
157
+ // Add new roles
158
+ for (const role_id of to_add) {
159
+ // Check if role exists
160
+ const roles_service = createCrudService(hazoConnect, "hazo_roles");
161
+ const roles = await roles_service.findBy({ id: role_id });
162
+ if (Array.isArray(roles) && roles.length > 0) {
163
+ await user_roles_service.insert({
164
+ user_id,
165
+ role_id,
166
+ created_at: now,
167
+ changed_at: now,
168
+ });
169
+ }
170
+ }
171
+ // Remove roles
172
+ // Note: hazo_user_roles is a junction table without an id column
173
+ // We need to use SQLite admin service to delete by composite key (user_id, role_id)
174
+ if (to_remove.length > 0) {
175
+ try {
176
+ const admin_service = getSqliteAdminService();
177
+ for (const role_id of to_remove) {
178
+ // Delete using SQLite admin service with criteria (user_id and role_id)
179
+ await admin_service.deleteRows("hazo_user_roles", {
180
+ user_id,
181
+ role_id,
182
+ });
183
+ }
184
+ }
185
+ catch (adminError) {
186
+ // Fallback: try using createCrudService deleteById if rowid exists
187
+ // SQLite tables have a hidden rowid column that can be used
188
+ const error_message = adminError instanceof Error ? adminError.message : "Unknown error";
189
+ logger.warn("user_management_user_role_delete_admin_failed", {
190
+ filename: get_filename(),
191
+ line_number: get_line_number(),
192
+ error: error_message,
193
+ note: "Trying fallback method",
194
+ });
195
+ // Fallback: try to find and delete using rowid if available
196
+ for (const role_id of to_remove) {
197
+ const assignments_to_remove = await user_roles_service.findBy({
198
+ user_id,
199
+ role_id,
200
+ });
201
+ if (Array.isArray(assignments_to_remove) && assignments_to_remove.length > 0) {
202
+ for (const assignment of assignments_to_remove) {
203
+ // Try deleteById with rowid (SQLite has hidden rowid)
204
+ try {
205
+ // Check if assignment has an id field (could be rowid)
206
+ if (assignment.id !== undefined) {
207
+ await user_roles_service.deleteById(assignment.id);
208
+ }
209
+ else if (assignment.rowid !== undefined) {
210
+ await user_roles_service.deleteById(assignment.rowid);
211
+ }
212
+ else {
213
+ // Last resort: log error
214
+ logger.error("user_management_user_role_delete_no_id", {
215
+ filename: get_filename(),
216
+ line_number: get_line_number(),
217
+ user_id,
218
+ role_id,
219
+ assignment,
220
+ });
221
+ }
222
+ }
223
+ catch (deleteError) {
224
+ const delete_error_message = deleteError instanceof Error ? deleteError.message : "Unknown error";
225
+ logger.error("user_management_user_role_delete_failed", {
226
+ filename: get_filename(),
227
+ line_number: get_line_number(),
228
+ user_id,
229
+ role_id,
230
+ error: delete_error_message,
231
+ });
232
+ }
233
+ }
234
+ }
235
+ }
236
+ }
237
+ }
238
+ // Invalidate user cache after role assignment changes
239
+ try {
240
+ const config = get_auth_utility_config();
241
+ const cache = get_auth_cache(config.cache_max_users, config.cache_ttl_minutes, config.cache_max_age_minutes);
242
+ cache.invalidate_user(user_id);
243
+ }
244
+ catch (cache_error) {
245
+ // Log but don't fail role update if cache invalidation fails
246
+ const cache_error_message = cache_error instanceof Error ? cache_error.message : "Unknown error";
247
+ logger.warn("user_management_user_roles_cache_invalidation_failed", {
248
+ filename: get_filename(),
249
+ line_number: get_line_number(),
250
+ user_id,
251
+ error: cache_error_message,
252
+ });
253
+ }
254
+ logger.info("user_management_user_roles_updated", {
255
+ filename: get_filename(),
256
+ line_number: get_line_number(),
257
+ user_id,
258
+ added: to_add.length,
259
+ removed: to_remove.length,
260
+ });
261
+ return NextResponse.json({
262
+ success: true,
263
+ added: to_add.length,
264
+ removed: to_remove.length,
265
+ }, { status: 200 });
266
+ }
267
+ catch (error) {
268
+ const error_message = error instanceof Error ? error.message : "Unknown error";
269
+ logger.error("user_management_user_roles_update_failed", {
270
+ filename: get_filename(),
271
+ line_number: get_line_number(),
272
+ error: error_message,
273
+ });
274
+ return NextResponse.json({ error: "Failed to update user roles" }, { status: 500 });
275
+ }
276
+ }
@@ -0,0 +1,39 @@
1
+ import { NextRequest, NextResponse } from "next/server";
2
+ export declare const dynamic = "force-dynamic";
3
+ /**
4
+ * GET - Fetch all users with details or a specific user by id
5
+ * Query params: id (optional) - if provided, returns only that user
6
+ */
7
+ export declare function GET(request: NextRequest): Promise<NextResponse<{
8
+ error: string;
9
+ }> | NextResponse<{
10
+ success: boolean;
11
+ users: {
12
+ id: unknown;
13
+ name: {} | null;
14
+ email_address: unknown;
15
+ email_verified: {};
16
+ is_active: boolean;
17
+ last_logon: {} | null;
18
+ created_at: {} | null;
19
+ profile_picture_url: {} | null;
20
+ profile_source: {} | null;
21
+ }[];
22
+ }>>;
23
+ /**
24
+ * PATCH - Update user (deactivate: set is_active to false)
25
+ */
26
+ export declare function PATCH(request: NextRequest): Promise<NextResponse<{
27
+ error: string;
28
+ }> | NextResponse<{
29
+ success: boolean;
30
+ }>>;
31
+ /**
32
+ * POST - Send password reset email to user
33
+ */
34
+ export declare function POST(request: NextRequest): Promise<NextResponse<{
35
+ error: string;
36
+ }> | NextResponse<{
37
+ success: boolean;
38
+ }>>;
39
+ //# sourceMappingURL=route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/users/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAUxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;;GAGG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;;;IA2D7C;AAED;;GAEG;AACH,wBAAsB,KAAK,CAAC,OAAO,EAAE,WAAW;;;;IA0E/C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;IA2E9C"}
@@ -0,0 +1,170 @@
1
+ // file_description: API route for user management operations (list users, deactivate, reset password)
2
+ // section: imports
3
+ import { 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 { request_password_reset } from "../../../../../lib/services/password_reset_service";
9
+ import { get_auth_cache } from "../../../../../lib/auth/auth_cache";
10
+ import { get_auth_utility_config } from "../../../../../lib/auth_utility_config.server";
11
+ // section: route_config
12
+ export const dynamic = 'force-dynamic';
13
+ // section: api_handler
14
+ /**
15
+ * GET - Fetch all users with details or a specific user by id
16
+ * Query params: id (optional) - if provided, returns only that user
17
+ */
18
+ export async function GET(request) {
19
+ const logger = create_app_logger();
20
+ try {
21
+ const { searchParams } = new URL(request.url);
22
+ const user_id = searchParams.get("id");
23
+ const hazoConnect = get_hazo_connect_instance();
24
+ const users_service = createCrudService(hazoConnect, "hazo_users");
25
+ // Fetch users - filter by id if provided, otherwise get all
26
+ const users = await users_service.findBy(user_id ? { id: user_id } : {});
27
+ if (!Array.isArray(users)) {
28
+ return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 });
29
+ }
30
+ logger.info("user_management_users_fetched", {
31
+ filename: get_filename(),
32
+ line_number: get_line_number(),
33
+ user_count: users.length,
34
+ });
35
+ return NextResponse.json({
36
+ success: true,
37
+ users: users.map((user) => ({
38
+ id: user.id,
39
+ name: user.name || null,
40
+ email_address: user.email_address,
41
+ email_verified: user.email_verified || false,
42
+ is_active: user.is_active !== false,
43
+ last_logon: user.last_logon || null,
44
+ created_at: user.created_at || null,
45
+ profile_picture_url: user.profile_picture_url || null,
46
+ profile_source: user.profile_source || null,
47
+ })),
48
+ }, { status: 200 });
49
+ }
50
+ catch (error) {
51
+ const error_message = error instanceof Error ? error.message : "Unknown error";
52
+ const error_stack = error instanceof Error ? error.stack : undefined;
53
+ logger.error("user_management_users_fetch_error", {
54
+ filename: get_filename(),
55
+ line_number: get_line_number(),
56
+ error_message,
57
+ error_stack,
58
+ });
59
+ return NextResponse.json({ error: "Failed to fetch users" }, { status: 500 });
60
+ }
61
+ }
62
+ /**
63
+ * PATCH - Update user (deactivate: set is_active to false)
64
+ */
65
+ export async function PATCH(request) {
66
+ const logger = create_app_logger();
67
+ try {
68
+ const body = await request.json();
69
+ const { user_id, is_active } = body;
70
+ if (!user_id || typeof is_active !== "boolean") {
71
+ return NextResponse.json({ error: "user_id and is_active (boolean) are required" }, { status: 400 });
72
+ }
73
+ const hazoConnect = get_hazo_connect_instance();
74
+ const users_service = createCrudService(hazoConnect, "hazo_users");
75
+ // Update user with changed_at timestamp
76
+ const now = new Date().toISOString();
77
+ await users_service.updateById(user_id, {
78
+ is_active,
79
+ changed_at: now,
80
+ });
81
+ // Invalidate user cache after deactivation
82
+ if (is_active === false) {
83
+ try {
84
+ const config = get_auth_utility_config();
85
+ const cache = get_auth_cache(config.cache_max_users, config.cache_ttl_minutes, config.cache_max_age_minutes);
86
+ cache.invalidate_user(user_id);
87
+ }
88
+ catch (cache_error) {
89
+ // Log but don't fail user update if cache invalidation fails
90
+ const cache_error_message = cache_error instanceof Error ? cache_error.message : "Unknown error";
91
+ logger.warn("user_management_user_cache_invalidation_failed", {
92
+ filename: get_filename(),
93
+ line_number: get_line_number(),
94
+ user_id,
95
+ error: cache_error_message,
96
+ });
97
+ }
98
+ }
99
+ logger.info("user_management_user_updated", {
100
+ filename: get_filename(),
101
+ line_number: get_line_number(),
102
+ user_id,
103
+ is_active,
104
+ });
105
+ return NextResponse.json({ success: true }, { status: 200 });
106
+ }
107
+ catch (error) {
108
+ const error_message = error instanceof Error ? error.message : "Unknown error";
109
+ const error_stack = error instanceof Error ? error.stack : undefined;
110
+ logger.error("user_management_user_update_error", {
111
+ filename: get_filename(),
112
+ line_number: get_line_number(),
113
+ error_message,
114
+ error_stack,
115
+ });
116
+ return NextResponse.json({ error: "Failed to update user" }, { status: 500 });
117
+ }
118
+ }
119
+ /**
120
+ * POST - Send password reset email to user
121
+ */
122
+ export async function POST(request) {
123
+ const logger = create_app_logger();
124
+ try {
125
+ const body = await request.json();
126
+ const { user_id } = body;
127
+ if (!user_id) {
128
+ return NextResponse.json({ error: "user_id is required" }, { status: 400 });
129
+ }
130
+ const hazoConnect = get_hazo_connect_instance();
131
+ const users_service = createCrudService(hazoConnect, "hazo_users");
132
+ // Get user by ID
133
+ const users = await users_service.findBy({ id: user_id });
134
+ if (!Array.isArray(users) || users.length === 0) {
135
+ return NextResponse.json({ error: "User not found" }, { status: 404 });
136
+ }
137
+ const user = users[0];
138
+ const email = user.email_address;
139
+ // Request password reset using existing service
140
+ const result = await request_password_reset(hazoConnect, { email });
141
+ if (!result.success) {
142
+ logger.warn("user_management_password_reset_failed", {
143
+ filename: get_filename(),
144
+ line_number: get_line_number(),
145
+ user_id,
146
+ email,
147
+ error: result.error,
148
+ });
149
+ return NextResponse.json({ error: result.error || "Failed to send password reset email" }, { status: 500 });
150
+ }
151
+ logger.info("user_management_password_reset_sent", {
152
+ filename: get_filename(),
153
+ line_number: get_line_number(),
154
+ user_id,
155
+ email,
156
+ });
157
+ return NextResponse.json({ success: true }, { status: 200 });
158
+ }
159
+ catch (error) {
160
+ const error_message = error instanceof Error ? error.message : "Unknown error";
161
+ const error_stack = error instanceof Error ? error.stack : undefined;
162
+ logger.error("user_management_password_reset_error", {
163
+ filename: get_filename(),
164
+ line_number: get_line_number(),
165
+ error_message,
166
+ error_stack,
167
+ });
168
+ return NextResponse.json({ error: "Failed to send password reset email" }, { status: 500 });
169
+ }
170
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAwJF,wBAAgB,eAAe,CAAC,OAAO,GAAE,eAAoB,GAAG,IAAI,CA8DnE"}
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/cli/generate.ts"],"names":[],"mappings":"AAqBA,MAAM,MAAM,eAAe,GAAG;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AA4LF,wBAAgB,eAAe,CAAC,OAAO,GAAE,eAAoB,GAAG,IAAI,CA8DnE"}
@@ -22,6 +22,20 @@ const ROUTES = [
22
22
  { name: "validate_reset_token", path: "api/hazo_auth/validate_reset_token", method: "GET", export_name: "validateResetTokenGET" },
23
23
  { name: "profile_picture_filename", path: "api/hazo_auth/profile_picture/[filename]", method: "GET", export_name: "profilePictureFilenameGET" },
24
24
  { name: "invalidate_cache", path: "api/hazo_auth/invalidate_cache", method: "POST", export_name: "invalidateCachePOST" },
25
+ // User management routes
26
+ { name: "user_management_users", path: "api/hazo_auth/user_management/users", method: "GET", export_name: "userManagementUsersGET" },
27
+ { name: "user_management_users_patch", path: "api/hazo_auth/user_management/users", method: "PATCH", export_name: "userManagementUsersPATCH" },
28
+ { name: "user_management_users_post", path: "api/hazo_auth/user_management/users", method: "POST", export_name: "userManagementUsersPOST" },
29
+ { name: "user_management_permissions", path: "api/hazo_auth/user_management/permissions", method: "GET", export_name: "userManagementPermissionsGET" },
30
+ { name: "user_management_permissions_post", path: "api/hazo_auth/user_management/permissions", method: "POST", export_name: "userManagementPermissionsPOST" },
31
+ { name: "user_management_permissions_put", path: "api/hazo_auth/user_management/permissions", method: "PUT", export_name: "userManagementPermissionsPUT" },
32
+ { name: "user_management_permissions_delete", path: "api/hazo_auth/user_management/permissions", method: "DELETE", export_name: "userManagementPermissionsDELETE" },
33
+ { name: "user_management_roles", path: "api/hazo_auth/user_management/roles", method: "GET", export_name: "userManagementRolesGET" },
34
+ { name: "user_management_roles_post", path: "api/hazo_auth/user_management/roles", method: "POST", export_name: "userManagementRolesPOST" },
35
+ { name: "user_management_roles_put", path: "api/hazo_auth/user_management/roles", method: "PUT", export_name: "userManagementRolesPUT" },
36
+ { name: "user_management_users_roles", path: "api/hazo_auth/user_management/users/roles", method: "GET", export_name: "userManagementUsersRolesGET" },
37
+ { name: "user_management_users_roles_post", path: "api/hazo_auth/user_management/users/roles", method: "POST", export_name: "userManagementUsersRolesPOST" },
38
+ { name: "user_management_users_roles_put", path: "api/hazo_auth/user_management/users/roles", method: "PUT", export_name: "userManagementUsersRolesPUT" },
25
39
  ];
26
40
  const PAGES = [
27
41
  { name: "login", path: "hazo_auth/login", component_name: "LoginPage", import_path: "hazo_auth/pages/login" },
@@ -63,6 +77,16 @@ function generate_route_content(route) {
63
77
  export { ${route.export_name} as ${route.method} } from "hazo_auth/server/routes";
64
78
  `;
65
79
  }
80
+ function generate_route_content_multi(routes) {
81
+ const path = routes[0].path;
82
+ const exports = routes.map(r => `export { ${r.export_name} as ${r.method} } from "hazo_auth/server/routes";`).join("\n");
83
+ const methods = routes.map(r => r.method).join(", ");
84
+ return `// Generated by hazo_auth - do not edit manually
85
+ // Route: /${path}
86
+ // Methods: ${methods}
87
+ ${exports}
88
+ `;
89
+ }
66
90
  function generate_page_content(page) {
67
91
  return `// Generated by hazo_auth - do not edit manually
68
92
  // Page: /${page.path}
@@ -77,23 +101,32 @@ function generate_api_routes(app_dir, project_root) {
77
101
  let skipped = 0;
78
102
  let errors = 0;
79
103
  console.log("\x1b[1m📡 Generating API routes...\x1b[0m\n");
104
+ // Group routes by path to handle multiple methods per path
105
+ const routes_by_path = new Map();
80
106
  for (const route of ROUTES) {
81
- const route_dir = path.join(app_dir, route.path);
107
+ const existing = routes_by_path.get(route.path) || [];
108
+ existing.push(route);
109
+ routes_by_path.set(route.path, existing);
110
+ }
111
+ for (const [route_path, routes_for_path] of routes_by_path) {
112
+ const route_dir = path.join(app_dir, route_path);
82
113
  const route_file = path.join(route_dir, "route.ts");
83
114
  if (file_exists(route_file)) {
84
- console.log(`\x1b[33m[SKIP]\x1b[0m ${route.path}/route.ts (already exists)`);
115
+ console.log(`\x1b[33m[SKIP]\x1b[0m ${route_path}/route.ts (already exists)`);
85
116
  skipped++;
86
117
  continue;
87
118
  }
88
119
  try {
89
120
  ensure_dir(route_dir);
90
- const content = generate_route_content(route);
121
+ // Generate content with all methods for this path
122
+ const content = generate_route_content_multi(routes_for_path);
91
123
  fs.writeFileSync(route_file, content, "utf-8");
92
- console.log(`\x1b[32m[CREATE]\x1b[0m ${route.path}/route.ts`);
124
+ const methods = routes_for_path.map(r => r.method).join(", ");
125
+ console.log(`\x1b[32m[CREATE]\x1b[0m ${route_path}/route.ts (${methods})`);
93
126
  created++;
94
127
  }
95
128
  catch (err) {
96
- console.log(`\x1b[31m[ERROR]\x1b[0m ${route.path}/route.ts - ${err instanceof Error ? err.message : "Unknown error"}`);
129
+ console.log(`\x1b[31m[ERROR]\x1b[0m ${route_path}/route.ts - ${err instanceof Error ? err.message : "Unknown error"}`);
97
130
  errors++;
98
131
  }
99
132
  }
@@ -1 +1 @@
1
- {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AA4dF,wBAAgB,cAAc,IAAI,iBAAiB,CAqElD"}
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/cli/validate.ts"],"names":[],"mappings":"AAOA,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C,KAAK,WAAW,GAAG;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,EAAE,CAAC;CACxB,CAAC;AA0eF,wBAAgB,cAAc,IAAI,iBAAiB,CAqElD"}
@@ -29,6 +29,20 @@ const REQUIRED_API_ROUTES = [
29
29
  { path: "api/hazo_auth/get_auth", method: "POST" },
30
30
  { path: "api/hazo_auth/validate_reset_token", method: "POST" },
31
31
  { path: "api/hazo_auth/profile_picture/[filename]", method: "GET" },
32
+ // User management routes
33
+ { path: "api/hazo_auth/user_management/users", method: "GET" },
34
+ { path: "api/hazo_auth/user_management/users", method: "PATCH" },
35
+ { path: "api/hazo_auth/user_management/users", method: "POST" },
36
+ { path: "api/hazo_auth/user_management/permissions", method: "GET" },
37
+ { path: "api/hazo_auth/user_management/permissions", method: "POST" },
38
+ { path: "api/hazo_auth/user_management/permissions", method: "PUT" },
39
+ { path: "api/hazo_auth/user_management/permissions", method: "DELETE" },
40
+ { path: "api/hazo_auth/user_management/roles", method: "GET" },
41
+ { path: "api/hazo_auth/user_management/roles", method: "POST" },
42
+ { path: "api/hazo_auth/user_management/roles", method: "PUT" },
43
+ { path: "api/hazo_auth/user_management/users/roles", method: "GET" },
44
+ { path: "api/hazo_auth/user_management/users/roles", method: "POST" },
45
+ { path: "api/hazo_auth/user_management/users/roles", method: "PUT" },
32
46
  ];
33
47
  // section: helpers
34
48
  function get_project_root() {
@@ -10,13 +10,17 @@ export type ProfilePicMenuProps = {
10
10
  custom_menu_items?: ProfilePicMenuMenuItem[];
11
11
  className?: string;
12
12
  avatar_size?: "default" | "sm" | "lg";
13
+ variant?: "dropdown" | "sidebar";
14
+ sidebar_group_label?: string;
13
15
  };
14
16
  /**
15
17
  * Profile picture menu component
16
18
  * Shows user profile picture when authenticated, or sign up/sign in buttons when not authenticated
17
- * Clicking profile picture opens dropdown menu with user info and actions
19
+ * Supports two variants:
20
+ * - "dropdown" (default): Clicking profile picture opens dropdown menu (for navbar/header)
21
+ * - "sidebar": Shows profile picture and name in sidebar, clicking opens dropdown menu (for sidebar navigation)
18
22
  * @param props - Component props including configuration options
19
23
  * @returns Profile picture menu component
20
24
  */
21
- export declare function ProfilePicMenu({ show_single_button, sign_up_label, sign_in_label, register_path, login_path, settings_path, logout_path, custom_menu_items, className, avatar_size, }: ProfilePicMenuProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function ProfilePicMenu({ show_single_button, sign_up_label, sign_in_label, register_path, login_path, settings_path, logout_path, custom_menu_items, className, avatar_size, variant, sidebar_group_label, }: ProfilePicMenuProps): import("react/jsx-runtime").JSX.Element;
22
26
  //# sourceMappingURL=profile_pic_menu.d.ts.map