ocean-brain 0.12.3 → 0.13.1

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 (101) hide show
  1. package/README.md +5 -3
  2. package/dist/index.js +12 -4
  3. package/package.json +10 -8
  4. package/server/client/dist/assets/{ArrowRight.es-CD1IMhU2.js → ArrowRight.es-BlNsZQFs.js} +1 -1
  5. package/server/client/dist/assets/AuxiliaryPanelHeader-HAttwVby.js +1 -0
  6. package/server/client/dist/assets/Calendar-DExFS7xq.js +25 -0
  7. package/server/client/dist/assets/Callout-CcYyvHQP.js +1 -0
  8. package/server/client/dist/assets/CaretUp.es-C8iY5OlB.js +1 -0
  9. package/server/client/dist/assets/{CheckCircle.es-C7fkzE3t.js → CheckCircle.es-Dqr3WImn.js} +1 -1
  10. package/server/client/dist/assets/{Copy.es-D6rl6b78.js → Copy.es-fMv0ETir.js} +1 -1
  11. package/server/client/dist/assets/{File.es-CcjtzE38.js → File.es-CHtXXafR.js} +1 -1
  12. package/server/client/dist/assets/FileText.es-DPIwDCEe.js +1 -0
  13. package/server/client/dist/assets/{FlowArrow.es-CAIThqWk.js → FlowArrow.es-Dxee98ir.js} +1 -1
  14. package/server/client/dist/assets/Graph-CdEu8eAn.js +46 -0
  15. package/server/client/dist/assets/Graph-hVj6vAYJ.js +1 -0
  16. package/server/client/dist/assets/{Image.es-BDpo5R66.js → Image.es-CjXY1JVf.js} +1 -1
  17. package/server/client/dist/assets/Info.es-CYxg-Vku.js +1 -0
  18. package/server/client/dist/assets/LinkSimple.es-B-P-7Iem.js +1 -0
  19. package/server/client/dist/assets/{Note-Dq2Szaxd.js → Note-CGI-Y1__.js} +4 -4
  20. package/server/client/dist/assets/{NotePropertiesPanel-Cu3RgDic.js → NotePropertiesPanel-6P7UfRBR.js} +2 -2
  21. package/server/client/dist/assets/{Plus.es-P9Fjwtud.js → Plus.es-Dy6onD-Z.js} +1 -1
  22. package/server/client/dist/assets/Reminders-DnuALBL6.js +1 -0
  23. package/server/client/dist/assets/{Search-DVROxY_B.js → Search-B_BRjOyG.js} +2 -2
  24. package/server/client/dist/assets/{SurfaceCard-D_OU_Oy6.js → SurfaceCard-D5QxOte_.js} +1 -1
  25. package/server/client/dist/assets/Tag-2hcR9dd4.js +1 -0
  26. package/server/client/dist/assets/TagNotes-BE20Ji5s.js +1 -0
  27. package/server/client/dist/assets/{Trash.es-BeR1sZMe.js → Trash.es-XEcWG_aq.js} +1 -1
  28. package/server/client/dist/assets/{ViewChip-D-Ax5PdL.js → ViewChip-BVkZAXyx.js} +1 -1
  29. package/server/client/dist/assets/Views-CQXgnbUQ.js +1 -0
  30. package/server/client/dist/assets/app-4M3jUhmd.js +151 -0
  31. package/server/client/dist/assets/app-DjDwGe0t.css +1 -0
  32. package/server/client/dist/assets/calendar-data-wB0ifXdo.js +1 -0
  33. package/server/client/dist/assets/{image.api-CtfyPwkV.js → image.api-ChvlfuZg.js} +1 -1
  34. package/server/client/dist/assets/index-CaT-yZJw.js +1 -0
  35. package/server/client/dist/assets/{manage-image-m-7v_Z1W.js → manage-image-CSJ3Flgc.js} +1 -1
  36. package/server/client/dist/assets/manage-image-detail-Bu9yymBn.js +1 -0
  37. package/server/client/dist/assets/mcp-D8footel.js +5 -0
  38. package/server/client/dist/assets/{mcp-admin-adapter-Cw6w5mfg.js → mcp-admin-adapter-D0R8p9az.js} +1 -1
  39. package/server/client/dist/assets/{note-ui-CYsZc3DI.js → note-ui-BaqzP-YU.js} +1 -1
  40. package/server/client/dist/assets/placeholder-BZ-p6ahW.js +34 -0
  41. package/server/client/dist/assets/{properties-B93_V2uD.js → properties-B7Fv95qY.js} +1 -1
  42. package/server/client/dist/assets/route-preload.js +2 -2
  43. package/server/client/dist/assets/{search-admin-adapter-Dj0KKKrJ.js → search-admin-adapter-Daf7e2YQ.js} +1 -1
  44. package/server/client/dist/assets/search-ddYl3qH-.js +1 -0
  45. package/server/client/dist/assets/{tag.api-BJ6eaHui.js → tag.api-DEUROVKQ.js} +1 -1
  46. package/server/client/dist/assets/time-PxWila4-.js +361 -0
  47. package/server/client/dist/assets/trash-BHida7BG.js +1 -0
  48. package/server/client/dist/assets/{url-BvjSC8PU.js → url-DKUMNVCI.js} +1 -1
  49. package/server/client/dist/assets/useNoteMutate-BjvZUmtX.js +9 -0
  50. package/server/client/dist/assets/{useReminderMutate-kTBRSjYc.js → useReminderMutate-C5n-5LM-.js} +2 -2
  51. package/server/client/dist/index.html +6 -6
  52. package/server/dist/app.js +91 -8
  53. package/server/dist/features/auth/http/api.js +9 -8
  54. package/server/dist/features/auth/http/pages.js +27 -28
  55. package/server/dist/features/auth/service.js +7 -27
  56. package/server/dist/features/cache/graphql/cache.type-defs.js +1 -1
  57. package/server/dist/features/image/http/upload.js +2 -2
  58. package/server/dist/features/mcp-admin/http/handlers.js +9 -9
  59. package/server/dist/features/note/http/mcp.js +17 -17
  60. package/server/dist/features/note/services/properties.js +32 -8
  61. package/server/dist/features/search/http/handlers.js +10 -10
  62. package/server/dist/features/search/search-manager.js +34 -6
  63. package/server/dist/features/tag/http/mcp.js +2 -2
  64. package/server/dist/features/view/graphql/view.query.resolver.js +20 -0
  65. package/server/dist/features/view/graphql/view.type-defs.js +17 -0
  66. package/server/dist/features/view/services/workspace.js +116 -1
  67. package/server/dist/modules/auth-guard.js +36 -53
  68. package/server/dist/modules/blocknote.js +16 -9
  69. package/server/dist/modules/error-handler.js +17 -20
  70. package/server/dist/modules/mcp-auth.js +50 -56
  71. package/server/dist/modules/rate-limit.js +23 -23
  72. package/server/dist/modules/server-events-handler.js +15 -13
  73. package/server/dist/modules/session-store.js +6 -8
  74. package/server/dist/paths.js +2 -1
  75. package/server/dist/routes/api.js +105 -71
  76. package/server/dist/routes/auth-pages.js +21 -11
  77. package/server/dist/routes/client.js +60 -72
  78. package/server/dist/routes/graphql.js +31 -26
  79. package/server/dist/routes/mcp.js +25 -35
  80. package/server/dist/server.js +14 -16
  81. package/server/client/dist/assets/AuxiliaryPanelHeader-zoJGsYWR.js +0 -1
  82. package/server/client/dist/assets/Calendar-6otw_EJn.js +0 -25
  83. package/server/client/dist/assets/Callout-C1QiCWCa.js +0 -1
  84. package/server/client/dist/assets/Graph-BHsmPDPh.js +0 -46
  85. package/server/client/dist/assets/Reminders-DKm0GFI2.js +0 -1
  86. package/server/client/dist/assets/Tag-wMHTRwlF.js +0 -1
  87. package/server/client/dist/assets/TagNotes-BRza6i14.js +0 -1
  88. package/server/client/dist/assets/Views-CL7rC05G.js +0 -1
  89. package/server/client/dist/assets/app-CyQtGTtn.js +0 -139
  90. package/server/client/dist/assets/app-DGFNtdpS.css +0 -1
  91. package/server/client/dist/assets/index-BEfKfbvr.js +0 -1
  92. package/server/client/dist/assets/manage-image-detail-BNYOCSmu.js +0 -1
  93. package/server/client/dist/assets/mcp-B-aGeAQD.js +0 -4
  94. package/server/client/dist/assets/placeholder-C5fNzKrL.js +0 -34
  95. package/server/client/dist/assets/search-BmXuuK26.js +0 -1
  96. package/server/client/dist/assets/sortable.esm-DgSW3bAA.js +0 -5
  97. package/server/client/dist/assets/time-DI902Sxa.js +0 -361
  98. package/server/client/dist/assets/trash-GulvRh9f.js +0 -1
  99. package/server/client/dist/assets/useNoteMutate-xEIypxi5.js +0 -9
  100. package/server/dist/modules/logger.js +0 -53
  101. package/server/dist/modules/use-async.js +0 -10
@@ -44,64 +44,58 @@ const readBearerToken = (authorizationHeader) => {
44
44
  return authorizationHeader.slice("Bearer ".length).trim() || void 0;
45
45
  };
46
46
  const createMcpAuthMiddleware = (_authConfig, mcpAdminAuth) => {
47
- return async (req, res, next) => {
48
- try {
49
- const status = await mcpAdminAuth.getStatus();
50
- if (!status.enabled) {
51
- res.status(403).set(JSON_HEADERS).json({
52
- code: "MCP_DISABLED",
53
- message: "MCP access is disabled by admin."
54
- }).end();
55
- return;
56
- }
57
- const bearerToken = readBearerToken(req.headers.authorization);
58
- if (!bearerToken) {
59
- res.status(401).set(JSON_HEADERS).json({
60
- code: "UNAUTHORIZED",
61
- message: "A valid MCP bearer token is required."
62
- }).end();
63
- return;
64
- }
65
- const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);
66
- if (!validation.ok && validation.reason === "not_configured") {
67
- res.status(503).set(JSON_HEADERS).json({
68
- code: "MCP_AUTH_NOT_CONFIGURED",
69
- message: "MCP bearer auth is not configured."
70
- }).end();
71
- return;
72
- }
73
- if (!validation.ok) {
74
- res.status(403).set(JSON_HEADERS).json({
75
- code: "FORBIDDEN",
76
- message: "Invalid MCP bearer token."
77
- }).end();
78
- return;
79
- }
80
- const versionInfo = getOceanBrainVersionInfo();
81
- const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);
82
- const mcpClientVersion = readMcpClientVersion(req);
83
- if (!mcpCompatibilityVersion || !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)) {
84
- res.status(426).set(JSON_HEADERS).json({
85
- code: "MCP_VERSION_INCOMPATIBLE",
86
- message: createMcpVersionCompatibilityMessage({
87
- serverVersion: versionInfo.version,
88
- requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
89
- mcpCompatibilityVersion,
90
- mcpClientVersion
91
- }),
47
+ return async (req, reply) => {
48
+ const status = await mcpAdminAuth.getStatus();
49
+ if (!status.enabled) {
50
+ await reply.status(403).headers(JSON_HEADERS).send({
51
+ code: "MCP_DISABLED",
52
+ message: "MCP access is disabled by admin."
53
+ });
54
+ return;
55
+ }
56
+ const bearerToken = readBearerToken(req.headers.authorization);
57
+ if (!bearerToken) {
58
+ await reply.status(401).headers(JSON_HEADERS).send({
59
+ code: "UNAUTHORIZED",
60
+ message: "A valid MCP bearer token is required."
61
+ });
62
+ return;
63
+ }
64
+ const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);
65
+ if (!validation.ok && validation.reason === "not_configured") {
66
+ await reply.status(503).headers(JSON_HEADERS).send({
67
+ code: "MCP_AUTH_NOT_CONFIGURED",
68
+ message: "MCP bearer auth is not configured."
69
+ });
70
+ return;
71
+ }
72
+ if (!validation.ok) {
73
+ await reply.status(403).headers(JSON_HEADERS).send({
74
+ code: "FORBIDDEN",
75
+ message: "Invalid MCP bearer token."
76
+ });
77
+ return;
78
+ }
79
+ const versionInfo = getOceanBrainVersionInfo();
80
+ const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);
81
+ const mcpClientVersion = readMcpClientVersion(req);
82
+ if (!mcpCompatibilityVersion || !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)) {
83
+ await reply.status(426).headers(JSON_HEADERS).send({
84
+ code: "MCP_VERSION_INCOMPATIBLE",
85
+ message: createMcpVersionCompatibilityMessage({
92
86
  serverVersion: versionInfo.version,
93
- mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,
94
- mcpClientVersion: mcpClientVersion ?? null,
95
- mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,
96
- requiredMcpVersion: versionInfo.mcpVersionRequirement,
97
87
  requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
98
- releaseUrl: versionInfo.releaseUrl
99
- }).end();
100
- return;
101
- }
102
- next();
103
- } catch (error) {
104
- next(error);
88
+ mcpCompatibilityVersion,
89
+ mcpClientVersion
90
+ }),
91
+ serverVersion: versionInfo.version,
92
+ mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,
93
+ mcpClientVersion: mcpClientVersion ?? null,
94
+ mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,
95
+ requiredMcpVersion: versionInfo.mcpVersionRequirement,
96
+ requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
97
+ releaseUrl: versionInfo.releaseUrl
98
+ });
105
99
  }
106
100
  };
107
101
  };
@@ -1,31 +1,31 @@
1
- import { rateLimit } from "express-rate-limit";
1
+ import { isAuthenticatedRequest } from "./auth-guard.js";
2
+ import { createAppError } from "./error-handler.js";
2
3
  const AUTH_RATE_LIMIT_MESSAGE = "Too many authentication attempts. Please try again later.";
3
4
  const SESSION_ACCESS_RATE_LIMIT_MESSAGE = "Too many authenticated requests. Please try again later.";
4
- const createAuthAttemptRateLimit = () => rateLimit({
5
- windowMs: 15 * 60 * 1e3,
6
- limit: 10,
7
- standardHeaders: true,
8
- legacyHeaders: false,
9
- handler: (_req, res) => {
10
- res.status(429).json({
11
- code: "AUTH_RATE_LIMITED",
12
- message: AUTH_RATE_LIMIT_MESSAGE
13
- });
14
- }
5
+ const IMAGE_ASSET_RATE_LIMIT_MESSAGE = "Too many image asset requests. Please try again later.";
6
+ const createOptions = (max, timeWindow, code, message, options = {}) => ({
7
+ max,
8
+ timeWindow,
9
+ hook: "preHandler",
10
+ enableDraftSpec: true,
11
+ errorResponseBuilder: (_request, context) => createAppError(context.statusCode, code, message),
12
+ ...options
15
13
  });
16
- const createSessionAccessRateLimit = () => rateLimit({
17
- windowMs: 60 * 1e3,
18
- limit: 300,
19
- standardHeaders: true,
20
- legacyHeaders: false,
21
- handler: (_req, res) => {
22
- res.status(429).json({
23
- code: "SESSION_RATE_LIMITED",
24
- message: SESSION_ACCESS_RATE_LIMIT_MESSAGE
25
- });
26
- }
14
+ const createAuthAttemptRateLimit = () => createOptions(10, 15 * 60 * 1e3, "AUTH_RATE_LIMITED", AUTH_RATE_LIMIT_MESSAGE, {
15
+ groupId: "auth-attempt"
16
+ });
17
+ const createSessionAccessRateLimit = () => createOptions(300, 60 * 1e3, "SESSION_RATE_LIMITED", SESSION_ACCESS_RATE_LIMIT_MESSAGE, {
18
+ groupId: "session-access"
19
+ });
20
+ const createImageAssetRateLimit = (authConfig) => ({
21
+ ...createOptions(10, 15 * 60 * 1e3, "IMAGE_ASSET_RATE_LIMITED", IMAGE_ASSET_RATE_LIMIT_MESSAGE, {
22
+ groupId: "image-asset",
23
+ allowList: (request) => authConfig.mode !== "password" || isAuthenticatedRequest(request)
24
+ }),
25
+ hook: "onRequest"
27
26
  });
28
27
  export {
29
28
  createAuthAttemptRateLimit,
29
+ createImageAssetRateLimit,
30
30
  createSessionAccessRateLimit
31
31
  };
@@ -12,23 +12,25 @@ const getEventStreamLifetimeMs = (expires) => {
12
12
  return Math.max(0, Math.min(expiresAt.getTime() - Date.now(), AUTH_SESSION_IDLE_TIMEOUT_MS));
13
13
  };
14
14
  const createServerEventsHandler = () => {
15
- return async (req, res) => {
16
- res.status(200);
17
- res.setHeader("Content-Type", "text/event-stream; charset=utf-8");
18
- res.setHeader("Cache-Control", "no-cache, no-transform");
19
- res.setHeader("Connection", "keep-alive");
20
- res.setHeader("X-Accel-Buffering", "no");
21
- res.flushHeaders?.();
22
- res.write(": connected\n\n");
15
+ return async (req, reply) => {
16
+ reply.hijack();
17
+ const response = reply.raw;
18
+ response.statusCode = 200;
19
+ response.setHeader("Content-Type", "text/event-stream; charset=utf-8");
20
+ response.setHeader("Cache-Control", "no-cache, no-transform");
21
+ response.setHeader("Connection", "keep-alive");
22
+ response.setHeader("X-Accel-Buffering", "no");
23
+ response.flushHeaders();
24
+ response.write(": connected\n\n");
23
25
  const unsubscribe = subscribeServerEvents((event) => {
24
- res.write(serializeServerEvent(event));
26
+ response.write(serializeServerEvent(event));
25
27
  });
26
28
  const keepAliveTimer = setInterval(() => {
27
- res.write(": keepalive\n\n");
29
+ response.write(": keepalive\n\n");
28
30
  }, KEEP_ALIVE_INTERVAL_MS);
29
31
  keepAliveTimer.unref?.();
30
32
  const sessionExpiryTimer = setTimeout(() => {
31
- res.end();
33
+ response.end();
32
34
  }, getEventStreamLifetimeMs(req.session?.cookie.expires));
33
35
  sessionExpiryTimer.unref?.();
34
36
  let cleanedUp = false;
@@ -41,8 +43,8 @@ const createServerEventsHandler = () => {
41
43
  clearTimeout(sessionExpiryTimer);
42
44
  unsubscribe();
43
45
  };
44
- req.on("close", cleanup);
45
- res.on("close", cleanup);
46
+ req.raw.on("close", cleanup);
47
+ response.on("close", cleanup);
46
48
  };
47
49
  };
48
50
  export {
@@ -1,4 +1,3 @@
1
- import session from "express-session";
2
1
  const AUTH_SESSION_IDLE_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1e3;
3
2
  const ANONYMOUS_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
4
3
  const AUTH_SESSION_PRUNE_INTERVAL_MS = 60 * 60 * 1e3;
@@ -25,9 +24,8 @@ const getSessionExpiry = (data, now = Date.now()) => {
25
24
  }
26
25
  return fallbackExpiresAt;
27
26
  };
28
- class TtlMemorySessionStore extends session.Store {
27
+ class TtlMemorySessionStore {
29
28
  constructor(options = {}) {
30
- super();
31
29
  this.options = options;
32
30
  this.pruneTimer = setInterval(() => {
33
31
  this.pruneExpiredSessions();
@@ -54,7 +52,7 @@ class TtlMemorySessionStore extends session.Store {
54
52
  callback(error);
55
53
  }
56
54
  }
57
- set(sid, data, callback) {
55
+ set(sid, data, callback = () => void 0) {
58
56
  try {
59
57
  const now = Date.now();
60
58
  this.sessions.set(sid, {
@@ -65,14 +63,14 @@ class TtlMemorySessionStore extends session.Store {
65
63
  });
66
64
  this.pruneExpiredSessions();
67
65
  this.evictOldestSessions();
68
- callback?.();
66
+ callback();
69
67
  } catch (error) {
70
- callback?.(error);
68
+ callback(error);
71
69
  }
72
70
  }
73
- destroy(sid, callback) {
71
+ destroy(sid, callback = () => void 0) {
74
72
  this.sessions.delete(sid);
75
- callback?.();
73
+ callback();
76
74
  }
77
75
  touch(sid, data, callback) {
78
76
  const entry = this.sessions.get(sid);
@@ -6,9 +6,10 @@ const DATA_DIR = process.env.OCEAN_BRAIN_DATA_DIR || ".";
6
6
  const IMAGE_DIR = process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(DATA_DIR, "assets/images");
7
7
  const SEARCH_INDEX_PATH = process.env.OCEAN_BRAIN_SEARCH_INDEX_PATH || path.resolve(DATA_DIR, "search.sqlite3");
8
8
  const EMBEDDING_API_KEY_PATH = path.resolve(DATA_DIR, "embedding-api-key");
9
+ const CLIENT_DIST = process.env.OCEAN_BRAIN_CLIENT_DIST || path.resolve(PACKAGE_ROOT, "client/dist");
9
10
  const paths = {
10
11
  packageRoot: path.resolve(PACKAGE_ROOT),
11
- clientDist: path.resolve(PACKAGE_ROOT, "client/dist"),
12
+ clientDist: path.resolve(CLIENT_DIST),
12
13
  imageDir: path.resolve(IMAGE_DIR),
13
14
  searchIndex: path.resolve(SEARCH_INDEX_PATH),
14
15
  embeddingApiKey: path.resolve(EMBEDDING_API_KEY_PATH)
@@ -1,4 +1,3 @@
1
- import { Router } from "express";
2
1
  import { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from "../features/auth/http/api.js";
3
2
  import { createUploadImageHandler } from "../features/image/http/upload.js";
4
3
  import {
@@ -17,78 +16,113 @@ import {
17
16
  import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
18
17
  import { createAuthAttemptRateLimit, createSessionAccessRateLimit } from "../modules/rate-limit.js";
19
18
  import { createServerEventsHandler } from "../modules/server-events-handler.js";
20
- import useAsync from "../modules/use-async.js";
21
19
  import { createMcpRouter } from "./mcp.js";
22
20
  const createApiRouter = (authConfig, mcpAdminService) => {
23
- const csrfProtection = createCsrfProtection(authConfig);
24
- const requireSession = requireSessionForWrite(authConfig);
25
- const sessionAccessRateLimit = createSessionAccessRateLimit();
26
- return Router().use("/mcp", createMcpRouter(authConfig, mcpAdminService)).get("/auth/session", csrfProtection, useAsync(createSessionStatusHandler(authConfig))).post("/auth/login", csrfProtection, createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig))).post(
27
- "/auth/logout",
28
- sessionAccessRateLimit,
29
- requireSession,
30
- csrfProtection,
31
- useAsync(createLogoutHandler(authConfig))
32
- ).get(
33
- "/mcp-admin/status",
34
- sessionAccessRateLimit,
35
- requireSession,
36
- csrfProtection,
37
- useAsync(createMcpAdminStatusHandler(mcpAdminService))
38
- ).post(
39
- "/mcp-admin/enabled",
40
- sessionAccessRateLimit,
41
- requireSession,
42
- csrfProtection,
43
- useAsync(createMcpAdminSetEnabledHandler(mcpAdminService))
44
- ).post(
45
- "/mcp-admin/token/rotate",
46
- sessionAccessRateLimit,
47
- requireSession,
48
- csrfProtection,
49
- useAsync(createMcpAdminRotateTokenHandler(mcpAdminService))
50
- ).post(
51
- "/mcp-admin/token/revoke",
52
- sessionAccessRateLimit,
53
- requireSession,
54
- csrfProtection,
55
- useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
56
- ).get(
57
- "/search-admin/status",
58
- sessionAccessRateLimit,
59
- requireSession,
60
- csrfProtection,
61
- useAsync(createSearchAdminStatusHandler())
62
- ).post(
63
- "/search-admin/config",
64
- sessionAccessRateLimit,
65
- requireSession,
66
- csrfProtection,
67
- useAsync(createSearchAdminSaveConfigHandler())
68
- ).post(
69
- "/search-admin/models",
70
- sessionAccessRateLimit,
71
- requireSession,
72
- csrfProtection,
73
- useAsync(createSearchAdminListModelsHandler())
74
- ).post(
75
- "/search-admin/test",
76
- sessionAccessRateLimit,
77
- requireSession,
78
- csrfProtection,
79
- useAsync(createSearchAdminTestConnectionHandler())
80
- ).post(
81
- "/search-admin/reindex",
82
- sessionAccessRateLimit,
83
- requireSession,
84
- csrfProtection,
85
- useAsync(createSearchAdminReindexHandler())
86
- ).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler()).use((_req, res) => {
87
- res.status(404).json({
88
- code: "API_ROUTE_NOT_FOUND",
89
- message: "The requested API route was not found."
90
- }).end();
91
- });
21
+ return async (app) => {
22
+ const csrfProtection = createCsrfProtection(authConfig);
23
+ const requireSession = requireSessionForWrite(authConfig);
24
+ const sessionAccessRateLimit = app.rateLimit(createSessionAccessRateLimit());
25
+ app.register(createMcpRouter(authConfig, mcpAdminService), { prefix: "/mcp" });
26
+ app.get("/auth/session", createSessionStatusHandler(authConfig));
27
+ app.post(
28
+ "/auth/login",
29
+ {
30
+ preHandler: csrfProtection,
31
+ config: { rateLimit: createAuthAttemptRateLimit() }
32
+ },
33
+ createLoginHandler(authConfig)
34
+ );
35
+ app.post(
36
+ "/auth/logout",
37
+ {
38
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
39
+ },
40
+ createLogoutHandler(authConfig)
41
+ );
42
+ app.get(
43
+ "/mcp-admin/status",
44
+ {
45
+ preHandler: [sessionAccessRateLimit, requireSession]
46
+ },
47
+ createMcpAdminStatusHandler(mcpAdminService)
48
+ );
49
+ app.post(
50
+ "/mcp-admin/enabled",
51
+ {
52
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
53
+ },
54
+ createMcpAdminSetEnabledHandler(mcpAdminService)
55
+ );
56
+ app.post(
57
+ "/mcp-admin/token/rotate",
58
+ {
59
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
60
+ },
61
+ createMcpAdminRotateTokenHandler(mcpAdminService)
62
+ );
63
+ app.post(
64
+ "/mcp-admin/token/revoke",
65
+ {
66
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
67
+ },
68
+ createMcpAdminRevokeTokenHandler(mcpAdminService)
69
+ );
70
+ app.get(
71
+ "/search-admin/status",
72
+ {
73
+ preHandler: [sessionAccessRateLimit, requireSession]
74
+ },
75
+ createSearchAdminStatusHandler()
76
+ );
77
+ app.post(
78
+ "/search-admin/config",
79
+ {
80
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
81
+ },
82
+ createSearchAdminSaveConfigHandler()
83
+ );
84
+ app.post(
85
+ "/search-admin/models",
86
+ {
87
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
88
+ },
89
+ createSearchAdminListModelsHandler()
90
+ );
91
+ app.post(
92
+ "/search-admin/test",
93
+ {
94
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
95
+ },
96
+ createSearchAdminTestConnectionHandler()
97
+ );
98
+ app.post(
99
+ "/search-admin/reindex",
100
+ {
101
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
102
+ },
103
+ createSearchAdminReindexHandler()
104
+ );
105
+ app.post(
106
+ "/image",
107
+ {
108
+ preHandler: [sessionAccessRateLimit, requireSession, csrfProtection]
109
+ },
110
+ createUploadImageHandler()
111
+ );
112
+ app.get(
113
+ "/events",
114
+ {
115
+ preHandler: [sessionAccessRateLimit, requireSession]
116
+ },
117
+ createServerEventsHandler()
118
+ );
119
+ app.all("/*", (_request, reply) => {
120
+ return reply.status(404).send({
121
+ code: "API_ROUTE_NOT_FOUND",
122
+ message: "The requested API route was not found."
123
+ });
124
+ });
125
+ };
92
126
  };
93
127
  export {
94
128
  createApiRouter
@@ -1,21 +1,31 @@
1
- import { Router } from "express";
2
1
  import {
3
2
  createLoginPageHandler,
4
3
  createLoginPageSubmitHandler,
5
4
  createLogoutPageHandler
6
5
  } from "../features/auth/http/pages.js";
7
- import { createCsrfProtection, createLoginCsrfFailureHandler, requireSessionForWrite } from "../modules/auth-guard.js";
6
+ import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
8
7
  import { createAuthAttemptRateLimit, createSessionAccessRateLimit } from "../modules/rate-limit.js";
9
8
  const createAuthPagesRouter = (authConfig) => {
10
- const csrfProtection = createCsrfProtection(authConfig);
11
- const sessionAccessRateLimit = createSessionAccessRateLimit();
12
- return Router().get("/login", csrfProtection, createLoginPageHandler(authConfig)).post("/login", csrfProtection, createAuthAttemptRateLimit(), createLoginPageSubmitHandler(authConfig)).post(
13
- "/logout",
14
- sessionAccessRateLimit,
15
- requireSessionForWrite(authConfig),
16
- csrfProtection,
17
- createLogoutPageHandler(authConfig)
18
- ).use(createLoginCsrfFailureHandler(authConfig));
9
+ return async (app) => {
10
+ const csrfProtection = createCsrfProtection(authConfig);
11
+ app.get("/login", createLoginPageHandler(authConfig));
12
+ app.post(
13
+ "/login",
14
+ {
15
+ preHandler: csrfProtection,
16
+ config: { rateLimit: createAuthAttemptRateLimit() }
17
+ },
18
+ createLoginPageSubmitHandler(authConfig)
19
+ );
20
+ app.post(
21
+ "/logout",
22
+ {
23
+ preHandler: [requireSessionForWrite(authConfig), csrfProtection],
24
+ config: { rateLimit: createSessionAccessRateLimit() }
25
+ },
26
+ createLogoutPageHandler(authConfig)
27
+ );
28
+ };
19
29
  };
20
30
  export {
21
31
  createAuthPagesRouter