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.
- package/README.md +179 -2
- package/SETUP_CHECKLIST.md +104 -6
- package/dist/app/api/hazo_auth/me/route.d.ts +30 -1
- package/dist/app/api/hazo_auth/me/route.d.ts.map +1 -1
- package/dist/app/api/hazo_auth/me/route.js +76 -16
- package/dist/app/api/hazo_auth/user_management/permissions/route.d.ts +50 -0
- package/dist/app/api/hazo_auth/user_management/permissions/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/user_management/permissions/route.js +257 -0
- package/dist/app/api/hazo_auth/user_management/roles/route.d.ts +40 -0
- package/dist/app/api/hazo_auth/user_management/roles/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/user_management/roles/route.js +352 -0
- package/dist/app/api/hazo_auth/user_management/users/roles/route.d.ts +37 -0
- package/dist/app/api/hazo_auth/user_management/users/roles/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/user_management/users/roles/route.js +276 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts +39 -0
- package/dist/app/api/hazo_auth/user_management/users/route.d.ts.map +1 -0
- package/dist/app/api/hazo_auth/user_management/users/route.js +170 -0
- package/dist/cli/generate.d.ts.map +1 -1
- package/dist/cli/generate.js +38 -5
- package/dist/cli/validate.d.ts.map +1 -1
- package/dist/cli/validate.js +14 -0
- package/dist/components/layouts/shared/components/profile_pic_menu.d.ts +6 -2
- package/dist/components/layouts/shared/components/profile_pic_menu.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/profile_pic_menu.js +39 -4
- package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.d.ts +4 -2
- package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/profile_pic_menu_wrapper.js +3 -3
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.d.ts.map +1 -1
- package/dist/components/layouts/shared/components/sidebar_layout_wrapper.js +2 -2
- package/dist/components/layouts/shared/hooks/use_auth_status.d.ts +3 -0
- package/dist/components/layouts/shared/hooks/use_auth_status.d.ts.map +1 -1
- package/dist/components/layouts/shared/hooks/use_auth_status.js +4 -0
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.d.ts.map +1 -1
- package/dist/server/routes/index.js +5 -0
- package/dist/server/routes/user_management_permissions.d.ts +2 -0
- package/dist/server/routes/user_management_permissions.d.ts.map +1 -0
- package/dist/server/routes/user_management_permissions.js +2 -0
- package/dist/server/routes/user_management_roles.d.ts +2 -0
- package/dist/server/routes/user_management_roles.d.ts.map +1 -0
- package/dist/server/routes/user_management_roles.js +2 -0
- package/dist/server/routes/user_management_users.d.ts +2 -0
- package/dist/server/routes/user_management_users.d.ts.map +1 -0
- package/dist/server/routes/user_management_users.js +2 -0
- package/dist/server/routes/user_management_users_roles.d.ts +2 -0
- package/dist/server/routes/user_management_users_roles.d.ts.map +1 -0
- package/dist/server/routes/user_management_users_roles.js +2 -0
- package/hazo_auth_config.example.ini +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// file_description: API route for permissions management operations (list, migrate from config, update, delete)
|
|
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 { get_user_management_config } from "../../../../../lib/user_management_config.server";
|
|
9
|
+
// section: route_config
|
|
10
|
+
export const dynamic = 'force-dynamic';
|
|
11
|
+
// section: api_handler
|
|
12
|
+
/**
|
|
13
|
+
* GET - Fetch all permissions from database and config
|
|
14
|
+
*/
|
|
15
|
+
export async function GET(request) {
|
|
16
|
+
const logger = create_app_logger();
|
|
17
|
+
try {
|
|
18
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
19
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
20
|
+
// Fetch all permissions from database (empty object means no filter - get all records)
|
|
21
|
+
const db_permissions = await permissions_service.findBy({});
|
|
22
|
+
if (!Array.isArray(db_permissions)) {
|
|
23
|
+
return NextResponse.json({ error: "Failed to fetch permissions" }, { status: 500 });
|
|
24
|
+
}
|
|
25
|
+
// Get config permissions
|
|
26
|
+
const config = get_user_management_config();
|
|
27
|
+
const config_permission_names = config.application_permission_list_defaults || [];
|
|
28
|
+
// Get DB permission names
|
|
29
|
+
const db_permission_names = db_permissions.map((p) => p.permission_name);
|
|
30
|
+
// Find config permissions not in DB
|
|
31
|
+
const config_only_permissions = config_permission_names.filter((name) => !db_permission_names.includes(name));
|
|
32
|
+
logger.info("user_management_permissions_fetched", {
|
|
33
|
+
filename: get_filename(),
|
|
34
|
+
line_number: get_line_number(),
|
|
35
|
+
db_count: db_permissions.length,
|
|
36
|
+
config_count: config_permission_names.length,
|
|
37
|
+
});
|
|
38
|
+
return NextResponse.json({
|
|
39
|
+
success: true,
|
|
40
|
+
db_permissions: db_permissions.map((p) => ({
|
|
41
|
+
id: p.id,
|
|
42
|
+
permission_name: p.permission_name,
|
|
43
|
+
description: p.description || "",
|
|
44
|
+
})),
|
|
45
|
+
config_permissions: config_only_permissions,
|
|
46
|
+
}, { status: 200 });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
50
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
51
|
+
logger.error("user_management_permissions_fetch_error", {
|
|
52
|
+
filename: get_filename(),
|
|
53
|
+
line_number: get_line_number(),
|
|
54
|
+
error_message,
|
|
55
|
+
error_stack,
|
|
56
|
+
});
|
|
57
|
+
return NextResponse.json({ error: "Failed to fetch permissions" }, { status: 500 });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* POST - Create new permission or migrate config permissions to database
|
|
62
|
+
*/
|
|
63
|
+
export async function POST(request) {
|
|
64
|
+
const logger = create_app_logger();
|
|
65
|
+
try {
|
|
66
|
+
const { searchParams } = new URL(request.url);
|
|
67
|
+
const action = searchParams.get("action");
|
|
68
|
+
// Handle migrate action
|
|
69
|
+
if (action === "migrate") {
|
|
70
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
71
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
72
|
+
// Get config permissions
|
|
73
|
+
const config = get_user_management_config();
|
|
74
|
+
const config_permission_names = config.application_permission_list_defaults || [];
|
|
75
|
+
if (config_permission_names.length === 0) {
|
|
76
|
+
return NextResponse.json({
|
|
77
|
+
success: true,
|
|
78
|
+
message: "No permissions to migrate",
|
|
79
|
+
created: [],
|
|
80
|
+
skipped: [],
|
|
81
|
+
}, { status: 200 });
|
|
82
|
+
}
|
|
83
|
+
// Get existing permissions from DB (empty object means no filter - get all records)
|
|
84
|
+
const db_permissions = await permissions_service.findBy({});
|
|
85
|
+
const db_permission_names = Array.isArray(db_permissions)
|
|
86
|
+
? db_permissions.map((p) => p.permission_name)
|
|
87
|
+
: [];
|
|
88
|
+
const now = new Date().toISOString();
|
|
89
|
+
const created = [];
|
|
90
|
+
const skipped = [];
|
|
91
|
+
// Migrate each config permission
|
|
92
|
+
for (const permission_name of config_permission_names) {
|
|
93
|
+
if (db_permission_names.includes(permission_name)) {
|
|
94
|
+
// Skip if already exists
|
|
95
|
+
skipped.push(permission_name);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
// Create new permission
|
|
99
|
+
await permissions_service.insert({
|
|
100
|
+
permission_name: permission_name.trim(),
|
|
101
|
+
description: "",
|
|
102
|
+
created_at: now,
|
|
103
|
+
changed_at: now,
|
|
104
|
+
});
|
|
105
|
+
created.push(permission_name);
|
|
106
|
+
}
|
|
107
|
+
logger.info("user_management_permissions_migrated", {
|
|
108
|
+
filename: get_filename(),
|
|
109
|
+
line_number: get_line_number(),
|
|
110
|
+
created_count: created.length,
|
|
111
|
+
skipped_count: skipped.length,
|
|
112
|
+
});
|
|
113
|
+
return NextResponse.json({
|
|
114
|
+
success: true,
|
|
115
|
+
created,
|
|
116
|
+
skipped,
|
|
117
|
+
}, { status: 200 });
|
|
118
|
+
}
|
|
119
|
+
// Handle create new permission
|
|
120
|
+
const body = await request.json();
|
|
121
|
+
const { permission_name, description } = body;
|
|
122
|
+
if (!permission_name || typeof permission_name !== "string" || permission_name.trim().length === 0) {
|
|
123
|
+
return NextResponse.json({ error: "permission_name is required and must be a non-empty string" }, { status: 400 });
|
|
124
|
+
}
|
|
125
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
126
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
127
|
+
// Check if permission already exists
|
|
128
|
+
const existing_permissions = await permissions_service.findBy({
|
|
129
|
+
permission_name: permission_name.trim(),
|
|
130
|
+
});
|
|
131
|
+
if (Array.isArray(existing_permissions) && existing_permissions.length > 0) {
|
|
132
|
+
return NextResponse.json({ error: "Permission with this name already exists" }, { status: 409 });
|
|
133
|
+
}
|
|
134
|
+
// Create new permission
|
|
135
|
+
const now = new Date().toISOString();
|
|
136
|
+
const new_permission_result = await permissions_service.insert({
|
|
137
|
+
permission_name: permission_name.trim(),
|
|
138
|
+
description: (description || "").trim(),
|
|
139
|
+
created_at: now,
|
|
140
|
+
changed_at: now,
|
|
141
|
+
});
|
|
142
|
+
// insert() returns an array, get the first element
|
|
143
|
+
if (!Array.isArray(new_permission_result) || new_permission_result.length === 0) {
|
|
144
|
+
return NextResponse.json({ error: "Failed to create permission - no record returned" }, { status: 500 });
|
|
145
|
+
}
|
|
146
|
+
const new_permission = new_permission_result[0];
|
|
147
|
+
logger.info("user_management_permission_created", {
|
|
148
|
+
filename: get_filename(),
|
|
149
|
+
line_number: get_line_number(),
|
|
150
|
+
permission_id: new_permission.id,
|
|
151
|
+
permission_name: permission_name.trim(),
|
|
152
|
+
});
|
|
153
|
+
return NextResponse.json({
|
|
154
|
+
success: true,
|
|
155
|
+
permission: {
|
|
156
|
+
id: new_permission.id,
|
|
157
|
+
permission_name: permission_name.trim(),
|
|
158
|
+
description: (description || "").trim(),
|
|
159
|
+
},
|
|
160
|
+
}, { status: 201 });
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
164
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
165
|
+
logger.error("user_management_permissions_post_error", {
|
|
166
|
+
filename: get_filename(),
|
|
167
|
+
line_number: get_line_number(),
|
|
168
|
+
error_message,
|
|
169
|
+
error_stack,
|
|
170
|
+
});
|
|
171
|
+
return NextResponse.json({ error: "Failed to create permission" }, { status: 500 });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* PUT - Update permission description
|
|
176
|
+
*/
|
|
177
|
+
export async function PUT(request) {
|
|
178
|
+
const logger = create_app_logger();
|
|
179
|
+
try {
|
|
180
|
+
const body = await request.json();
|
|
181
|
+
const { permission_id, description } = body;
|
|
182
|
+
if (!permission_id || typeof description !== "string") {
|
|
183
|
+
return NextResponse.json({ error: "permission_id and description are required" }, { status: 400 });
|
|
184
|
+
}
|
|
185
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
186
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
187
|
+
// Update permission with changed_at timestamp
|
|
188
|
+
const now = new Date().toISOString();
|
|
189
|
+
await permissions_service.updateById(permission_id, {
|
|
190
|
+
description: description.trim(),
|
|
191
|
+
changed_at: now,
|
|
192
|
+
});
|
|
193
|
+
logger.info("user_management_permission_updated", {
|
|
194
|
+
filename: get_filename(),
|
|
195
|
+
line_number: get_line_number(),
|
|
196
|
+
permission_id,
|
|
197
|
+
});
|
|
198
|
+
return NextResponse.json({ success: true }, { status: 200 });
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
202
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
203
|
+
logger.error("user_management_permission_update_error", {
|
|
204
|
+
filename: get_filename(),
|
|
205
|
+
line_number: get_line_number(),
|
|
206
|
+
error_message,
|
|
207
|
+
error_stack,
|
|
208
|
+
});
|
|
209
|
+
return NextResponse.json({ error: "Failed to update permission" }, { status: 500 });
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* DELETE - Delete permission from database
|
|
214
|
+
*/
|
|
215
|
+
export async function DELETE(request) {
|
|
216
|
+
const logger = create_app_logger();
|
|
217
|
+
try {
|
|
218
|
+
const { searchParams } = new URL(request.url);
|
|
219
|
+
const permission_id = searchParams.get("permission_id");
|
|
220
|
+
if (!permission_id) {
|
|
221
|
+
return NextResponse.json({ error: "permission_id is required" }, { status: 400 });
|
|
222
|
+
}
|
|
223
|
+
const permission_id_num = parseInt(permission_id, 10);
|
|
224
|
+
if (isNaN(permission_id_num)) {
|
|
225
|
+
return NextResponse.json({ error: "permission_id must be a number" }, { status: 400 });
|
|
226
|
+
}
|
|
227
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
228
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
229
|
+
const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
|
|
230
|
+
// Check if permission is used in any role
|
|
231
|
+
const role_permissions = await role_permissions_service.findBy({
|
|
232
|
+
permission_id: permission_id_num,
|
|
233
|
+
});
|
|
234
|
+
if (Array.isArray(role_permissions) && role_permissions.length > 0) {
|
|
235
|
+
return NextResponse.json({ error: "Cannot delete permission that is assigned to roles" }, { status: 409 });
|
|
236
|
+
}
|
|
237
|
+
// Delete permission
|
|
238
|
+
await permissions_service.deleteById(permission_id_num);
|
|
239
|
+
logger.info("user_management_permission_deleted", {
|
|
240
|
+
filename: get_filename(),
|
|
241
|
+
line_number: get_line_number(),
|
|
242
|
+
permission_id: permission_id_num,
|
|
243
|
+
});
|
|
244
|
+
return NextResponse.json({ success: true }, { status: 200 });
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
248
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
249
|
+
logger.error("user_management_permission_delete_error", {
|
|
250
|
+
filename: get_filename(),
|
|
251
|
+
line_number: get_line_number(),
|
|
252
|
+
error_message,
|
|
253
|
+
error_stack,
|
|
254
|
+
});
|
|
255
|
+
return NextResponse.json({ error: "Failed to delete permission" }, { status: 500 });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
export declare const dynamic = "force-dynamic";
|
|
3
|
+
/**
|
|
4
|
+
* GET - Fetch all roles with their permissions
|
|
5
|
+
*/
|
|
6
|
+
export declare function GET(request: NextRequest): Promise<NextResponse<{
|
|
7
|
+
error: string;
|
|
8
|
+
}> | NextResponse<{
|
|
9
|
+
success: boolean;
|
|
10
|
+
roles: {
|
|
11
|
+
role_id: unknown;
|
|
12
|
+
role_name: unknown;
|
|
13
|
+
permissions: string[];
|
|
14
|
+
}[];
|
|
15
|
+
permissions: {
|
|
16
|
+
id: unknown;
|
|
17
|
+
permission_name: unknown;
|
|
18
|
+
}[];
|
|
19
|
+
}>>;
|
|
20
|
+
/**
|
|
21
|
+
* POST - Create new role
|
|
22
|
+
*/
|
|
23
|
+
export declare function POST(request: NextRequest): Promise<NextResponse<{
|
|
24
|
+
error: string;
|
|
25
|
+
}> | NextResponse<{
|
|
26
|
+
success: boolean;
|
|
27
|
+
role: {
|
|
28
|
+
role_id: number;
|
|
29
|
+
role_name: string;
|
|
30
|
+
};
|
|
31
|
+
}>>;
|
|
32
|
+
/**
|
|
33
|
+
* PUT - Update role permissions (save role-permission matrix)
|
|
34
|
+
*/
|
|
35
|
+
export declare function PUT(request: NextRequest): Promise<NextResponse<{
|
|
36
|
+
error: string;
|
|
37
|
+
}> | NextResponse<{
|
|
38
|
+
success: boolean;
|
|
39
|
+
}>>;
|
|
40
|
+
//# sourceMappingURL=route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../../../../../src/app/api/hazo_auth/user_management/roles/route.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AASxD,eAAO,MAAM,OAAO,kBAAkB,CAAC;AAGvC;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;;;;;;;;;;IAqF7C;AAED;;GAEG;AACH,wBAAsB,IAAI,CAAC,OAAO,EAAE,WAAW;;;;;;;;IAgF9C;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,WAAW;;;;IAwP7C"}
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// file_description: API route for roles management operations (list roles with permissions, create role, update role permissions)
|
|
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 - Fetch all roles with their permissions
|
|
15
|
+
*/
|
|
16
|
+
export async function GET(request) {
|
|
17
|
+
const logger = create_app_logger();
|
|
18
|
+
try {
|
|
19
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
20
|
+
const roles_service = createCrudService(hazoConnect, "hazo_roles");
|
|
21
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
22
|
+
const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
|
|
23
|
+
// Fetch all roles (empty object means no filter - get all records)
|
|
24
|
+
const roles = await roles_service.findBy({});
|
|
25
|
+
const permissions = await permissions_service.findBy({});
|
|
26
|
+
const role_permissions = await role_permissions_service.findBy({});
|
|
27
|
+
if (!Array.isArray(roles) || !Array.isArray(permissions) || !Array.isArray(role_permissions)) {
|
|
28
|
+
return NextResponse.json({ error: "Failed to fetch roles data" }, { status: 500 });
|
|
29
|
+
}
|
|
30
|
+
// Build role-permission mapping
|
|
31
|
+
const role_permission_map = {};
|
|
32
|
+
role_permissions.forEach((rp) => {
|
|
33
|
+
const role_id = rp.role_id;
|
|
34
|
+
const permission_id = rp.permission_id;
|
|
35
|
+
if (!role_permission_map[role_id]) {
|
|
36
|
+
role_permission_map[role_id] = [];
|
|
37
|
+
}
|
|
38
|
+
role_permission_map[role_id].push(permission_id);
|
|
39
|
+
});
|
|
40
|
+
// Build permission name map
|
|
41
|
+
const permission_name_map = {};
|
|
42
|
+
permissions.forEach((perm) => {
|
|
43
|
+
permission_name_map[perm.id] = perm.permission_name;
|
|
44
|
+
});
|
|
45
|
+
// Format response
|
|
46
|
+
const roles_with_permissions = roles.map((role) => {
|
|
47
|
+
const role_id = role.id;
|
|
48
|
+
const permission_ids = role_permission_map[role_id] || [];
|
|
49
|
+
const permission_names = permission_ids.map((pid) => permission_name_map[pid]).filter(Boolean);
|
|
50
|
+
return {
|
|
51
|
+
role_id: role.id,
|
|
52
|
+
role_name: role.role_name,
|
|
53
|
+
permissions: permission_names,
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
logger.info("user_management_roles_fetched", {
|
|
57
|
+
filename: get_filename(),
|
|
58
|
+
line_number: get_line_number(),
|
|
59
|
+
role_count: roles.length,
|
|
60
|
+
permission_count: permissions.length,
|
|
61
|
+
});
|
|
62
|
+
return NextResponse.json({
|
|
63
|
+
success: true,
|
|
64
|
+
roles: roles_with_permissions,
|
|
65
|
+
permissions: permissions.map((p) => ({
|
|
66
|
+
id: p.id,
|
|
67
|
+
permission_name: p.permission_name,
|
|
68
|
+
})),
|
|
69
|
+
}, { status: 200 });
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
73
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
74
|
+
logger.error("user_management_roles_fetch_error", {
|
|
75
|
+
filename: get_filename(),
|
|
76
|
+
line_number: get_line_number(),
|
|
77
|
+
error_message,
|
|
78
|
+
error_stack,
|
|
79
|
+
});
|
|
80
|
+
return NextResponse.json({ error: "Failed to fetch roles" }, { status: 500 });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* POST - Create new role
|
|
85
|
+
*/
|
|
86
|
+
export async function POST(request) {
|
|
87
|
+
const logger = create_app_logger();
|
|
88
|
+
try {
|
|
89
|
+
const body = await request.json();
|
|
90
|
+
const { role_name } = body;
|
|
91
|
+
if (!role_name || typeof role_name !== "string" || role_name.trim().length === 0) {
|
|
92
|
+
return NextResponse.json({ error: "role_name is required and must be a non-empty string" }, { status: 400 });
|
|
93
|
+
}
|
|
94
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
95
|
+
const roles_service = createCrudService(hazoConnect, "hazo_roles");
|
|
96
|
+
// Check if role already exists
|
|
97
|
+
const existing_roles = await roles_service.findBy({
|
|
98
|
+
role_name: role_name.trim(),
|
|
99
|
+
});
|
|
100
|
+
if (Array.isArray(existing_roles) && existing_roles.length > 0) {
|
|
101
|
+
return NextResponse.json({ error: "Role with this name already exists" }, { status: 409 });
|
|
102
|
+
}
|
|
103
|
+
// Create new role
|
|
104
|
+
const now = new Date().toISOString();
|
|
105
|
+
const new_role_result = await roles_service.insert({
|
|
106
|
+
role_name: role_name.trim(),
|
|
107
|
+
created_at: now,
|
|
108
|
+
changed_at: now,
|
|
109
|
+
});
|
|
110
|
+
// insert() returns an array, get the first element
|
|
111
|
+
if (!Array.isArray(new_role_result) || new_role_result.length === 0) {
|
|
112
|
+
return NextResponse.json({ error: "Failed to create role - no record returned" }, { status: 500 });
|
|
113
|
+
}
|
|
114
|
+
const new_role = new_role_result[0];
|
|
115
|
+
logger.info("user_management_role_created", {
|
|
116
|
+
filename: get_filename(),
|
|
117
|
+
line_number: get_line_number(),
|
|
118
|
+
role_id: new_role.id,
|
|
119
|
+
role_name: role_name.trim(),
|
|
120
|
+
});
|
|
121
|
+
return NextResponse.json({
|
|
122
|
+
success: true,
|
|
123
|
+
role: {
|
|
124
|
+
role_id: new_role.id,
|
|
125
|
+
role_name: role_name.trim(),
|
|
126
|
+
},
|
|
127
|
+
}, { status: 201 });
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
131
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
132
|
+
logger.error("user_management_role_create_error", {
|
|
133
|
+
filename: get_filename(),
|
|
134
|
+
line_number: get_line_number(),
|
|
135
|
+
error_message,
|
|
136
|
+
error_stack,
|
|
137
|
+
});
|
|
138
|
+
return NextResponse.json({ error: "Failed to create role" }, { status: 500 });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* PUT - Update role permissions (save role-permission matrix)
|
|
143
|
+
*/
|
|
144
|
+
export async function PUT(request) {
|
|
145
|
+
const logger = create_app_logger();
|
|
146
|
+
try {
|
|
147
|
+
const body = await request.json();
|
|
148
|
+
const { roles } = body;
|
|
149
|
+
if (!Array.isArray(roles)) {
|
|
150
|
+
return NextResponse.json({ error: "roles array is required" }, { status: 400 });
|
|
151
|
+
}
|
|
152
|
+
const hazoConnect = get_hazo_connect_instance();
|
|
153
|
+
const roles_service = createCrudService(hazoConnect, "hazo_roles");
|
|
154
|
+
const permissions_service = createCrudService(hazoConnect, "hazo_permissions");
|
|
155
|
+
const role_permissions_service = createCrudService(hazoConnect, "hazo_role_permissions");
|
|
156
|
+
// Get all permissions to build name-to-id map (empty object means no filter - get all records)
|
|
157
|
+
const all_permissions = await permissions_service.findBy({});
|
|
158
|
+
if (!Array.isArray(all_permissions)) {
|
|
159
|
+
return NextResponse.json({ error: "Failed to fetch permissions" }, { status: 500 });
|
|
160
|
+
}
|
|
161
|
+
const permission_name_to_id = {};
|
|
162
|
+
all_permissions.forEach((perm) => {
|
|
163
|
+
permission_name_to_id[perm.permission_name] = perm.id;
|
|
164
|
+
});
|
|
165
|
+
const now = new Date().toISOString();
|
|
166
|
+
const modified_role_ids = []; // Track all role IDs that were modified
|
|
167
|
+
// Process each role
|
|
168
|
+
for (const role_data of roles) {
|
|
169
|
+
const { role_id, role_name, permissions } = role_data;
|
|
170
|
+
if (!role_name || !Array.isArray(permissions)) {
|
|
171
|
+
continue; // Skip invalid entries
|
|
172
|
+
}
|
|
173
|
+
let current_role_id;
|
|
174
|
+
if (role_id) {
|
|
175
|
+
// Update existing role
|
|
176
|
+
current_role_id = role_id;
|
|
177
|
+
await roles_service.updateById(role_id, {
|
|
178
|
+
role_name: role_name.trim(),
|
|
179
|
+
changed_at: now,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
// Create new role
|
|
184
|
+
const existing_roles = await roles_service.findBy({
|
|
185
|
+
role_name: role_name.trim(),
|
|
186
|
+
});
|
|
187
|
+
if (Array.isArray(existing_roles) && existing_roles.length > 0) {
|
|
188
|
+
current_role_id = existing_roles[0].id;
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const new_role = await roles_service.insert({
|
|
192
|
+
role_name: role_name.trim(),
|
|
193
|
+
created_at: now,
|
|
194
|
+
changed_at: now,
|
|
195
|
+
});
|
|
196
|
+
// Handle both single object and array responses from insert
|
|
197
|
+
if (Array.isArray(new_role) && new_role.length > 0) {
|
|
198
|
+
current_role_id = new_role[0].id;
|
|
199
|
+
}
|
|
200
|
+
else if (!Array.isArray(new_role) && new_role.id !== undefined) {
|
|
201
|
+
current_role_id = new_role.id;
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
// If insert didn't return an id, try to find the role by name
|
|
205
|
+
const inserted_roles = await roles_service.findBy({
|
|
206
|
+
role_name: role_name.trim(),
|
|
207
|
+
});
|
|
208
|
+
if (Array.isArray(inserted_roles) && inserted_roles.length > 0) {
|
|
209
|
+
current_role_id = inserted_roles[0].id;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// Skip if we couldn't determine the role ID
|
|
215
|
+
if (!current_role_id) {
|
|
216
|
+
logger.warn("user_management_role_id_not_found", {
|
|
217
|
+
filename: get_filename(),
|
|
218
|
+
line_number: get_line_number(),
|
|
219
|
+
role_name: role_name.trim(),
|
|
220
|
+
role_id,
|
|
221
|
+
});
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
// Track this role ID for cache invalidation
|
|
225
|
+
modified_role_ids.push(current_role_id);
|
|
226
|
+
// Get current role-permission mappings
|
|
227
|
+
const current_mappings = await role_permissions_service.findBy({
|
|
228
|
+
role_id: current_role_id,
|
|
229
|
+
});
|
|
230
|
+
const current_permission_ids = Array.isArray(current_mappings)
|
|
231
|
+
? current_mappings.map((m) => m.permission_id)
|
|
232
|
+
: [];
|
|
233
|
+
// Get target permission IDs
|
|
234
|
+
const target_permission_ids = permissions
|
|
235
|
+
.map((perm_name) => permission_name_to_id[perm_name])
|
|
236
|
+
.filter((id) => id !== undefined);
|
|
237
|
+
// Delete removed permissions
|
|
238
|
+
// Note: hazo_role_permissions is a junction table without an id column
|
|
239
|
+
// We need to use SQLite admin service to delete by composite key (role_id, permission_id)
|
|
240
|
+
const to_delete = current_permission_ids.filter((id) => !target_permission_ids.includes(id));
|
|
241
|
+
if (to_delete.length > 0) {
|
|
242
|
+
try {
|
|
243
|
+
const admin_service = getSqliteAdminService();
|
|
244
|
+
for (const perm_id of to_delete) {
|
|
245
|
+
// Delete using SQLite admin service with criteria (role_id and permission_id)
|
|
246
|
+
await admin_service.deleteRows("hazo_role_permissions", {
|
|
247
|
+
role_id: current_role_id,
|
|
248
|
+
permission_id: perm_id,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch (adminError) {
|
|
253
|
+
// Fallback: try using createCrudService deleteById if rowid exists
|
|
254
|
+
// SQLite tables have a hidden rowid column that can be used
|
|
255
|
+
const error_message = adminError instanceof Error ? adminError.message : "Unknown error";
|
|
256
|
+
logger.warn("user_management_role_permission_delete_admin_failed", {
|
|
257
|
+
filename: get_filename(),
|
|
258
|
+
line_number: get_line_number(),
|
|
259
|
+
error: error_message,
|
|
260
|
+
note: "Trying fallback method",
|
|
261
|
+
});
|
|
262
|
+
// Fallback: try to find and delete using rowid if available
|
|
263
|
+
for (const perm_id of to_delete) {
|
|
264
|
+
const mappings_to_delete = await role_permissions_service.findBy({
|
|
265
|
+
role_id: current_role_id,
|
|
266
|
+
permission_id: perm_id,
|
|
267
|
+
});
|
|
268
|
+
if (Array.isArray(mappings_to_delete) && mappings_to_delete.length > 0) {
|
|
269
|
+
for (const mapping of mappings_to_delete) {
|
|
270
|
+
// Try deleteById with rowid (SQLite has hidden rowid)
|
|
271
|
+
try {
|
|
272
|
+
// Check if mapping has an id field (could be rowid)
|
|
273
|
+
if (mapping.id !== undefined) {
|
|
274
|
+
await role_permissions_service.deleteById(mapping.id);
|
|
275
|
+
}
|
|
276
|
+
else if (mapping.rowid !== undefined) {
|
|
277
|
+
await role_permissions_service.deleteById(mapping.rowid);
|
|
278
|
+
}
|
|
279
|
+
else {
|
|
280
|
+
// Last resort: log error
|
|
281
|
+
logger.error("user_management_role_permission_delete_no_id", {
|
|
282
|
+
filename: get_filename(),
|
|
283
|
+
line_number: get_line_number(),
|
|
284
|
+
role_id: current_role_id,
|
|
285
|
+
permission_id: perm_id,
|
|
286
|
+
mapping,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
catch (deleteError) {
|
|
291
|
+
const delete_error_message = deleteError instanceof Error ? deleteError.message : "Unknown error";
|
|
292
|
+
logger.error("user_management_role_permission_delete_failed", {
|
|
293
|
+
filename: get_filename(),
|
|
294
|
+
line_number: get_line_number(),
|
|
295
|
+
role_id: current_role_id,
|
|
296
|
+
permission_id: perm_id,
|
|
297
|
+
error: delete_error_message,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
// Add new permissions
|
|
306
|
+
const to_add = target_permission_ids.filter((id) => !current_permission_ids.includes(id));
|
|
307
|
+
for (const perm_id of to_add) {
|
|
308
|
+
await role_permissions_service.insert({
|
|
309
|
+
role_id: current_role_id,
|
|
310
|
+
permission_id: perm_id,
|
|
311
|
+
created_at: now,
|
|
312
|
+
changed_at: now,
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
// Invalidate cache for all affected roles
|
|
317
|
+
try {
|
|
318
|
+
const config = get_auth_utility_config();
|
|
319
|
+
const cache = get_auth_cache(config.cache_max_users, config.cache_ttl_minutes, config.cache_max_age_minutes);
|
|
320
|
+
// Invalidate by all role IDs that were modified (including newly created ones)
|
|
321
|
+
if (modified_role_ids.length > 0) {
|
|
322
|
+
cache.invalidate_by_roles(modified_role_ids);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
catch (cache_error) {
|
|
326
|
+
// Log but don't fail role update if cache invalidation fails
|
|
327
|
+
const cache_error_message = cache_error instanceof Error ? cache_error.message : "Unknown error";
|
|
328
|
+
logger.warn("user_management_roles_cache_invalidation_failed", {
|
|
329
|
+
filename: get_filename(),
|
|
330
|
+
line_number: get_line_number(),
|
|
331
|
+
error: cache_error_message,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
logger.info("user_management_roles_updated", {
|
|
335
|
+
filename: get_filename(),
|
|
336
|
+
line_number: get_line_number(),
|
|
337
|
+
role_count: roles.length,
|
|
338
|
+
});
|
|
339
|
+
return NextResponse.json({ success: true }, { status: 200 });
|
|
340
|
+
}
|
|
341
|
+
catch (error) {
|
|
342
|
+
const error_message = error instanceof Error ? error.message : "Unknown error";
|
|
343
|
+
const error_stack = error instanceof Error ? error.stack : undefined;
|
|
344
|
+
logger.error("user_management_roles_update_error", {
|
|
345
|
+
filename: get_filename(),
|
|
346
|
+
line_number: get_line_number(),
|
|
347
|
+
error_message,
|
|
348
|
+
error_stack,
|
|
349
|
+
});
|
|
350
|
+
return NextResponse.json({ error: "Failed to update roles" }, { status: 500 });
|
|
351
|
+
}
|
|
352
|
+
}
|