openlearn-next 0.3.13 → 0.3.15

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 (31) hide show
  1. package/dist/assets/{BatchPickerModal-Cz10EQIx.js → BatchPickerModal-DrQn8KfF.js} +1 -1
  2. package/dist/assets/{ClassesView-B5mQUqdu.js → ClassesView-NzXmBc5D.js} +1 -1
  3. package/dist/assets/{CloudDriveModal-DVgJpq73.js → CloudDriveModal-CkBmfYLe.js} +1 -1
  4. package/dist/assets/{CourseManagement-CfyqBe9O.js → CourseManagement-D0nfQuUO.js} +1 -1
  5. package/dist/assets/{CourseWizardModal-DgGqaOBb.js → CourseWizardModal-Sf71a02x.js} +1 -1
  6. package/dist/assets/{Dashboard-CVLDc2Ra.js → Dashboard-BYLfPux5.js} +1 -1
  7. package/dist/assets/{ExportWeightModal-BPzBJ5QA.js → ExportWeightModal-uMQTJEn3.js} +1 -1
  8. package/dist/assets/{HelpView-D7FDc6f2.js → HelpView-iOEvT7B3.js} +1 -1
  9. package/dist/assets/{ImportLessonsModal-CsNg6pUL.js → ImportLessonsModal-DRuFaGKs.js} +1 -1
  10. package/dist/assets/{InteractiveCoursewareViewer-CWBrLReA.js → InteractiveCoursewareViewer-GAUCqBpg.js} +1 -1
  11. package/dist/assets/{InteractiveWhiteboard-Jjkl4Ayz.js → InteractiveWhiteboard-SnBmwJaO.js} +1 -1
  12. package/dist/assets/{LazyCourseware-C1Iw9I5f.js → LazyCourseware-CICx7ccM.js} +1 -1
  13. package/dist/assets/{LazyWhiteboard-DumYte7H.js → LazyWhiteboard-Dy3pdZ5v.js} +2 -2
  14. package/dist/assets/{LessonEditorView-CheUoLYL.js → LessonEditorView-CxAFaXPX.js} +1 -1
  15. package/dist/assets/{LiveClassroomView-tKU_lHcI.js → LiveClassroomView-1xEQD8kc.js} +1 -1
  16. package/dist/assets/{NotificationDetailModal-uccOU2pe.js → NotificationDetailModal-Ct6gVcYm.js} +1 -1
  17. package/dist/assets/{PluginView-M7vQw4BM.js → PluginView-C_x3cLPF.js} +1 -1
  18. package/dist/assets/{QuizGeneratorModal-BuNlxGoR.js → QuizGeneratorModal-wuwCjhBQ.js} +1 -1
  19. package/dist/assets/{StudentAssignmentView-DLtHxJi9.js → StudentAssignmentView-COE6Qt_M.js} +1 -1
  20. package/dist/assets/{StudentLessonView-D0Ad4Xlk.js → StudentLessonView-Va3OjK-3.js} +1 -1
  21. package/dist/assets/{StudentPreviewModal-hjJu-Rno.js → StudentPreviewModal-DWQIUz_y.js} +1 -1
  22. package/dist/assets/{StudentView-BcNgjfKD.js → StudentView-DDr5t9eP.js} +2 -2
  23. package/dist/assets/{SystemResourceLibraryModal-BFX8Od8z.js → SystemResourceLibraryModal-L2ArPBxr.js} +1 -1
  24. package/dist/assets/{TeacherView-B4YDVXJu.js → TeacherView-DHBfbHxr.js} +2 -2
  25. package/dist/assets/{index-hKvFOZ9V.js → index-AmZ2Y6s6.js} +22 -22
  26. package/dist/assets/index-D7PQWdtD.css +1 -0
  27. package/dist/assets/{index-WzkWF1BD.js → index-Du12Kt7k.js} +2 -2
  28. package/dist/index.html +2 -2
  29. package/dist/server.cjs +94 -7
  30. package/package.json +4 -4
  31. package/dist/assets/index-B-YxO39A.css +0 -1
package/dist/server.cjs CHANGED
@@ -351,7 +351,7 @@ var init_token = __esm({
351
351
  });
352
352
 
353
353
  // packages/core/di/interfaces.ts
354
- var ICommandBusServiceToken4, IEventBusServiceToken3, IActionRegistryServiceToken4, ICapabilityServiceToken, IProcessServiceToken2, IStorageServiceToken, IAIServiceToken, IDatabaseToken4, IPluginHostToken, IPluginLifecycleManagerToken, IPluginDistributionManagerToken, IPluginRuntimeCompositionToken, IUnifiedExtensionRegistryToken, IPluginCapabilityGatewayToken, ICapabilityRegistryToken, ISemesterGradeServiceToken, IPointsDimensionRegistryToken, IPointsLedgerServiceToken, ILessonEngineServiceToken, IClassroomRuntimeServiceToken, IPresenceEngineServiceToken, ITeachingCollaborationServiceToken, ILearningAnalyticsServiceToken, IAICapabilityServiceToken, ICapabilityRuntimeServiceToken, ICapabilityGovernanceServiceToken, IPlatformServiceRegistryToken;
354
+ var ICommandBusServiceToken4, IEventBusServiceToken3, IActionRegistryServiceToken4, ICapabilityServiceToken, IProcessServiceToken2, IStorageServiceToken, IAIServiceToken, IDatabaseToken4, IPluginHostToken, IPluginLifecycleManagerToken, IPluginDistributionManagerToken, IPluginRuntimeCompositionToken, IUnifiedExtensionRegistryToken, IPluginCapabilityGatewayToken, ICapabilityRegistryToken, IAuthSessionBridgeToken, ISemesterGradeServiceToken, IPointsDimensionRegistryToken, IPointsLedgerServiceToken, ILessonEngineServiceToken, IClassroomRuntimeServiceToken, IPresenceEngineServiceToken, ITeachingCollaborationServiceToken, ILearningAnalyticsServiceToken, IAICapabilityServiceToken, ICapabilityRuntimeServiceToken, ICapabilityGovernanceServiceToken, IPlatformServiceRegistryToken;
355
355
  var init_interfaces = __esm({
356
356
  "packages/core/di/interfaces.ts"() {
357
357
  init_token();
@@ -400,6 +400,10 @@ var init_interfaces = __esm({
400
400
  ICapabilityRegistryToken = new Token(
401
401
  "@openlearn/core:ICapabilityRegistry"
402
402
  );
403
+ IAuthSessionBridgeToken = new Token(
404
+ "@openlearn/core:IAuthSessionBridgeService",
405
+ "1.0.0"
406
+ );
403
407
  ISemesterGradeServiceToken = new Token(
404
408
  "@openlearn/core:ISemesterGradeService"
405
409
  );
@@ -8402,6 +8406,78 @@ var PointsLedgerService = class {
8402
8406
  }
8403
8407
  };
8404
8408
 
8409
+ // packages/core/di/auth-session-bridge-service.ts
8410
+ var import_node_crypto = __toESM(require("node:crypto"), 1);
8411
+ var AuthSessionBridgeService = class {
8412
+ constructor(db2) {
8413
+ this.db = db2;
8414
+ }
8415
+ async createSession(user) {
8416
+ if (!user || !user.userId || !user.role) {
8417
+ throw new Error("Invalid user payload: userId and role are required");
8418
+ }
8419
+ const now = Date.now();
8420
+ const maxAgeSeconds = 7 * 24 * 60 * 60;
8421
+ const expiresAt = now + maxAgeSeconds * 1e3;
8422
+ const sessionToken = "token_" + import_node_crypto.default.randomBytes(16).toString("hex");
8423
+ if (user.role === "student") {
8424
+ const existingStudent = this.db.prepare("SELECT id FROM students WHERE id = ?").get(user.userId);
8425
+ if (!existingStudent) {
8426
+ const studentNumber = user.username || `sso_${user.userId}`;
8427
+ const studentNumberCheck = this.db.prepare("SELECT id FROM students WHERE student_number = ?").get(studentNumber);
8428
+ const finalStudentNumber = studentNumberCheck ? `sso_${user.userId}_${Date.now()}` : studentNumber;
8429
+ this.db.prepare(`
8430
+ INSERT INTO students (id, student_number, name, email, created_at)
8431
+ VALUES (?, ?, ?, ?, ?)
8432
+ `).run(
8433
+ user.userId,
8434
+ finalStudentNumber,
8435
+ user.name || user.username || user.userId,
8436
+ user.email || null,
8437
+ now
8438
+ );
8439
+ }
8440
+ } else if (user.role === "teacher" || user.role === "administrator") {
8441
+ const existingUser = this.db.prepare("SELECT id FROM users WHERE id = ?").get(user.userId);
8442
+ if (!existingUser) {
8443
+ const username = user.username || `sso_${user.userId}`;
8444
+ const usernameCheck = this.db.prepare("SELECT id FROM users WHERE username = ?").get(username);
8445
+ const finalUsername = usernameCheck ? `sso_${user.userId}_${Date.now()}` : username;
8446
+ this.db.prepare(`
8447
+ INSERT INTO users (id, username, password_hash, role, name, created_at, status)
8448
+ VALUES (?, ?, ?, ?, ?, ?, 'active')
8449
+ `).run(
8450
+ user.userId,
8451
+ finalUsername,
8452
+ "",
8453
+ // SSO 用户无本地明文/哈希密码
8454
+ user.role,
8455
+ user.name || user.username || user.userId,
8456
+ now
8457
+ );
8458
+ }
8459
+ }
8460
+ const sessionData = {
8461
+ userId: user.userId,
8462
+ username: user.username || user.userId,
8463
+ role: user.role,
8464
+ subRole: user.role,
8465
+ name: user.name || user.username || user.userId,
8466
+ email: user.email || null,
8467
+ avatar: user.avatar ?? null,
8468
+ classId: user.classId ?? null
8469
+ };
8470
+ this.db.prepare(`
8471
+ INSERT INTO client_sessions (id, session_data, updated_at, expires_at)
8472
+ VALUES (?, ?, ?, ?)
8473
+ `).run(sessionToken, JSON.stringify(sessionData), now, expiresAt);
8474
+ return {
8475
+ token: sessionToken,
8476
+ maxAge: maxAgeSeconds
8477
+ };
8478
+ }
8479
+ };
8480
+
8405
8481
  // packages/core/plugin-host/index.ts
8406
8482
  var import_uuid13 = require("uuid");
8407
8483
  var import_fs5 = __toESM(require("fs"), 1);
@@ -8988,7 +9064,7 @@ var HotReloadActivationError = class extends HotReloadError {
8988
9064
  init_interfaces();
8989
9065
 
8990
9066
  // packages/core/version.ts
8991
- var PLATFORM_VERSION = "0.3.13";
9067
+ var PLATFORM_VERSION = "0.3.15";
8992
9068
  var OPENLEARN_VERSION = PLATFORM_VERSION;
8993
9069
 
8994
9070
  // packages/core/plugin-host/plugin-runtime-composition.ts
@@ -17975,6 +18051,7 @@ var Kernel = class {
17975
18051
  this.serviceRegistry.register(ICapabilityRuntimeServiceToken, { getRuntimeKernel: async () => this.capabilityFrameworkRuntime });
17976
18052
  this.serviceRegistry.register(ICapabilityGovernanceServiceToken, { getGovernanceKernel: async () => this.capabilityGovernance });
17977
18053
  this.serviceRegistry.register(IPlatformServiceRegistryToken, { getServiceRegistryKernel: async () => this.platformServiceRegistryKernel });
18054
+ this.serviceRegistry.register(IAuthSessionBridgeToken, new AuthSessionBridgeService(this.db));
17978
18055
  this.commandBus.setInterceptor(async (command) => {
17979
18056
  const action = this.actionRegistry.getActionByCommandType(command.type);
17980
18057
  if (action) {
@@ -25069,6 +25146,12 @@ data: ${JSON.stringify({ error: msg })}
25069
25146
  5e3
25070
25147
  );
25071
25148
  const status = typeof response.status === "number" && response.status >= 100 && response.status <= 599 ? response.status : 200;
25149
+ if (response.sessionToken && typeof response.sessionToken === "string" && response.sessionToken.startsWith("token_")) {
25150
+ const isSecure = req.secure || req.headers["x-forwarded-proto"] === "https" || process.env.NODE_ENV === "production" && process.env.ENABLE_HTTPS === "true";
25151
+ const secureFlag = isSecure ? "; Secure" : "";
25152
+ const sameSite = isSecure ? "SameSite=None" : "SameSite=Lax";
25153
+ res.setHeader("Set-Cookie", `edu_os_token=${response.sessionToken}; Path=/; HttpOnly; ${sameSite}; Max-Age=604800${secureFlag}`);
25154
+ }
25072
25155
  if (response.headers && typeof response.headers === "object") {
25073
25156
  for (const [k, v] of Object.entries(response.headers)) {
25074
25157
  const lowerKey = k.toLowerCase();
@@ -25077,10 +25160,10 @@ data: ${JSON.stringify({ error: msg })}
25077
25160
  }
25078
25161
  }
25079
25162
  }
25080
- if (res.getHeader("content-type") === void 0) {
25163
+ if (res.getHeader("content-type") === void 0 && response.body !== void 0 && response.body !== null) {
25081
25164
  res.setHeader("content-type", "application/json; charset=utf-8");
25082
25165
  }
25083
- res.status(status).send(response.body);
25166
+ res.status(status).send(response.body !== void 0 ? response.body : "");
25084
25167
  } catch (err) {
25085
25168
  if (err.name === "GatewayTimeoutError" || err.message?.includes("timed out")) {
25086
25169
  res.status(504).json({ success: false, error: "Gateway Timeout: Plugin did not respond in time" });
@@ -25752,8 +25835,11 @@ async function startServer() {
25752
25835
  kernelContainer.pluginHost.setExpressApp(app);
25753
25836
  const PORT = parseInt(process.env.PORT || "9000", 10);
25754
25837
  app.set("trust proxy", 1);
25755
- const frameAllowedOrigins = process.env.ALLOWED_FRAME_ORIGINS ? process.env.ALLOWED_FRAME_ORIGINS.split(",").map((s) => s.trim()) : ["http://localhost:*", "http://127.0.0.1:*"];
25838
+ const frameAllowedOrigins = process.env.ALLOWED_FRAME_ORIGINS ? process.env.ALLOWED_FRAME_ORIGINS.split(",").map((s) => s.trim()).filter(Boolean) : ["http://localhost:*", "http://127.0.0.1:*"];
25839
+ const ltiAllowedOrigins = process.env.LTI_ALLOWED_LMS_ORIGINS ? process.env.LTI_ALLOWED_LMS_ORIGINS.split(",").map((s) => s.trim()).filter(Boolean) : [];
25756
25840
  app.use((0, import_helmet.default)({
25841
+ // SEC-LTI: 若配置了允许嵌入的 LMS 平台域名,禁用全局 X-Frame-Options,由 CSP frame-ancestors 严格精细化管控
25842
+ xFrameOptions: ltiAllowedOrigins.length > 0 ? false : { action: "sameorigin" },
25757
25843
  contentSecurityPolicy: {
25758
25844
  directives: {
25759
25845
  defaultSrc: ["'self'"],
@@ -25765,6 +25851,7 @@ async function startServer() {
25765
25851
  fontSrc: ["'self'", "data:", "https://fonts.gstatic.com"],
25766
25852
  // 移除通配 http: 和 https:,限制 iframe 仅能加载本地、沙箱或受信任课件源
25767
25853
  frameSrc: ["'self'", "blob:", "data:", ...frameAllowedOrigins],
25854
+ frameAncestors: ltiAllowedOrigins.length > 0 ? ["'self'", ...ltiAllowedOrigins] : ["'self'"],
25768
25855
  objectSrc: ["'none'"],
25769
25856
  baseUri: ["'self'"]
25770
25857
  }
@@ -25772,10 +25859,10 @@ async function startServer() {
25772
25859
  crossOriginOpenerPolicy: false,
25773
25860
  crossOriginEmbedderPolicy: false,
25774
25861
  crossOriginResourcePolicy: { policy: "cross-origin" },
25775
- // 允许沙箱 iframe(opaque origin)加载静态资?
25862
+ // 允许沙箱 iframe(opaque origin)加载静态资源
25776
25863
  originAgentCluster: false,
25777
25864
  strictTransportSecurity: false
25778
- // �? HTTP 部署,禁�? HSTS(否则浏览器缓存后强�? HTTPS,导�? ERR_CONNECTION_REFUSED�?
25865
+ // 针对 HTTP 部署,禁用 HSTS(否则浏览器缓存后强制 HTTPS,导致 ERR_CONNECTION_REFUSED)
25779
25866
  }));
25780
25867
  const loginLimiter = (0, import_express_rate_limit.default)({
25781
25868
  windowMs: 60 * 1e3,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openlearn-next",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "private": false,
5
5
  "description": "A plugin-driven Educational OS / LMS platform with AI agent teaching assistant",
6
6
  "keywords": [
@@ -74,10 +74,11 @@
74
74
  "socket.io": "^4.8.3",
75
75
  "socket.io-client": "^4.8.3",
76
76
  "uuid": "^14.0.0",
77
+ "xlsx": "^0.18.5",
77
78
  "xss": "^1.0.15",
78
79
  "zod": "^4.4.3",
79
80
  "zustand": "^5.0.14",
80
- "@openlearn/plugin-sdk": "3.6.0"
81
+ "@openlearn/plugin-sdk": "3.6.1"
81
82
  },
82
83
  "devDependencies": {
83
84
  "@eslint/js": "^10.0.1",
@@ -106,8 +107,7 @@
106
107
  "tailwindcss": "^4.1.14",
107
108
  "tsx": "^4.21.0",
108
109
  "typescript": "~5.8.2",
109
- "vitest": "^4.1.9",
110
- "xlsx": "^0.18.5"
110
+ "vitest": "^4.1.9"
111
111
  },
112
112
  "scripts": {
113
113
  "dev": "npx tsx --no-cache server.ts",