openlearn-next 0.2.3 → 0.2.6
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 +34 -136
- package/dist/assets/AdminDirectoryView-Cai0cQqi.js +1 -0
- package/dist/assets/BatchPickerModal-CKQyeY6R.js +1 -0
- package/dist/assets/ClassesView-Cxgfcrx1.js +19 -0
- package/dist/assets/CloudDriveModal-DgDurUBT.js +1 -0
- package/dist/assets/ComputerLabView-CUCO2VIV.js +1 -0
- package/dist/assets/CourseManagement-DSCOjuIC.js +1 -0
- package/dist/assets/CourseWizardModal-B3QnFxvj.js +3 -0
- package/dist/assets/CoursewareHubPanel-CAaf22Qm.js +1 -0
- package/dist/assets/Dashboard-Xly67TyD.js +8 -0
- package/dist/assets/ExportWeightModal-BlS2auLs.js +1 -0
- package/dist/assets/HelpTour-ZgEkRnin.js +1 -0
- package/dist/assets/HelpView-DgFLEuso.js +204 -0
- package/dist/assets/ImportLessonsModal-BZZRDBRU.js +1 -0
- package/dist/assets/ImportModal-mkWuDoAN.js +1 -0
- package/dist/assets/InteractiveCoursewareViewer-EIRNulEo.js +1 -0
- package/dist/assets/InteractiveWhiteboard-DDOKVrHd.js +102 -0
- package/dist/assets/LazyCourseware-qE4CY67E.js +1 -0
- package/dist/assets/LazyWhiteboard-C9fzRtzL.js +2 -0
- package/dist/assets/LessonEditorView-D1nhlQon.js +1 -0
- package/dist/assets/LiveClassroomView-C1lI4lFd.js +1 -0
- package/dist/assets/NotificationDetailModal-dndFrE7p.js +1 -0
- package/dist/assets/PluginView-5TYIP1ze.js +65 -0
- package/dist/assets/ProcessLogsModal-8mVp07ic.js +1 -0
- package/dist/assets/QuizGeneratorModal-DpTFYbVJ.js +1 -0
- package/dist/assets/StudentAssignmentView-CCGWFVqD.js +1 -0
- package/dist/assets/StudentLessonView-yhRHP8e8.js +1 -0
- package/dist/assets/StudentPreviewModal-UpNg1yl0.js +1 -0
- package/dist/assets/StudentView-C1cc6RuC.js +1 -0
- package/dist/assets/SystemResourceLibraryModal-Dnh7F5Vm.js +1 -0
- package/dist/assets/TeacherView-CWjbV0x-.js +2 -0
- package/dist/assets/TimetableView-zce3okQn.js +6 -0
- package/dist/assets/index-B9cwmoaG.js +129 -0
- package/dist/assets/index-BrEBeWAQ.js +14 -0
- package/dist/assets/index-CATki2cp.css +1 -0
- package/dist/assets/index-Cq_aKTfc.js +1 -0
- package/dist/assets/{index.es-CF5dm42F.js → index.es-DZ6UeYE-.js} +2 -2
- package/dist/assets/vendor-charts-B7ViBL9w.js +71 -0
- package/dist/assets/vendor-icons-BvCtGbt1.js +8221 -0
- package/dist/assets/vendor-konva-CzxOmTv9.js +49 -0
- package/dist/assets/vendor-motion-Dz5URUo4.js +9 -0
- package/dist/assets/vendor-pdf-CVyNPGVr.js +191 -0
- package/dist/assets/{vendor-pptx-CJ3I-KT0.js → vendor-pptx-B0hlo0eh.js} +1 -1
- package/dist/assets/vendor-react-0Lg0vA-4.js +49 -0
- package/dist/assets/vendor-utils-4FcTwKPj.js +1 -0
- package/dist/index.html +2 -3
- package/dist/server.cjs +185 -73
- package/package.json +1 -1
- package/dist/assets/courseware-Cc-Yy0Ha.js +0 -1
- package/dist/assets/index-CsN5CIqI.css +0 -1
- package/dist/assets/index-D_zAVH7u.js +0 -682
- package/dist/assets/vendor-html2canvas-B0tyYwQk.js +0 -22
- package/dist/assets/vendor-konva-GivUq6dt.js +0 -89
- package/dist/assets/whiteboard-jgQrAUm7.js +0 -8343
- /package/dist/assets/{purify.es-V6uLfjnH.js → vendor-content-V6uLfjnH.js} +0 -0
package/dist/server.cjs
CHANGED
|
@@ -973,8 +973,8 @@ function wrapCommandBus(commandBus, tracker, pluginId, manifestId) {
|
|
|
973
973
|
throw e;
|
|
974
974
|
}
|
|
975
975
|
}),
|
|
976
|
-
setInterceptor: createSafeFunction((
|
|
977
|
-
|
|
976
|
+
setInterceptor: createSafeFunction((_interceptor) => {
|
|
977
|
+
throw new Error("[Security] setInterceptor cannot be configured from PluginContext");
|
|
978
978
|
}),
|
|
979
979
|
unregisterHandler: createSafeFunction((commandType) => {
|
|
980
980
|
return commandBus.unregisterHandler(resolveType(commandType));
|
|
@@ -2506,8 +2506,8 @@ parentPort.on('message', async function(msg) {
|
|
|
2506
2506
|
createCommand: function(type, payload, actorId, metadata) {
|
|
2507
2507
|
return rawCommandBus.createCommand(type, payload, actorId, metadata);
|
|
2508
2508
|
},
|
|
2509
|
-
setInterceptor: function(
|
|
2510
|
-
|
|
2509
|
+
setInterceptor: function(_interceptor) {
|
|
2510
|
+
throw new Error('[Security] setInterceptor cannot be configured from Worker PluginContext');
|
|
2511
2511
|
}
|
|
2512
2512
|
} : undefined;
|
|
2513
2513
|
|
|
@@ -2659,6 +2659,21 @@ parentPort.on('message', async function(msg) {
|
|
|
2659
2659
|
},
|
|
2660
2660
|
db: dbApi,
|
|
2661
2661
|
require: function(moduleName) {
|
|
2662
|
+
var BLOCKED_NATIVE_MODULES = [
|
|
2663
|
+
'child_process', 'node:child_process',
|
|
2664
|
+
'fs', 'node:fs', 'fs/promises', 'node:fs/promises',
|
|
2665
|
+
'net', 'node:net',
|
|
2666
|
+
'http', 'node:http', 'https', 'node:https', 'http2', 'node:http2',
|
|
2667
|
+
'dgram', 'node:dgram', 'dns', 'node:dns',
|
|
2668
|
+
'cluster', 'node:cluster',
|
|
2669
|
+
'worker_threads', 'node:worker_threads',
|
|
2670
|
+
'vm', 'node:vm',
|
|
2671
|
+
'v8', 'node:v8',
|
|
2672
|
+
'wasi', 'node:wasi'
|
|
2673
|
+
];
|
|
2674
|
+
if (BLOCKED_NATIVE_MODULES.indexOf(moduleName) !== -1) {
|
|
2675
|
+
throw new Error('[SecurityError] Direct access to Node.js native module "' + moduleName + '" is forbidden in plugin worker sandbox.');
|
|
2676
|
+
}
|
|
2662
2677
|
if (PLUGIN_SHARED_MODULES.indexOf(moduleName) !== -1) {
|
|
2663
2678
|
try {
|
|
2664
2679
|
return requireFn(moduleName);
|
|
@@ -3374,7 +3389,16 @@ var CapabilityGuard = class {
|
|
|
3374
3389
|
constructor() {
|
|
3375
3390
|
this.actorCapabilities = /* @__PURE__ */ new Map();
|
|
3376
3391
|
this.actorCapabilities.set("user-demo", ["*:*:*"]);
|
|
3377
|
-
this.actorCapabilities.set("user-frontend", [
|
|
3392
|
+
this.actorCapabilities.set("user-frontend", [
|
|
3393
|
+
"lesson:*",
|
|
3394
|
+
"whiteboard:*",
|
|
3395
|
+
"management:*",
|
|
3396
|
+
"quiz:*",
|
|
3397
|
+
"vfs:*",
|
|
3398
|
+
"process:*",
|
|
3399
|
+
"plugin:*"
|
|
3400
|
+
]);
|
|
3401
|
+
this.actorCapabilities.set("anonymous", []);
|
|
3378
3402
|
this.actorCapabilities.set("agent-system-0", [
|
|
3379
3403
|
"lesson:write",
|
|
3380
3404
|
"lesson:delete",
|
|
@@ -3388,8 +3412,8 @@ var CapabilityGuard = class {
|
|
|
3388
3412
|
"process:write",
|
|
3389
3413
|
"process:read"
|
|
3390
3414
|
]);
|
|
3391
|
-
this.actorCapabilities.set("teacher-demo", ["lesson
|
|
3392
|
-
this.actorCapabilities.set("student-demo", ["student:write"]);
|
|
3415
|
+
this.actorCapabilities.set("teacher-demo", ["lesson:*", "whiteboard:*", "management:*", "quiz:*", "vfs:*"]);
|
|
3416
|
+
this.actorCapabilities.set("student-demo", ["student:write", "lesson:read", "whiteboard:read"]);
|
|
3393
3417
|
}
|
|
3394
3418
|
grant(actorId, cap) {
|
|
3395
3419
|
const caps = this.actorCapabilities.get(actorId) || [];
|
|
@@ -3402,8 +3426,28 @@ var CapabilityGuard = class {
|
|
|
3402
3426
|
this.actorCapabilities.delete(actorId);
|
|
3403
3427
|
}
|
|
3404
3428
|
check(actorId, requiredCap) {
|
|
3405
|
-
const isAdmin = actorId === "role:administrator" || actorId?.endsWith(":administrator") || actorId === "admin-demo";
|
|
3429
|
+
const isAdmin = actorId === "role:administrator" || actorId?.endsWith(":administrator") || actorId === "admin" || actorId === "usr_admin" || actorId === "admin-demo";
|
|
3406
3430
|
if (isAdmin) return true;
|
|
3431
|
+
if (actorId?.endsWith(":teacher")) {
|
|
3432
|
+
const teacherCaps = ["lesson:*", "whiteboard:*", "management:*", "quiz:*", "vfs:*", "process:*", "plugin:*"];
|
|
3433
|
+
const [reqRes2, reqAct2] = requiredCap.split(":");
|
|
3434
|
+
if (teacherCaps.some((c) => {
|
|
3435
|
+
const [res, act] = c.split(":");
|
|
3436
|
+
return (res === reqRes2 || res === "*") && (act === reqAct2 || act === "*");
|
|
3437
|
+
})) {
|
|
3438
|
+
return true;
|
|
3439
|
+
}
|
|
3440
|
+
}
|
|
3441
|
+
if (actorId?.endsWith(":student")) {
|
|
3442
|
+
const studentCaps = ["student:write", "lesson:read", "whiteboard:read"];
|
|
3443
|
+
const [reqRes2, reqAct2] = requiredCap.split(":");
|
|
3444
|
+
if (studentCaps.some((c) => {
|
|
3445
|
+
const [res, act] = c.split(":");
|
|
3446
|
+
return (res === reqRes2 || res === "*") && (act === reqAct2 || act === "*");
|
|
3447
|
+
})) {
|
|
3448
|
+
return true;
|
|
3449
|
+
}
|
|
3450
|
+
}
|
|
3407
3451
|
const caps = this.actorCapabilities.get(actorId) || [];
|
|
3408
3452
|
if (caps.includes("*:*:*") || caps.includes("*")) return true;
|
|
3409
3453
|
if (caps.includes(requiredCap)) return true;
|
|
@@ -3638,7 +3682,7 @@ var classroomToolSchema = import_zod.z.object({
|
|
|
3638
3682
|
icon: import_zod.z.string().optional(),
|
|
3639
3683
|
description: import_zod.z.string().optional(),
|
|
3640
3684
|
commandType: import_zod.z.string().min(1),
|
|
3641
|
-
payload: import_zod.z.record(import_zod.z.unknown()).optional()
|
|
3685
|
+
payload: import_zod.z.record(import_zod.z.string(), import_zod.z.unknown()).optional()
|
|
3642
3686
|
});
|
|
3643
3687
|
var contributesSchema = import_zod.z.object({
|
|
3644
3688
|
"classroom.tool": import_zod.z.array(classroomToolSchema).optional(),
|
|
@@ -3665,7 +3709,10 @@ var contributesSchema = import_zod.z.object({
|
|
|
3665
3709
|
label: import_zod.z.string().min(1),
|
|
3666
3710
|
icon: import_zod.z.string().optional()
|
|
3667
3711
|
})).optional()
|
|
3668
|
-
|
|
3712
|
+
// v0.2.6: 锚点槽位(anchor:*)为开放命名空间,由宿主公布锚点 id。
|
|
3713
|
+
// .passthrough() 保留任意 anchor:* 键(运行时透传),避免被 zod 默认 strip。
|
|
3714
|
+
// 锚点条目的运行时校验由前端注册时(ExtensionPointConfig)完成。
|
|
3715
|
+
}).passthrough().optional();
|
|
3669
3716
|
var deploySchema = import_zod.z.object({
|
|
3670
3717
|
script: import_zod.z.string().min(1, { error: "deploy.script \u4E0D\u80FD\u4E3A\u7A7A" }).optional(),
|
|
3671
3718
|
staticRoute: import_zod.z.string().optional(),
|
|
@@ -3689,7 +3736,7 @@ var manifestSchema = import_zod.z.object({
|
|
|
3689
3736
|
pluginDependencies: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
3690
3737
|
provides: import_zod.z.array(import_zod.z.string().min(1)).optional(),
|
|
3691
3738
|
configuration: import_zod.z.object({
|
|
3692
|
-
properties: import_zod.z.record(import_zod.z.object({
|
|
3739
|
+
properties: import_zod.z.record(import_zod.z.string(), import_zod.z.object({
|
|
3693
3740
|
type: import_zod.z.enum(["string", "number", "boolean", "integer"]),
|
|
3694
3741
|
default: import_zod.z.unknown().optional(),
|
|
3695
3742
|
description: import_zod.z.string().optional(),
|
|
@@ -4116,6 +4163,17 @@ db.exec(`
|
|
|
4116
4163
|
status TEXT NOT NULL DEFAULT 'draft',
|
|
4117
4164
|
graded_at INTEGER
|
|
4118
4165
|
);
|
|
4166
|
+
|
|
4167
|
+
-- Performance Indexes
|
|
4168
|
+
CREATE INDEX IF NOT EXISTS idx_whiteboard_lesson ON whiteboard_elements(lesson_id);
|
|
4169
|
+
CREATE INDEX IF NOT EXISTS idx_class_students_class ON class_students(class_id);
|
|
4170
|
+
CREATE INDEX IF NOT EXISTS idx_class_students_student ON class_students(student_id);
|
|
4171
|
+
CREATE INDEX IF NOT EXISTS idx_schedules_class_date ON schedules(class_id, scheduled_date);
|
|
4172
|
+
CREATE INDEX IF NOT EXISTS idx_courseware_attempt_cw_st ON courseware_attempt(courseware_id, student_id);
|
|
4173
|
+
CREATE INDEX IF NOT EXISTS idx_submission_result_attempt ON submission_result(attempt_id);
|
|
4174
|
+
CREATE INDEX IF NOT EXISTS idx_events_type_time ON events(type, timestamp);
|
|
4175
|
+
CREATE INDEX IF NOT EXISTS idx_assignments_class ON assignments(class_id);
|
|
4176
|
+
CREATE INDEX IF NOT EXISTS idx_attendance_schedule ON attendance(schedule_id);
|
|
4119
4177
|
`);
|
|
4120
4178
|
try {
|
|
4121
4179
|
db.exec(`ALTER TABLE plugins ADD COLUMN execution_mode TEXT DEFAULT 'inline'`);
|
|
@@ -4250,7 +4308,7 @@ var VfsPlugin = {
|
|
|
4250
4308
|
"@openlearn/core:IDatabase@^1.0.0"
|
|
4251
4309
|
],
|
|
4252
4310
|
capabilitiesProposed: ["vfs:read", "vfs:write"],
|
|
4253
|
-
engines: { openlearn: "^2.5
|
|
4311
|
+
engines: { openlearn: "^0.2.5" }
|
|
4254
4312
|
},
|
|
4255
4313
|
activate: async (ctx) => {
|
|
4256
4314
|
const commandBus = ctx.services.commandBus;
|
|
@@ -4416,7 +4474,7 @@ var ProcessPlugin = {
|
|
|
4416
4474
|
"@openlearn/core:IDatabase@^1.0.0"
|
|
4417
4475
|
],
|
|
4418
4476
|
capabilitiesProposed: ["process:read", "process:write"],
|
|
4419
|
-
engines: { openlearn: "^2.5
|
|
4477
|
+
engines: { openlearn: "^0.2.5" }
|
|
4420
4478
|
},
|
|
4421
4479
|
activate: async (ctx) => {
|
|
4422
4480
|
const commandBus = ctx.services.commandBus;
|
|
@@ -4547,7 +4605,7 @@ var ManagementPlugin = {
|
|
|
4547
4605
|
"@openlearn/core:IDatabase@^1.0.0"
|
|
4548
4606
|
],
|
|
4549
4607
|
capabilitiesProposed: ["class:read", "class:write", "student:read", "student:write"],
|
|
4550
|
-
engines: { openlearn: "^2.5
|
|
4608
|
+
engines: { openlearn: "^0.2.5" }
|
|
4551
4609
|
},
|
|
4552
4610
|
activate: async (ctx) => {
|
|
4553
4611
|
const commandBus = ctx.services.commandBus;
|
|
@@ -5446,7 +5504,7 @@ var AiSubmitInjectorPlugin = {
|
|
|
5446
5504
|
"@openlearn/core:IAIService@^1.0.0"
|
|
5447
5505
|
],
|
|
5448
5506
|
capabilitiesProposed: [],
|
|
5449
|
-
engines: { openlearn: "^2.5
|
|
5507
|
+
engines: { openlearn: "^0.2.5" }
|
|
5450
5508
|
},
|
|
5451
5509
|
activate: async (ctx) => {
|
|
5452
5510
|
const eventBus = ctx.services.eventBus;
|
|
@@ -5538,7 +5596,7 @@ var BuiltinPlugin = {
|
|
|
5538
5596
|
"@openlearn/core:IPluginHost@^1.0.0"
|
|
5539
5597
|
],
|
|
5540
5598
|
capabilitiesProposed: ["lesson:read", "lesson:write", "whiteboard:read", "whiteboard:write"],
|
|
5541
|
-
engines: { openlearn: "^2.5
|
|
5599
|
+
engines: { openlearn: "^0.2.5" }
|
|
5542
5600
|
},
|
|
5543
5601
|
activate: async (ctx) => {
|
|
5544
5602
|
const commandBus = ctx.services.commandBus;
|
|
@@ -6923,7 +6981,7 @@ var AiPlannerPlugin = {
|
|
|
6923
6981
|
"@openlearn/core:IEventBusService@^1.0.0"
|
|
6924
6982
|
],
|
|
6925
6983
|
capabilitiesProposed: ["process:write", "lesson:write", "assignment:write"],
|
|
6926
|
-
engines: { openlearn: "^2.5
|
|
6984
|
+
engines: { openlearn: "^0.2.5" }
|
|
6927
6985
|
},
|
|
6928
6986
|
activate: async (ctx) => {
|
|
6929
6987
|
const commandBus = ctx.services.commandBus;
|
|
@@ -7109,7 +7167,7 @@ var AssignmentEvalPlugin = {
|
|
|
7109
7167
|
"@openlearn/core:ISemesterGradeService@^1.0.0"
|
|
7110
7168
|
],
|
|
7111
7169
|
capabilitiesProposed: ["lesson:read", "lesson:write"],
|
|
7112
|
-
engines: { openlearn: "^2.5
|
|
7170
|
+
engines: { openlearn: "^0.2.5" }
|
|
7113
7171
|
},
|
|
7114
7172
|
activate: async (ctx) => {
|
|
7115
7173
|
const commandBus = ctx.services.commandBus;
|
|
@@ -7695,7 +7753,7 @@ var ContributionRegistry = class {
|
|
|
7695
7753
|
register(pluginId, contributes) {
|
|
7696
7754
|
this.unregister(pluginId);
|
|
7697
7755
|
for (const [slot, configs] of Object.entries(contributes)) {
|
|
7698
|
-
if (!configs || configs.length === 0) continue;
|
|
7756
|
+
if (!Array.isArray(configs) || configs.length === 0) continue;
|
|
7699
7757
|
if (!this.contributions.has(slot)) {
|
|
7700
7758
|
this.contributions.set(slot, /* @__PURE__ */ new Map());
|
|
7701
7759
|
}
|
|
@@ -8057,7 +8115,7 @@ var PluginRuntimeComposition = class {
|
|
|
8057
8115
|
this.workerManager = workerManager;
|
|
8058
8116
|
this.id = "srv_plugin_runtime_composition";
|
|
8059
8117
|
this.name = "PluginRuntimeComposition";
|
|
8060
|
-
this.version = "2.5
|
|
8118
|
+
this.version = "0.2.5";
|
|
8061
8119
|
this._isStarted = false;
|
|
8062
8120
|
}
|
|
8063
8121
|
get isStarted() {
|
|
@@ -8111,7 +8169,7 @@ var PluginLifecycleManager = class {
|
|
|
8111
8169
|
this.pluginHost = pluginHost;
|
|
8112
8170
|
this.id = "srv_plugin_lifecycle_manager";
|
|
8113
8171
|
this.name = "PluginLifecycleManager";
|
|
8114
|
-
this.version = "2.5
|
|
8172
|
+
this.version = "0.2.5";
|
|
8115
8173
|
}
|
|
8116
8174
|
getPluginState(pluginId) {
|
|
8117
8175
|
return this.pluginHost.getPluginState(pluginId);
|
|
@@ -8158,7 +8216,7 @@ var PluginCapabilityGateway = class {
|
|
|
8158
8216
|
this.capabilityRegistry = capabilityRegistry;
|
|
8159
8217
|
this.id = "srv_plugin_capability_gateway";
|
|
8160
8218
|
this.name = "PluginCapabilityGateway";
|
|
8161
|
-
this.version = "2.5
|
|
8219
|
+
this.version = "0.2.5";
|
|
8162
8220
|
}
|
|
8163
8221
|
listCapabilities() {
|
|
8164
8222
|
const rawCaps = this.capabilityRegistry.listCapabilities();
|
|
@@ -8212,7 +8270,7 @@ var UnifiedExtensionRegistry = class {
|
|
|
8212
8270
|
constructor() {
|
|
8213
8271
|
this.id = "srv_unified_extension_registry";
|
|
8214
8272
|
this.name = "UnifiedExtensionRegistry";
|
|
8215
|
-
this.version = "2.5
|
|
8273
|
+
this.version = "0.2.5";
|
|
8216
8274
|
this._extensions = /* @__PURE__ */ new Map();
|
|
8217
8275
|
}
|
|
8218
8276
|
registerExtension(category, id, impl, meta) {
|
|
@@ -8317,7 +8375,7 @@ var PluginDistributionManager = class {
|
|
|
8317
8375
|
this.pluginHost = pluginHost;
|
|
8318
8376
|
this.id = "srv_plugin_distribution_manager";
|
|
8319
8377
|
this.name = "PluginDistributionManager";
|
|
8320
|
-
this.version = "2.5
|
|
8378
|
+
this.version = "0.2.5";
|
|
8321
8379
|
this._repositories = /* @__PURE__ */ new Map();
|
|
8322
8380
|
}
|
|
8323
8381
|
registerRepository(repo) {
|
|
@@ -8406,7 +8464,7 @@ function validatePluginStateTransition(currentState, nextState, pluginId) {
|
|
|
8406
8464
|
}
|
|
8407
8465
|
var ACTIVATION_TIMEOUT_MS = 5e3;
|
|
8408
8466
|
var DEACTIVATION_TIMEOUT_MS = 5e3;
|
|
8409
|
-
var OPENLEARN_VERSION = "2.5
|
|
8467
|
+
var OPENLEARN_VERSION = "0.2.5";
|
|
8410
8468
|
var PluginHost = class {
|
|
8411
8469
|
constructor(serviceRegistry, esmLoader, db2, pluginsDir) {
|
|
8412
8470
|
this.serviceRegistry = serviceRegistry;
|
|
@@ -10431,7 +10489,7 @@ var HotReloadController = class {
|
|
|
10431
10489
|
}
|
|
10432
10490
|
if (this.enabled) return;
|
|
10433
10491
|
this.enabled = true;
|
|
10434
|
-
await this.fileWatcher.startWatch(this.watchDir, (event) => {
|
|
10492
|
+
await this.fileWatcher.startWatch(this.watchDir, async (event) => {
|
|
10435
10493
|
this.handleFileChange(event.pluginId, event.filePath);
|
|
10436
10494
|
});
|
|
10437
10495
|
console.log("[HotReload] Hot reload enabled \u2014 watching for plugin changes");
|
|
@@ -16020,7 +16078,7 @@ var StartupTimeoutError = class extends PlatformBootstrapError {
|
|
|
16020
16078
|
this.name = "StartupTimeoutError";
|
|
16021
16079
|
}
|
|
16022
16080
|
};
|
|
16023
|
-
var PLATFORM_VERSION = "2.5
|
|
16081
|
+
var PLATFORM_VERSION = "0.2.5";
|
|
16024
16082
|
var BOOTSTRAP_STAGE_NAMES = Object.freeze([
|
|
16025
16083
|
"Created" /* Created */,
|
|
16026
16084
|
"Configuring" /* Configuring */,
|
|
@@ -16722,10 +16780,10 @@ var PluginCompositionModule = class {
|
|
|
16722
16780
|
const realPluginHost = options?.infrastructureRefs?.get("pluginHost") ?? { name: "PluginHostService", isReady: true, pluginsCount: 0 };
|
|
16723
16781
|
const realContributionRegistry = options?.infrastructureRefs?.get("contributionRegistry") ?? { name: "ContributionRegistry", slotsCount: 0 };
|
|
16724
16782
|
const realRuntimeComposition = options?.infrastructureRefs?.get("runtimeComposition") ?? { name: "PluginRuntimeComposition", isStarted: true };
|
|
16725
|
-
const realLifecycleManager = options?.infrastructureRefs?.get("lifecycleManager") ?? { name: "PluginLifecycleManager", version: "2.5
|
|
16726
|
-
const realCapabilityGateway = options?.infrastructureRefs?.get("capabilityGateway") ?? { name: "PluginCapabilityGateway", version: "2.5
|
|
16727
|
-
const realExtensionRegistry = options?.infrastructureRefs?.get("extensionRegistry") ?? { name: "UnifiedExtensionRegistry", version: "2.5
|
|
16728
|
-
const realDistributionManager = options?.infrastructureRefs?.get("distributionManager") ?? { name: "PluginDistributionManager", version: "2.5
|
|
16783
|
+
const realLifecycleManager = options?.infrastructureRefs?.get("lifecycleManager") ?? { name: "PluginLifecycleManager", version: "0.2.5" };
|
|
16784
|
+
const realCapabilityGateway = options?.infrastructureRefs?.get("capabilityGateway") ?? { name: "PluginCapabilityGateway", version: "0.2.5" };
|
|
16785
|
+
const realExtensionRegistry = options?.infrastructureRefs?.get("extensionRegistry") ?? { name: "UnifiedExtensionRegistry", version: "0.2.5" };
|
|
16786
|
+
const realDistributionManager = options?.infrastructureRefs?.get("distributionManager") ?? { name: "PluginDistributionManager", version: "0.2.5" };
|
|
16729
16787
|
registerOrReplace({
|
|
16730
16788
|
id: "srv_plugin_host",
|
|
16731
16789
|
lifetime: "Singleton",
|
|
@@ -16893,7 +16951,7 @@ var Kernel = class {
|
|
|
16893
16951
|
throw new Error(`[PayloadValidationError] Invalid command payload for ${command.type}: ${errors.join("; ")}`);
|
|
16894
16952
|
}
|
|
16895
16953
|
}
|
|
16896
|
-
const isAdmin = command.actorId === "role:administrator" || command.actorId === "admin" || command.actorId === "usr_admin" || command.actorId === "admin-demo" || command.actorId?.endsWith(":administrator") || command.actorId?.endsWith(":admin")
|
|
16954
|
+
const isAdmin = command.actorId === "role:administrator" || command.actorId === "admin" || command.actorId === "usr_admin" || command.actorId === "admin-demo" || command.actorId?.endsWith(":administrator") || command.actorId?.endsWith(":admin");
|
|
16897
16955
|
if (action.capabilityRequired && !isAdmin) {
|
|
16898
16956
|
const allowed = this.capabilityGuard.check(command.actorId, action.capabilityRequired);
|
|
16899
16957
|
if (!allowed) {
|
|
@@ -18367,10 +18425,10 @@ function getCookieToken(req) {
|
|
|
18367
18425
|
}
|
|
18368
18426
|
function getActorId(req) {
|
|
18369
18427
|
const token = getCookieToken(req);
|
|
18370
|
-
if (!token) return "
|
|
18428
|
+
if (!token) return "anonymous";
|
|
18371
18429
|
try {
|
|
18372
18430
|
const session = getValidSession(token);
|
|
18373
|
-
if (!session) return "
|
|
18431
|
+
if (!session) return "anonymous";
|
|
18374
18432
|
let role = session.subRole || session.role;
|
|
18375
18433
|
if (session.username === "admin" || session.userId === "usr_admin" || role === "admin" || role === "administrator") {
|
|
18376
18434
|
role = "administrator";
|
|
@@ -18378,11 +18436,31 @@ function getActorId(req) {
|
|
|
18378
18436
|
if (role) {
|
|
18379
18437
|
return `user:${session.userId || "demo"}:${role}`;
|
|
18380
18438
|
}
|
|
18381
|
-
return "
|
|
18439
|
+
return "anonymous";
|
|
18382
18440
|
} catch {
|
|
18383
|
-
return "
|
|
18441
|
+
return "anonymous";
|
|
18384
18442
|
}
|
|
18385
18443
|
}
|
|
18444
|
+
function requireAuth(...roles) {
|
|
18445
|
+
return (req, res, next) => {
|
|
18446
|
+
const token = getCookieToken(req);
|
|
18447
|
+
if (!token) return res.status(401).json({ success: false, error: "Authentication required" });
|
|
18448
|
+
const session = getValidSession(token);
|
|
18449
|
+
if (!session) return res.status(401).json({ success: false, error: "Session expired or invalid" });
|
|
18450
|
+
if (roles.length > 0) {
|
|
18451
|
+
let userRole = session.role;
|
|
18452
|
+
if (session.username === "admin" || session.userId === "usr_admin" || userRole === "admin") {
|
|
18453
|
+
userRole = "administrator";
|
|
18454
|
+
}
|
|
18455
|
+
const effectiveRoles = roles.map((r) => r === "admin" ? "administrator" : r);
|
|
18456
|
+
if (!effectiveRoles.includes(userRole)) {
|
|
18457
|
+
return res.status(403).json({ success: false, error: `Role ${session.role} not allowed. Required: ${roles.join(", ")}` });
|
|
18458
|
+
}
|
|
18459
|
+
}
|
|
18460
|
+
req.session = session;
|
|
18461
|
+
next();
|
|
18462
|
+
};
|
|
18463
|
+
}
|
|
18386
18464
|
function checkIsTeacherOrAdmin(req) {
|
|
18387
18465
|
const token = getCookieToken(req);
|
|
18388
18466
|
if (!token) return false;
|
|
@@ -19143,7 +19221,7 @@ function registerOsRoutes(ctx) {
|
|
|
19143
19221
|
res.status(500).json({ error: e.message });
|
|
19144
19222
|
}
|
|
19145
19223
|
});
|
|
19146
|
-
app.post("/api/commands", async (req, res) => {
|
|
19224
|
+
app.post("/api/commands", requireAuth(), async (req, res) => {
|
|
19147
19225
|
try {
|
|
19148
19226
|
const { commandType, payload } = req.body;
|
|
19149
19227
|
const cmd = kernelContainer.commandBus.createCommand(
|
|
@@ -20860,7 +20938,7 @@ function registerAdminRoutes(ctx) {
|
|
|
20860
20938
|
runGeminiAgentChat: runGeminiAgentChat2,
|
|
20861
20939
|
runOpenAIAgentChat: runOpenAIAgentChat2
|
|
20862
20940
|
} = ctx;
|
|
20863
|
-
app.post("/api/admin/seed-demo", (req, res) => {
|
|
20941
|
+
app.post("/api/admin/seed-demo", requireAuth("administrator"), (req, res) => {
|
|
20864
20942
|
try {
|
|
20865
20943
|
const db2 = kernelContainer.db;
|
|
20866
20944
|
const DEMO_CLASS_ID = "demo-class";
|
|
@@ -20876,7 +20954,7 @@ function registerAdminRoutes(ctx) {
|
|
|
20876
20954
|
db2.prepare("INSERT INTO classes (id, name, description, created_at) VALUES (?, ?, ?, ?)").run(
|
|
20877
20955
|
DEMO_CLASS_ID,
|
|
20878
20956
|
"\u4EBA\u5DE5\u667A\u80FD\u4E0E\u521B\u610F\u7F16\u7A0B\u793A\u8303\u73ED",
|
|
20879
|
-
"\u8FD9\u662F\u7CFB\u7EDF\u521D\u59CB\u5316\u7684\u793A\u4F8B\u8BFE\u7A0B\u73ED\u7EA7\uFF0C\u7528\u4E8E\u6559\u5B66\u4F53\u9A8C
|
|
20957
|
+
"\u8FD9\u662F\u7CFB\u7EDF\u521D\u59CB\u5316\u7684\u793A\u4F8B\u8BFE\u7A0B\u73ED\u7EA7\uFF0C\u7528\u4E8E\u6559\u5B66\u4F53\u9A8C?",
|
|
20880
20958
|
Date.now()
|
|
20881
20959
|
);
|
|
20882
20960
|
}
|
|
@@ -20904,7 +20982,7 @@ function registerAdminRoutes(ctx) {
|
|
|
20904
20982
|
lessonId = "demo-lesson";
|
|
20905
20983
|
db2.prepare("INSERT INTO lessons (id, title, content, created_at, updated_at) VALUES (?, ?, ?, ?, ?)").run(
|
|
20906
20984
|
lessonId,
|
|
20907
|
-
"\u521D\u8BC6 Python\uFF1A\u667A\u80FD\u767D\u677F\u521B\u610F\u7F16
|
|
20985
|
+
"\u521D\u8BC6 Python\uFF1A\u667A\u80FD\u767D\u677F\u521B\u610F\u7F16?",
|
|
20908
20986
|
JSON.stringify({ elements: [] }),
|
|
20909
20987
|
Date.now(),
|
|
20910
20988
|
Date.now()
|
|
@@ -20926,7 +21004,7 @@ function registerAdminRoutes(ctx) {
|
|
|
20926
21004
|
res.status(500).json({ error: e.message });
|
|
20927
21005
|
}
|
|
20928
21006
|
});
|
|
20929
|
-
app.post("/api/classes/import", (req, res) => {
|
|
21007
|
+
app.post("/api/classes/import", requireAuth("teacher", "administrator"), (req, res) => {
|
|
20930
21008
|
try {
|
|
20931
21009
|
const { classes } = req.body;
|
|
20932
21010
|
if (!classes || !Array.isArray(classes)) {
|
|
@@ -20976,7 +21054,7 @@ function registerAdminRoutes(ctx) {
|
|
|
20976
21054
|
res.status(500).json({ error: e.message });
|
|
20977
21055
|
}
|
|
20978
21056
|
});
|
|
20979
|
-
app.post("/api/students/import", (req, res) => {
|
|
21057
|
+
app.post("/api/students/import", requireAuth("teacher", "administrator"), (req, res) => {
|
|
20980
21058
|
try {
|
|
20981
21059
|
const { students } = req.body;
|
|
20982
21060
|
if (!students || !Array.isArray(students)) {
|
|
@@ -21575,16 +21653,16 @@ function registerRosterRoutes(ctx) {
|
|
|
21575
21653
|
res.status(550).json({ error: e.message });
|
|
21576
21654
|
}
|
|
21577
21655
|
});
|
|
21578
|
-
app.get("/api/users", async (req, res) => {
|
|
21656
|
+
app.get("/api/users", requireAuth("administrator"), async (req, res) => {
|
|
21579
21657
|
try {
|
|
21580
|
-
const cmd = kernelContainer.commandBus.createCommand("user.list", {},
|
|
21658
|
+
const cmd = kernelContainer.commandBus.createCommand("user.list", {}, getActorId(req));
|
|
21581
21659
|
const users = await kernelContainer.commandBus.execute(cmd);
|
|
21582
21660
|
res.json(users);
|
|
21583
21661
|
} catch (e) {
|
|
21584
21662
|
res.status(550).json({ error: e.message });
|
|
21585
21663
|
}
|
|
21586
21664
|
});
|
|
21587
|
-
app.post("/api/users", async (req, res) => {
|
|
21665
|
+
app.post("/api/users", requireAuth("administrator"), async (req, res) => {
|
|
21588
21666
|
try {
|
|
21589
21667
|
const { username, password, role, name, status = "active" } = req.body;
|
|
21590
21668
|
const cmd = kernelContainer.commandBus.createCommand("user.create", {
|
|
@@ -21593,14 +21671,14 @@ function registerRosterRoutes(ctx) {
|
|
|
21593
21671
|
role,
|
|
21594
21672
|
name,
|
|
21595
21673
|
status
|
|
21596
|
-
},
|
|
21674
|
+
}, getActorId(req));
|
|
21597
21675
|
const result = await kernelContainer.commandBus.execute(cmd);
|
|
21598
21676
|
res.json(result);
|
|
21599
21677
|
} catch (e) {
|
|
21600
21678
|
res.status(550).json({ error: e.message });
|
|
21601
21679
|
}
|
|
21602
21680
|
});
|
|
21603
|
-
app.put("/api/users/:id", async (req, res) => {
|
|
21681
|
+
app.put("/api/users/:id", requireAuth("administrator"), async (req, res) => {
|
|
21604
21682
|
try {
|
|
21605
21683
|
const { username, role, name, password, status } = req.body;
|
|
21606
21684
|
const cmd = kernelContainer.commandBus.createCommand("user.update", {
|
|
@@ -21610,18 +21688,18 @@ function registerRosterRoutes(ctx) {
|
|
|
21610
21688
|
name,
|
|
21611
21689
|
password,
|
|
21612
21690
|
status
|
|
21613
|
-
},
|
|
21691
|
+
}, getActorId(req));
|
|
21614
21692
|
const result = await kernelContainer.commandBus.execute(cmd);
|
|
21615
21693
|
res.json(result);
|
|
21616
21694
|
} catch (e) {
|
|
21617
21695
|
res.status(550).json({ error: e.message });
|
|
21618
21696
|
}
|
|
21619
21697
|
});
|
|
21620
|
-
app.delete("/api/users/:id", async (req, res) => {
|
|
21698
|
+
app.delete("/api/users/:id", requireAuth("administrator"), async (req, res) => {
|
|
21621
21699
|
try {
|
|
21622
21700
|
const cmd = kernelContainer.commandBus.createCommand("user.delete", {
|
|
21623
21701
|
userId: req.params.id
|
|
21624
|
-
},
|
|
21702
|
+
}, getActorId(req));
|
|
21625
21703
|
const result = await kernelContainer.commandBus.execute(cmd);
|
|
21626
21704
|
res.json(result);
|
|
21627
21705
|
} catch (e) {
|
|
@@ -21636,7 +21714,7 @@ function registerRosterRoutes(ctx) {
|
|
|
21636
21714
|
res.status(500).json({ error: e.message });
|
|
21637
21715
|
}
|
|
21638
21716
|
});
|
|
21639
|
-
app.post("/api/labs", (req, res) => {
|
|
21717
|
+
app.post("/api/labs", requireAuth("teacher", "administrator"), (req, res) => {
|
|
21640
21718
|
try {
|
|
21641
21719
|
const { room_number, rows, cols } = req.body;
|
|
21642
21720
|
const id = "lab_" + Math.random().toString(36).slice(2, 10);
|
|
@@ -21648,7 +21726,7 @@ function registerRosterRoutes(ctx) {
|
|
|
21648
21726
|
res.status(500).json({ error: e.message });
|
|
21649
21727
|
}
|
|
21650
21728
|
});
|
|
21651
|
-
app.put("/api/labs/:id", (req, res) => {
|
|
21729
|
+
app.put("/api/labs/:id", requireAuth("teacher", "administrator"), (req, res) => {
|
|
21652
21730
|
try {
|
|
21653
21731
|
const { room_number, rows, cols } = req.body;
|
|
21654
21732
|
kernelContainer.db.prepare(
|
|
@@ -21659,7 +21737,7 @@ function registerRosterRoutes(ctx) {
|
|
|
21659
21737
|
res.status(500).json({ error: e.message });
|
|
21660
21738
|
}
|
|
21661
21739
|
});
|
|
21662
|
-
app.delete("/api/labs/:id", (req, res) => {
|
|
21740
|
+
app.delete("/api/labs/:id", requireAuth("teacher", "administrator"), (req, res) => {
|
|
21663
21741
|
try {
|
|
21664
21742
|
kernelContainer.db.prepare("DELETE FROM computer_labs WHERE id = ?").run(req.params.id);
|
|
21665
21743
|
kernelContainer.db.prepare("DELETE FROM student_seats WHERE lab_id = ?").run(req.params.id);
|
|
@@ -21678,7 +21756,7 @@ function registerRosterRoutes(ctx) {
|
|
|
21678
21756
|
res.status(500).json({ error: e.message });
|
|
21679
21757
|
}
|
|
21680
21758
|
});
|
|
21681
|
-
app.post("/api/classes/:classId/seats", (req, res) => {
|
|
21759
|
+
app.post("/api/classes/:classId/seats", requireAuth("teacher", "administrator"), (req, res) => {
|
|
21682
21760
|
try {
|
|
21683
21761
|
const { lab_id, seats } = req.body;
|
|
21684
21762
|
kernelContainer.db.prepare("UPDATE classes SET lab_id = ? WHERE id = ?").run(lab_id || null, req.params.classId);
|
|
@@ -21876,7 +21954,7 @@ function registerRosterRoutes(ctx) {
|
|
|
21876
21954
|
res.status(500).json({ error: e.message });
|
|
21877
21955
|
}
|
|
21878
21956
|
});
|
|
21879
|
-
app.post("/api/classes/:id/students/bulk-enroll", (req, res) => {
|
|
21957
|
+
app.post("/api/classes/:id/students/bulk-enroll", requireAuth("teacher", "administrator"), (req, res) => {
|
|
21880
21958
|
try {
|
|
21881
21959
|
const { students } = req.body;
|
|
21882
21960
|
const classId = req.params.id;
|
|
@@ -23058,7 +23136,7 @@ function registerGradingRoutes(ctx) {
|
|
|
23058
23136
|
if (!lessonId || !studentId || grade === void 0) {
|
|
23059
23137
|
return res.status(400).json({ success: false, error: "lessonId, studentId, and grade are required" });
|
|
23060
23138
|
}
|
|
23061
|
-
const gradeService = await kernelContainer.
|
|
23139
|
+
const gradeService = await kernelContainer.serviceRegistry.resolve(ISemesterGradeServiceToken);
|
|
23062
23140
|
await gradeService.saveSemesterGrade(lessonId, studentId, Math.round(Number(grade)));
|
|
23063
23141
|
res.json({ success: true });
|
|
23064
23142
|
} catch (e) {
|
|
@@ -23067,7 +23145,7 @@ function registerGradingRoutes(ctx) {
|
|
|
23067
23145
|
});
|
|
23068
23146
|
app.get("/api/classes/:classId/points-dimensions", async (req, res) => {
|
|
23069
23147
|
try {
|
|
23070
|
-
const dimensionRegistry = await kernelContainer.
|
|
23148
|
+
const dimensionRegistry = await kernelContainer.serviceRegistry.resolve(IPointsDimensionRegistryToken);
|
|
23071
23149
|
const dimensions = dimensionRegistry.listDimensions();
|
|
23072
23150
|
res.json({ success: true, dimensions });
|
|
23073
23151
|
} catch (e) {
|
|
@@ -23084,7 +23162,7 @@ function registerGradingRoutes(ctx) {
|
|
|
23084
23162
|
if (!classId || !dimensionId || deltaPoints === void 0 || !reason) {
|
|
23085
23163
|
return res.status(400).json({ success: false, error: "classId, dimensionId, deltaPoints, and reason are required" });
|
|
23086
23164
|
}
|
|
23087
|
-
const ledgerService = await kernelContainer.
|
|
23165
|
+
const ledgerService = await kernelContainer.serviceRegistry.resolve(IPointsLedgerServiceToken);
|
|
23088
23166
|
const logItem = await ledgerService.addPoints(studentId, classId, dimensionId, Number(deltaPoints), reason, pluginId);
|
|
23089
23167
|
kernelContainer.eventBus.publish({
|
|
23090
23168
|
id: logItem.id,
|
|
@@ -23102,7 +23180,7 @@ function registerGradingRoutes(ctx) {
|
|
|
23102
23180
|
try {
|
|
23103
23181
|
const { studentId } = req.params;
|
|
23104
23182
|
const { classId } = req.query;
|
|
23105
|
-
const ledgerService = await kernelContainer.
|
|
23183
|
+
const ledgerService = await kernelContainer.serviceRegistry.resolve(IPointsLedgerServiceToken);
|
|
23106
23184
|
const logs = await ledgerService.getLogs(studentId, classId);
|
|
23107
23185
|
const summary = await ledgerService.getStudentDimensionSummary(studentId, classId || "");
|
|
23108
23186
|
res.json({ success: true, logs, summary });
|
|
@@ -23421,6 +23499,28 @@ async function checkVersion(source, installedVersion) {
|
|
|
23421
23499
|
}
|
|
23422
23500
|
|
|
23423
23501
|
// server/routes/plugins.ts
|
|
23502
|
+
function isSafeExternalUrl(urlStr) {
|
|
23503
|
+
try {
|
|
23504
|
+
const parsed = new URL(urlStr);
|
|
23505
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
23506
|
+
return { safe: false, reason: "Only HTTP and HTTPS protocols are allowed" };
|
|
23507
|
+
}
|
|
23508
|
+
const hostname = parsed.hostname.toLowerCase();
|
|
23509
|
+
if (hostname === "localhost" || hostname.endsWith(".localhost") || hostname.endsWith(".local") || hostname === "0.0.0.0" || hostname === "::1" || hostname === "[::1]") {
|
|
23510
|
+
return { safe: false, reason: "Access to loopback/local addresses is forbidden" };
|
|
23511
|
+
}
|
|
23512
|
+
const ipv4Match = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
|
|
23513
|
+
if (ipv4Match) {
|
|
23514
|
+
const octets = ipv4Match.slice(1).map(Number);
|
|
23515
|
+
if (octets[0] === 127 || octets[0] === 10 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168 || octets[0] === 169 && octets[1] === 254 || octets[0] === 0 || octets[0] >= 224) {
|
|
23516
|
+
return { safe: false, reason: "Access to private or link-local IP addresses is forbidden" };
|
|
23517
|
+
}
|
|
23518
|
+
}
|
|
23519
|
+
return { safe: true };
|
|
23520
|
+
} catch (e) {
|
|
23521
|
+
return { safe: false, reason: `Invalid URL format: ${e.message}` };
|
|
23522
|
+
}
|
|
23523
|
+
}
|
|
23424
23524
|
function registerPluginsRoutes(ctx) {
|
|
23425
23525
|
const {
|
|
23426
23526
|
app,
|
|
@@ -23568,12 +23668,19 @@ function registerPluginsRoutes(ctx) {
|
|
|
23568
23668
|
res.status(500).json({ success: false, error: e.message });
|
|
23569
23669
|
}
|
|
23570
23670
|
});
|
|
23571
|
-
app.post("/api/plugins/:id(*)/one-click-update", async (req, res) => {
|
|
23671
|
+
app.post("/api/plugins/:id(*)/one-click-update", requireAuth("administrator"), async (req, res) => {
|
|
23572
23672
|
try {
|
|
23573
23673
|
const targetPluginId = decodeURIComponent(req.params.id);
|
|
23574
23674
|
const { downloadUrl } = req.body || {};
|
|
23575
23675
|
let zipBuffer;
|
|
23576
23676
|
if (downloadUrl) {
|
|
23677
|
+
const urlCheck = isSafeExternalUrl(downloadUrl);
|
|
23678
|
+
if (!urlCheck.safe) {
|
|
23679
|
+
return res.status(400).json({
|
|
23680
|
+
success: false,
|
|
23681
|
+
error: `\u5B89\u5168\u62E6\u622A: \u975E\u6CD5\u4E0B\u8F7D\u5730\u5740 (${urlCheck.reason})`
|
|
23682
|
+
});
|
|
23683
|
+
}
|
|
23577
23684
|
try {
|
|
23578
23685
|
const resp = await fetch(downloadUrl, {
|
|
23579
23686
|
headers: { "User-Agent": "OpenLearnV2-PluginUpdater/1.0" },
|
|
@@ -23651,7 +23758,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23651
23758
|
res.status(500).json({ success: false, error: e.message });
|
|
23652
23759
|
}
|
|
23653
23760
|
});
|
|
23654
|
-
app.post("/api/plugins/:id(*)/config", (req, res) => {
|
|
23761
|
+
app.post("/api/plugins/:id(*)/config", requireAuth("administrator"), (req, res) => {
|
|
23655
23762
|
try {
|
|
23656
23763
|
const rawId = decodeURIComponent(req.params.id);
|
|
23657
23764
|
const pluginId = kernelContainer.pluginHost.resolvePluginUuid(rawId);
|
|
@@ -23670,7 +23777,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23670
23777
|
res.status(500).json({ success: false, error: e.message });
|
|
23671
23778
|
}
|
|
23672
23779
|
});
|
|
23673
|
-
app.post("/api/plugins/:id(*)/toggle", async (req, res) => {
|
|
23780
|
+
app.post("/api/plugins/:id(*)/toggle", requireAuth("administrator"), async (req, res) => {
|
|
23674
23781
|
try {
|
|
23675
23782
|
const rawId = decodeURIComponent(req.params.id);
|
|
23676
23783
|
const cmd = kernelContainer.commandBus.createCommand(
|
|
@@ -23684,7 +23791,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23684
23791
|
res.status(500).json({ success: false, error: err.message });
|
|
23685
23792
|
}
|
|
23686
23793
|
});
|
|
23687
|
-
app.delete("/api/plugins/:id(*)", async (req, res) => {
|
|
23794
|
+
app.delete("/api/plugins/:id(*)", requireAuth("administrator"), async (req, res) => {
|
|
23688
23795
|
try {
|
|
23689
23796
|
const rawId = decodeURIComponent(req.params.id);
|
|
23690
23797
|
const cmd = kernelContainer.commandBus.createCommand(
|
|
@@ -23712,7 +23819,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23712
23819
|
res.status(404).json({ success: false, error: err.message });
|
|
23713
23820
|
}
|
|
23714
23821
|
});
|
|
23715
|
-
app.post("/api/plugins", async (req, res) => {
|
|
23822
|
+
app.post("/api/plugins", requireAuth("administrator"), async (req, res) => {
|
|
23716
23823
|
try {
|
|
23717
23824
|
const { sourceCode } = req.body;
|
|
23718
23825
|
const cmd = kernelContainer.commandBus.createCommand(
|
|
@@ -23727,7 +23834,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23727
23834
|
res.status(500).json({ success: false, error: err.message });
|
|
23728
23835
|
}
|
|
23729
23836
|
});
|
|
23730
|
-
app.post("/api/plugins/upload-zip", async (req, res) => {
|
|
23837
|
+
app.post("/api/plugins/upload-zip", requireAuth("administrator"), async (req, res) => {
|
|
23731
23838
|
try {
|
|
23732
23839
|
const { base64Data, filename, executionMode } = req.body;
|
|
23733
23840
|
const cmd = kernelContainer.commandBus.createCommand(
|
|
@@ -23742,7 +23849,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23742
23849
|
res.status(500).json({ success: false, error: err.message });
|
|
23743
23850
|
}
|
|
23744
23851
|
});
|
|
23745
|
-
app.post("/api/plugins/upload-zip-raw", import_express2.default.raw({ type: "application/octet-stream", limit: "400mb" }), async (req, res) => {
|
|
23852
|
+
app.post("/api/plugins/upload-zip-raw", requireAuth("administrator"), import_express2.default.raw({ type: "application/octet-stream", limit: "400mb" }), async (req, res) => {
|
|
23746
23853
|
try {
|
|
23747
23854
|
const zipBuffer = req.body;
|
|
23748
23855
|
const filename = req.headers["x-filename"] ? decodeURIComponent(req.headers["x-filename"]) : "plugin.zip";
|
|
@@ -23786,6 +23893,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23786
23893
|
});
|
|
23787
23894
|
app.post(
|
|
23788
23895
|
"/api/plugins/:id(*)/update-zip-raw",
|
|
23896
|
+
requireAuth("administrator"),
|
|
23789
23897
|
import_express2.default.raw({ type: "application/octet-stream", limit: "400mb" }),
|
|
23790
23898
|
async (req, res) => {
|
|
23791
23899
|
try {
|
|
@@ -23872,7 +23980,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23872
23980
|
res.status(500).json({ error: e.message });
|
|
23873
23981
|
}
|
|
23874
23982
|
});
|
|
23875
|
-
app.post("/api/ai-providers", (req, res) => {
|
|
23983
|
+
app.post("/api/ai-providers", requireAuth("administrator"), (req, res) => {
|
|
23876
23984
|
try {
|
|
23877
23985
|
const { name, api_url, api_key, model_name } = req.body;
|
|
23878
23986
|
if (!name || !api_url || !model_name) {
|
|
@@ -23887,7 +23995,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23887
23995
|
res.status(500).json({ error: e.message });
|
|
23888
23996
|
}
|
|
23889
23997
|
});
|
|
23890
|
-
app.put("/api/ai-providers/:id", (req, res) => {
|
|
23998
|
+
app.put("/api/ai-providers/:id", requireAuth("administrator"), (req, res) => {
|
|
23891
23999
|
try {
|
|
23892
24000
|
const { name, api_url, api_key, model_name } = req.body;
|
|
23893
24001
|
if (!name || !api_url || !model_name) {
|
|
@@ -23907,7 +24015,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23907
24015
|
res.status(500).json({ error: e.message });
|
|
23908
24016
|
}
|
|
23909
24017
|
});
|
|
23910
|
-
app.delete("/api/ai-providers/:id", (req, res) => {
|
|
24018
|
+
app.delete("/api/ai-providers/:id", requireAuth("administrator"), (req, res) => {
|
|
23911
24019
|
try {
|
|
23912
24020
|
kernelContainer.db.prepare("DELETE FROM ai_providers WHERE id = ?").run(req.params.id);
|
|
23913
24021
|
res.json({ success: true });
|
|
@@ -23927,7 +24035,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23927
24035
|
res.status(500).json({ error: e.message });
|
|
23928
24036
|
}
|
|
23929
24037
|
});
|
|
23930
|
-
app.put("/api/site-settings", (req, res) => {
|
|
24038
|
+
app.put("/api/site-settings", requireAuth("administrator"), (req, res) => {
|
|
23931
24039
|
try {
|
|
23932
24040
|
const { siteName, slogan, logoUrl } = req.body || {};
|
|
23933
24041
|
kernelContainer.db.prepare(
|
|
@@ -23942,7 +24050,7 @@ function registerPluginsRoutes(ctx) {
|
|
|
23942
24050
|
res.status(500).json({ error: e.message });
|
|
23943
24051
|
}
|
|
23944
24052
|
});
|
|
23945
|
-
app.post("/api/ai-providers/test", async (req, res) => {
|
|
24053
|
+
app.post("/api/ai-providers/test", requireAuth("administrator"), async (req, res) => {
|
|
23946
24054
|
try {
|
|
23947
24055
|
const { api_url, api_key: providedKey, model_name } = req.body;
|
|
23948
24056
|
if (!api_url || !model_name) {
|
|
@@ -23963,6 +24071,10 @@ function registerPluginsRoutes(ctx) {
|
|
|
23963
24071
|
if (!cleanUrl.endsWith("/chat/completions")) {
|
|
23964
24072
|
cleanUrl = cleanUrl.endsWith("/") ? cleanUrl + "chat/completions" : cleanUrl + "/chat/completions";
|
|
23965
24073
|
}
|
|
24074
|
+
const urlSafety = isSafeExternalUrl(cleanUrl);
|
|
24075
|
+
if (!urlSafety.safe) {
|
|
24076
|
+
return res.status(400).json({ error: `Blocked unsafe AI provider URL: ${urlSafety.reason}` });
|
|
24077
|
+
}
|
|
23966
24078
|
const response = await fetch(cleanUrl, {
|
|
23967
24079
|
method: "POST",
|
|
23968
24080
|
headers: {
|
|
@@ -24043,8 +24155,8 @@ async function startServer() {
|
|
|
24043
24155
|
standardHeaders: true,
|
|
24044
24156
|
legacyHeaders: false
|
|
24045
24157
|
});
|
|
24046
|
-
app.use(import_express3.default.json({ limit: "
|
|
24047
|
-
app.use(import_express3.default.urlencoded({ limit: "
|
|
24158
|
+
app.use(import_express3.default.json({ limit: "10mb" }));
|
|
24159
|
+
app.use(import_express3.default.urlencoded({ limit: "10mb", extended: true }));
|
|
24048
24160
|
app.use("/uploads", import_express3.default.static(import_path14.default.join(process.cwd(), "uploads")));
|
|
24049
24161
|
app.use("/plugins", import_express3.default.static(import_path14.default.join(process.cwd(), "plugins")));
|
|
24050
24162
|
app.use((req, res, next) => {
|