ocean-brain 0.7.5 → 0.8.0

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 (89) hide show
  1. package/dist/index.js +3 -3
  2. package/dist/mcp.js +180 -736
  3. package/package.json +4 -1
  4. package/server/client/dist/assets/ArrowRight.es-C4zlc54Z.js +1 -0
  5. package/server/client/dist/assets/{Calendar-Bc2zJueR.js → Calendar-COrN6Hjv.js} +2 -2
  6. package/server/client/dist/assets/Callout-BK0K9E74.js +1 -0
  7. package/server/client/dist/assets/Copy.es-zm8f_XXX.js +1 -0
  8. package/server/client/dist/assets/Graph-BH1fzhue.js +1 -0
  9. package/server/client/dist/assets/{Image-DxWhlIDG.js → Image-KXuHOG4b.js} +1 -1
  10. package/server/client/dist/assets/Image.es-BKahRbIm.js +1 -0
  11. package/server/client/dist/assets/Note-C143cKKw.js +12 -0
  12. package/server/client/dist/assets/Plus.es-BqeC8T8h.js +1 -0
  13. package/server/client/dist/assets/Reminders-BYG5rz1M.js +1 -0
  14. package/server/client/dist/assets/Search-DBWsIMDP.js +1 -0
  15. package/server/client/dist/assets/{SurfaceCard-CkAVMLPs.js → SurfaceCard-CNSQfYfM.js} +1 -1
  16. package/server/client/dist/assets/Tag-5aCfSO8Y.js +1 -0
  17. package/server/client/dist/assets/TagNotes-BPZjN-6l.js +1 -0
  18. package/server/client/dist/assets/Trash.es-CRQkTnXZ.js +1 -0
  19. package/server/client/dist/assets/ViewNotes-DGLNF96x.js +1 -0
  20. package/server/client/dist/assets/ViewSectionDialog-CzskgmMF.js +102 -0
  21. package/server/client/dist/assets/Views-DR5jT23G.js +1 -0
  22. package/server/client/dist/assets/{graph-vendor-CUxe67Lr.js → graph-vendor-UTvF2NS_.js} +2 -2
  23. package/server/client/dist/assets/image.api-8dlMwzMd.js +17 -0
  24. package/server/client/dist/assets/{index-C5BGzDgt.css → index-BEUqDuAZ.css} +1 -1
  25. package/server/client/dist/assets/{index-D3BfroIT.js → index-DGIAh3ZY.js} +1 -1
  26. package/server/client/dist/assets/index-VHp17GQX.js +342 -0
  27. package/server/client/dist/assets/manage-image-RqtWKas2.js +1 -0
  28. package/server/client/dist/assets/{manage-image-detail-C8Q7K7I3.js → manage-image-detail-BTSIbLlQ.js} +1 -1
  29. package/server/client/dist/assets/mcp-EDurbggR.js +4 -0
  30. package/server/client/dist/assets/mcp-admin-adapter-DmGUo5G8.js +1 -0
  31. package/server/client/dist/assets/{note-core-Z6kqulGB.js → note-core-BklwyZHk.js} +1 -1
  32. package/server/client/dist/assets/{note-vendor-i1M2FSI2.js → note-vendor-DkYKUmLo.js} +1 -1
  33. package/server/client/dist/assets/{placeholder-Cr_nI8UI.js → placeholder-DeKgSBnC.js} +4 -4
  34. package/server/client/dist/assets/{properties-CvYWJWUM.js → properties-BjrZvOYn.js} +1 -1
  35. package/server/client/dist/assets/tag.api-Dl8DQd0m.js +21 -0
  36. package/server/client/dist/assets/trash-C5ntddLu.js +1 -0
  37. package/server/client/dist/assets/{useReminderMutate-CDmIEwsw.js → useReminderMutate-CoXXqJY2.js} +1 -1
  38. package/server/client/dist/index.html +5 -5
  39. package/server/dist/features/auth/http/login-page.js +1 -1
  40. package/server/dist/features/image/http/upload.js +34 -23
  41. package/server/dist/features/image/http/upload.js.map +1 -1
  42. package/server/dist/features/image/services/validation.js +111 -0
  43. package/server/dist/features/image/services/validation.js.map +1 -0
  44. package/server/dist/features/mcp-admin/http/handlers.js +11 -3
  45. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
  46. package/server/dist/features/note/http/mcp.js +8 -43
  47. package/server/dist/features/note/http/mcp.js.map +1 -1
  48. package/server/dist/features/note/services/cleanup.js +2 -2
  49. package/server/dist/features/note/services/cleanup.js.map +1 -1
  50. package/server/dist/features/note/services/markdown-intent-write.js +11 -43
  51. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
  52. package/server/dist/features/note/services/markdown-patch.js +4 -123
  53. package/server/dist/features/note/services/markdown-patch.js.map +1 -1
  54. package/server/dist/features/note/services/write.js +13 -13
  55. package/server/dist/features/note/services/write.js.map +1 -1
  56. package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
  57. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
  58. package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
  59. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
  60. package/server/dist/modules/app-version.js +105 -0
  61. package/server/dist/modules/app-version.js.map +1 -0
  62. package/server/dist/modules/mcp-auth.js +62 -1
  63. package/server/dist/modules/mcp-auth.js.map +1 -1
  64. package/server/dist/paths.js +1 -0
  65. package/server/dist/paths.js.map +1 -1
  66. package/server/dist/routes/api.js +2 -8
  67. package/server/dist/routes/api.js.map +1 -1
  68. package/server/dist/routes/client.js +51 -1
  69. package/server/dist/routes/client.js.map +1 -1
  70. package/server/client/dist/assets/Callout-B4kA_Cvr.js +0 -1
  71. package/server/client/dist/assets/Graph-uq5jfJgt.js +0 -1
  72. package/server/client/dist/assets/Image.es-BpAOJjJT.js +0 -1
  73. package/server/client/dist/assets/Note-Bo8ZYRP-.js +0 -22
  74. package/server/client/dist/assets/Plus.es-Dc_js8AC.js +0 -1
  75. package/server/client/dist/assets/Reminders-lj7Equld.js +0 -1
  76. package/server/client/dist/assets/Search-1wcRO5--.js +0 -1
  77. package/server/client/dist/assets/Tag-CeygwFfv.js +0 -1
  78. package/server/client/dist/assets/TagNotes-B8DqItxw.js +0 -1
  79. package/server/client/dist/assets/Trash.es-OuQVb_SX.js +0 -1
  80. package/server/client/dist/assets/ViewNotes-BH7PbdOD.js +0 -1
  81. package/server/client/dist/assets/ViewSectionTableRenderer-BJwhd5be.js +0 -102
  82. package/server/client/dist/assets/Views-eYFtsdFj.js +0 -1
  83. package/server/client/dist/assets/image.api-C0eYGCeH.js +0 -17
  84. package/server/client/dist/assets/index-jIfkRdY_.js +0 -361
  85. package/server/client/dist/assets/manage-image-C0YwkLIP.js +0 -1
  86. package/server/client/dist/assets/mcp-DHHhTLC8.js +0 -1
  87. package/server/client/dist/assets/trash-DA27aW7L.js +0 -1
  88. package/server/dist/features/image/services/remote-fetch.js +0 -176
  89. package/server/dist/features/image/services/remote-fetch.js.map +0 -1
@@ -0,0 +1,105 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { paths } from "../paths.js";
4
+ const OCEAN_BRAIN_RELEASES_URL = "https://github.com/baealex/ocean-brain/releases";
5
+ const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = "X-Ocean-Brain-MCP-Compatibility-Version";
6
+ const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = "X-Ocean-Brain-MCP-Client-Version";
7
+ const normalizeVersion = (version) => version.trim().replace(/^v/i, "");
8
+ const parseMajorMinorVersion = (version) => {
9
+ const match = normalizeVersion(version).match(/^(\d+)\.(\d+)(?:\.\d+)?(?:[-+].*)?$/);
10
+ if (!match) {
11
+ return null;
12
+ }
13
+ return {
14
+ major: Number(match[1]),
15
+ minor: Number(match[2])
16
+ };
17
+ };
18
+ const formatMcpVersionRequirement = (mcpCompatibilityVersion) => {
19
+ const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);
20
+ return parsed ? `${parsed.major}.${parsed.minor}.x` : "unknown";
21
+ };
22
+ const readPackageMetadata = (packageJsonPath) => {
23
+ try {
24
+ return JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
25
+ } catch {
26
+ return void 0;
27
+ }
28
+ };
29
+ const readPackageVersion = (packageJsonPath) => {
30
+ const packageJson = readPackageMetadata(packageJsonPath);
31
+ return typeof packageJson?.version === "string" ? packageJson.version : void 0;
32
+ };
33
+ const readPackageMcpCompatibilityVersion = (packageJsonPath) => {
34
+ const packageJson = readPackageMetadata(packageJsonPath);
35
+ const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;
36
+ return typeof version === "string" ? version : void 0;
37
+ };
38
+ const createMissingMcpCompatibilityVersionError = () => new Error("Ocean Brain MCP compatibility version is required in package metadata.");
39
+ const getVersionCandidatePaths = () => {
40
+ return Array.from(
41
+ /* @__PURE__ */ new Set([
42
+ path.resolve(paths.packageRoot, "..", "package.json"),
43
+ path.resolve(paths.packageRoot, "..", "cli", "package.json"),
44
+ path.resolve(process.cwd(), "packages", "cli", "package.json"),
45
+ path.resolve(process.cwd(), "..", "cli", "package.json")
46
+ ])
47
+ );
48
+ };
49
+ const resolveOceanBrainVersion = () => {
50
+ for (const candidatePath of getVersionCandidatePaths()) {
51
+ const version = readPackageVersion(candidatePath);
52
+ if (version) {
53
+ return normalizeVersion(version);
54
+ }
55
+ }
56
+ if (process.env.OCEAN_BRAIN_VERSION) {
57
+ return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);
58
+ }
59
+ return "0.0.0";
60
+ };
61
+ const resolveMcpCompatibilityVersionFromPaths = (candidatePaths) => {
62
+ for (const candidatePath of candidatePaths) {
63
+ const version = readPackageMcpCompatibilityVersion(candidatePath);
64
+ if (version) {
65
+ return normalizeVersion(version);
66
+ }
67
+ }
68
+ throw createMissingMcpCompatibilityVersionError();
69
+ };
70
+ const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());
71
+ const buildOceanBrainVersionInfo = ({
72
+ mcpCompatibilityVersion,
73
+ version
74
+ }) => {
75
+ const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);
76
+ const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);
77
+ return {
78
+ version: normalizeVersion(version),
79
+ releaseUrl: OCEAN_BRAIN_RELEASES_URL,
80
+ mcpVersionRequirement: compatibilityRequirement,
81
+ mcp: {
82
+ compatibilityVersion,
83
+ compatibilityRequirement,
84
+ compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
85
+ clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER
86
+ }
87
+ };
88
+ };
89
+ const getOceanBrainVersionInfo = () => buildOceanBrainVersionInfo({
90
+ version: resolveOceanBrainVersion(),
91
+ mcpCompatibilityVersion: resolveMcpCompatibilityVersion()
92
+ });
93
+ export {
94
+ OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
95
+ OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
96
+ OCEAN_BRAIN_RELEASES_URL,
97
+ buildOceanBrainVersionInfo,
98
+ formatMcpVersionRequirement,
99
+ getOceanBrainVersionInfo,
100
+ parseMajorMinorVersion,
101
+ resolveMcpCompatibilityVersion,
102
+ resolveMcpCompatibilityVersionFromPaths,
103
+ resolveOceanBrainVersion
104
+ };
105
+ //# sourceMappingURL=app-version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/modules/app-version.ts"],"sourcesContent":["import fs from 'fs';\nimport path from 'path';\n\nimport { paths } from '~/paths.js';\n\nexport const OCEAN_BRAIN_RELEASES_URL = 'https://github.com/baealex/ocean-brain/releases';\nexport const OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER = 'X-Ocean-Brain-MCP-Compatibility-Version';\nexport const OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER = 'X-Ocean-Brain-MCP-Client-Version';\n\ninterface MajorMinorVersion {\n major: number;\n minor: number;\n}\n\ninterface PackageMetadata {\n oceanBrain?: {\n mcpCompatibilityVersion?: unknown;\n };\n version?: unknown;\n}\n\nexport interface OceanBrainVersionInfo {\n version: string;\n releaseUrl: string;\n mcpVersionRequirement: string;\n mcp: {\n compatibilityVersion: string;\n compatibilityRequirement: string;\n compatibilityVersionHeader: string;\n clientVersionHeader: string;\n };\n}\n\nconst normalizeVersion = (version: string) => version.trim().replace(/^v/i, '');\n\nexport const parseMajorMinorVersion = (version: string): MajorMinorVersion | null => {\n const match = normalizeVersion(version).match(/^(\\d+)\\.(\\d+)(?:\\.\\d+)?(?:[-+].*)?$/);\n\n if (!match) {\n return null;\n }\n\n return {\n major: Number(match[1]),\n minor: Number(match[2]),\n };\n};\n\nexport const formatMcpVersionRequirement = (mcpCompatibilityVersion: string) => {\n const parsed = parseMajorMinorVersion(mcpCompatibilityVersion);\n return parsed ? `${parsed.major}.${parsed.minor}.x` : 'unknown';\n};\n\nconst readPackageMetadata = (packageJsonPath: string): PackageMetadata | undefined => {\n try {\n return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')) as PackageMetadata;\n } catch {\n return undefined;\n }\n};\n\nconst readPackageVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n return typeof packageJson?.version === 'string' ? packageJson.version : undefined;\n};\n\nconst readPackageMcpCompatibilityVersion = (packageJsonPath: string) => {\n const packageJson = readPackageMetadata(packageJsonPath);\n const version = packageJson?.oceanBrain?.mcpCompatibilityVersion;\n return typeof version === 'string' ? version : undefined;\n};\n\nconst createMissingMcpCompatibilityVersionError = () =>\n new Error('Ocean Brain MCP compatibility version is required in package metadata.');\n\nconst getVersionCandidatePaths = () => {\n return Array.from(\n new Set([\n path.resolve(paths.packageRoot, '..', 'package.json'),\n path.resolve(paths.packageRoot, '..', 'cli', 'package.json'),\n path.resolve(process.cwd(), 'packages', 'cli', 'package.json'),\n path.resolve(process.cwd(), '..', 'cli', 'package.json'),\n ]),\n );\n};\n\nexport const resolveOceanBrainVersion = () => {\n for (const candidatePath of getVersionCandidatePaths()) {\n const version = readPackageVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n if (process.env.OCEAN_BRAIN_VERSION) {\n return normalizeVersion(process.env.OCEAN_BRAIN_VERSION);\n }\n\n return '0.0.0';\n};\n\nexport const resolveMcpCompatibilityVersionFromPaths = (candidatePaths: string[]) => {\n for (const candidatePath of candidatePaths) {\n const version = readPackageMcpCompatibilityVersion(candidatePath);\n if (version) {\n return normalizeVersion(version);\n }\n }\n\n throw createMissingMcpCompatibilityVersionError();\n};\n\nexport const resolveMcpCompatibilityVersion = () => resolveMcpCompatibilityVersionFromPaths(getVersionCandidatePaths());\n\nexport const buildOceanBrainVersionInfo = ({\n mcpCompatibilityVersion,\n version,\n}: {\n mcpCompatibilityVersion: string;\n version: string;\n}): OceanBrainVersionInfo => {\n const compatibilityVersion = normalizeVersion(mcpCompatibilityVersion);\n const compatibilityRequirement = formatMcpVersionRequirement(compatibilityVersion);\n\n return {\n version: normalizeVersion(version),\n releaseUrl: OCEAN_BRAIN_RELEASES_URL,\n mcpVersionRequirement: compatibilityRequirement,\n mcp: {\n compatibilityVersion,\n compatibilityRequirement,\n compatibilityVersionHeader: OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n clientVersionHeader: OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n },\n };\n};\n\nexport const getOceanBrainVersionInfo = () =>\n buildOceanBrainVersionInfo({\n version: resolveOceanBrainVersion(),\n mcpCompatibilityVersion: resolveMcpCompatibilityVersion(),\n });\n"],"mappings":"AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AAEjB,SAAS,aAAa;AAEf,MAAM,2BAA2B;AACjC,MAAM,+CAA+C;AACrD,MAAM,wCAAwC;AA0BrD,MAAM,mBAAmB,CAAC,YAAoB,QAAQ,KAAK,EAAE,QAAQ,OAAO,EAAE;AAEvE,MAAM,yBAAyB,CAAC,YAA8C;AACjF,QAAM,QAAQ,iBAAiB,OAAO,EAAE,MAAM,qCAAqC;AAEnF,MAAI,CAAC,OAAO;AACR,WAAO;AAAA,EACX;AAEA,SAAO;AAAA,IACH,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,IACtB,OAAO,OAAO,MAAM,CAAC,CAAC;AAAA,EAC1B;AACJ;AAEO,MAAM,8BAA8B,CAAC,4BAAoC;AAC5E,QAAM,SAAS,uBAAuB,uBAAuB;AAC7D,SAAO,SAAS,GAAG,OAAO,KAAK,IAAI,OAAO,KAAK,OAAO;AAC1D;AAEA,MAAM,sBAAsB,CAAC,oBAAyD;AAClF,MAAI;AACA,WAAO,KAAK,MAAM,GAAG,aAAa,iBAAiB,OAAO,CAAC;AAAA,EAC/D,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAEA,MAAM,qBAAqB,CAAC,oBAA4B;AACpD,QAAM,cAAc,oBAAoB,eAAe;AACvD,SAAO,OAAO,aAAa,YAAY,WAAW,YAAY,UAAU;AAC5E;AAEA,MAAM,qCAAqC,CAAC,oBAA4B;AACpE,QAAM,cAAc,oBAAoB,eAAe;AACvD,QAAM,UAAU,aAAa,YAAY;AACzC,SAAO,OAAO,YAAY,WAAW,UAAU;AACnD;AAEA,MAAM,4CAA4C,MAC9C,IAAI,MAAM,wEAAwE;AAEtF,MAAM,2BAA2B,MAAM;AACnC,SAAO,MAAM;AAAA,IACT,oBAAI,IAAI;AAAA,MACJ,KAAK,QAAQ,MAAM,aAAa,MAAM,cAAc;AAAA,MACpD,KAAK,QAAQ,MAAM,aAAa,MAAM,OAAO,cAAc;AAAA,MAC3D,KAAK,QAAQ,QAAQ,IAAI,GAAG,YAAY,OAAO,cAAc;AAAA,MAC7D,KAAK,QAAQ,QAAQ,IAAI,GAAG,MAAM,OAAO,cAAc;AAAA,IAC3D,CAAC;AAAA,EACL;AACJ;AAEO,MAAM,2BAA2B,MAAM;AAC1C,aAAW,iBAAiB,yBAAyB,GAAG;AACpD,UAAM,UAAU,mBAAmB,aAAa;AAChD,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,MAAI,QAAQ,IAAI,qBAAqB;AACjC,WAAO,iBAAiB,QAAQ,IAAI,mBAAmB;AAAA,EAC3D;AAEA,SAAO;AACX;AAEO,MAAM,0CAA0C,CAAC,mBAA6B;AACjF,aAAW,iBAAiB,gBAAgB;AACxC,UAAM,UAAU,mCAAmC,aAAa;AAChE,QAAI,SAAS;AACT,aAAO,iBAAiB,OAAO;AAAA,IACnC;AAAA,EACJ;AAEA,QAAM,0CAA0C;AACpD;AAEO,MAAM,iCAAiC,MAAM,wCAAwC,yBAAyB,CAAC;AAE/G,MAAM,6BAA6B,CAAC;AAAA,EACvC;AAAA,EACA;AACJ,MAG6B;AACzB,QAAM,uBAAuB,iBAAiB,uBAAuB;AACrE,QAAM,2BAA2B,4BAA4B,oBAAoB;AAEjF,SAAO;AAAA,IACH,SAAS,iBAAiB,OAAO;AAAA,IACjC,YAAY;AAAA,IACZ,uBAAuB;AAAA,IACvB,KAAK;AAAA,MACD;AAAA,MACA;AAAA,MACA,4BAA4B;AAAA,MAC5B,qBAAqB;AAAA,IACzB;AAAA,EACJ;AACJ;AAEO,MAAM,2BAA2B,MACpC,2BAA2B;AAAA,EACvB,SAAS,yBAAyB;AAAA,EAClC,yBAAyB,+BAA+B;AAC5D,CAAC;","names":[]}
@@ -1,5 +1,42 @@
1
1
  import { GraphQLError } from "graphql";
2
+ import {
3
+ getOceanBrainVersionInfo,
4
+ OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,
5
+ OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,
6
+ parseMajorMinorVersion
7
+ } from "./app-version.js";
2
8
  const JSON_HEADERS = { "Content-Type": "application/json" };
9
+ const OCEAN_BRAIN_MCP_VERSION_HEADER = "X-Ocean-Brain-MCP-Version";
10
+ const readHeaderValue = (req, headerName) => {
11
+ const value = req.headers[headerName.toLowerCase()];
12
+ return Array.isArray(value) ? value[0] : value;
13
+ };
14
+ const readMcpCompatibilityVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
15
+ const readMcpClientVersion = (req) => readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);
16
+ const isMcpVersionCompatible = (requiredMcpCompatibilityVersion, mcpCompatibilityVersion) => {
17
+ const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);
18
+ const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);
19
+ return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);
20
+ };
21
+ const createMcpVersionCompatibilityMessage = ({
22
+ mcpClientVersion,
23
+ mcpCompatibilityVersion,
24
+ requiredMcpCompatibilityVersion,
25
+ serverVersion
26
+ }) => {
27
+ const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : "not provided";
28
+ const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : "not provided";
29
+ return [
30
+ "Ocean Brain MCP compatibility versions are incompatible.",
31
+ `Server: v${serverVersion}`,
32
+ `Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,
33
+ `MCP compatibility: ${mcpCompatibilityVersionText}`,
34
+ `MCP client: ${mcpClientVersionText}`,
35
+ "",
36
+ "Please update Ocean Brain MCP to a compatible release.",
37
+ getOceanBrainVersionInfo().releaseUrl
38
+ ].join("\n");
39
+ };
3
40
  const readBearerToken = (authorizationHeader) => {
4
41
  if (!authorizationHeader?.startsWith("Bearer ")) {
5
42
  return void 0;
@@ -40,6 +77,28 @@ const createMcpAuthMiddleware = (_authConfig, mcpAdminAuth) => {
40
77
  }).end();
41
78
  return;
42
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
+ }),
92
+ serverVersion: versionInfo.version,
93
+ mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,
94
+ mcpClientVersion: mcpClientVersion ?? null,
95
+ mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,
96
+ requiredMcpVersion: versionInfo.mcpVersionRequirement,
97
+ requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,
98
+ releaseUrl: versionInfo.releaseUrl
99
+ }).end();
100
+ return;
101
+ }
43
102
  next();
44
103
  } catch (error) {
45
104
  next(error);
@@ -64,7 +123,9 @@ const createReadOnlyMcpValidationRule = () => {
64
123
  };
65
124
  };
66
125
  export {
126
+ OCEAN_BRAIN_MCP_VERSION_HEADER,
67
127
  createMcpAuthMiddleware,
68
- createReadOnlyMcpValidationRule
128
+ createReadOnlyMcpValidationRule,
129
+ isMcpVersionCompatible
69
130
  };
70
131
  //# sourceMappingURL=mcp-auth.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;AAc7B,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AAE1D,MAAM,kBAAkB,CAAC,wBAAiC;AACtD,MAAI,CAAC,qBAAqB,WAAW,SAAS,GAAG;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,oBAAoB,MAAM,UAAU,MAAM,EAAE,KAAK,KAAK;AACjE;AAEO,MAAM,0BAA0B,CAAC,aAAyB,iBAAmD;AAChH,SAAO,OAAO,KAAc,KAAe,SAAuB;AAC9D,QAAI;AACA,YAAM,SAAS,MAAM,aAAa,UAAU;AAC5C,UAAI,CAAC,OAAO,SAAS;AACjB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,cAAc,gBAAgB,IAAI,QAAQ,aAAa;AAE7D,UAAI,CAAC,aAAa;AACd,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,aAAa,MAAM,aAAa,uBAAuB,WAAW;AAExE,UAAI,CAAC,WAAW,MAAM,WAAW,WAAW,kBAAkB;AAC1D,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,UAAI,CAAC,WAAW,IAAI;AAChB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,SAAS,OAAO;AACZ,WAAK,KAAK;AAAA,IACd;AAAA,EACJ;AACJ;AAEO,MAAM,kCAAkC,MAAsB;AACjE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,SAAS;AAC5B;AAAA,QACJ;AAEA,gBAAQ;AAAA,UACJ,IAAI,aAAa,6BAA6B;AAAA,YAC1C,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,YAAY;AAAA,UACpC,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/mcp-auth.ts"],"sourcesContent":["import type { NextFunction, Request, RequestHandler, Response } from 'express';\nimport type { ValidationRule } from 'graphql';\nimport { GraphQLError } from 'graphql';\n\nimport {\n getOceanBrainVersionInfo,\n OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER,\n OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER,\n parseMajorMinorVersion,\n} from './app-version.js';\nimport type { AuthConfig } from './auth-mode.js';\n\nexport interface McpTokenValidationResult {\n ok: boolean;\n reason?: 'not_configured' | 'forbidden';\n}\n\nexport interface McpAdminAuthPort {\n getStatus: () => Promise<{ enabled: boolean }>;\n validatePresentedToken: (token: string) => Promise<McpTokenValidationResult>;\n}\n\nconst JSON_HEADERS = { 'Content-Type': 'application/json' };\nexport const OCEAN_BRAIN_MCP_VERSION_HEADER = 'X-Ocean-Brain-MCP-Version';\n\nconst readHeaderValue = (req: Request, headerName: string) => {\n const value = req.headers[headerName.toLowerCase()];\n return Array.isArray(value) ? value[0] : value;\n};\n\nconst readMcpCompatibilityVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_COMPATIBILITY_VERSION_HEADER) ??\n readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nconst readMcpClientVersion = (req: Request) =>\n readHeaderValue(req, OCEAN_BRAIN_MCP_CLIENT_VERSION_HEADER) ?? readHeaderValue(req, OCEAN_BRAIN_MCP_VERSION_HEADER);\n\nexport const isMcpVersionCompatible = (requiredMcpCompatibilityVersion: string, mcpCompatibilityVersion: string) => {\n const required = parseMajorMinorVersion(requiredMcpCompatibilityVersion);\n const mcp = parseMajorMinorVersion(mcpCompatibilityVersion);\n\n return Boolean(required && mcp && required.major === mcp.major && required.minor === mcp.minor);\n};\n\nconst createMcpVersionCompatibilityMessage = ({\n mcpClientVersion,\n mcpCompatibilityVersion,\n requiredMcpCompatibilityVersion,\n serverVersion,\n}: {\n mcpClientVersion?: string;\n mcpCompatibilityVersion?: string;\n requiredMcpCompatibilityVersion: string;\n serverVersion: string;\n}) => {\n const mcpCompatibilityVersionText = mcpCompatibilityVersion ? `v${mcpCompatibilityVersion}` : 'not provided';\n const mcpClientVersionText = mcpClientVersion ? `v${mcpClientVersion}` : 'not provided';\n\n return [\n 'Ocean Brain MCP compatibility versions are incompatible.',\n `Server: v${serverVersion}`,\n `Required MCP compatibility: ${requiredMcpCompatibilityVersion}`,\n `MCP compatibility: ${mcpCompatibilityVersionText}`,\n `MCP client: ${mcpClientVersionText}`,\n '',\n 'Please update Ocean Brain MCP to a compatible release.',\n getOceanBrainVersionInfo().releaseUrl,\n ].join('\\n');\n};\n\nconst readBearerToken = (authorizationHeader?: string) => {\n if (!authorizationHeader?.startsWith('Bearer ')) {\n return undefined;\n }\n\n return authorizationHeader.slice('Bearer '.length).trim() || undefined;\n};\n\nexport const createMcpAuthMiddleware = (_authConfig: AuthConfig, mcpAdminAuth: McpAdminAuthPort): RequestHandler => {\n return async (req: Request, res: Response, next: NextFunction) => {\n try {\n const status = await mcpAdminAuth.getStatus();\n if (!status.enabled) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_DISABLED',\n message: 'MCP access is disabled by admin.',\n })\n .end();\n return;\n }\n\n const bearerToken = readBearerToken(req.headers.authorization);\n\n if (!bearerToken) {\n res.status(401)\n .set(JSON_HEADERS)\n .json({\n code: 'UNAUTHORIZED',\n message: 'A valid MCP bearer token is required.',\n })\n .end();\n return;\n }\n\n const validation = await mcpAdminAuth.validatePresentedToken(bearerToken);\n\n if (!validation.ok && validation.reason === 'not_configured') {\n res.status(503)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_AUTH_NOT_CONFIGURED',\n message: 'MCP bearer auth is not configured.',\n })\n .end();\n return;\n }\n\n if (!validation.ok) {\n res.status(403)\n .set(JSON_HEADERS)\n .json({\n code: 'FORBIDDEN',\n message: 'Invalid MCP bearer token.',\n })\n .end();\n return;\n }\n\n const versionInfo = getOceanBrainVersionInfo();\n const mcpCompatibilityVersion = readMcpCompatibilityVersion(req);\n const mcpClientVersion = readMcpClientVersion(req);\n\n if (\n !mcpCompatibilityVersion ||\n !isMcpVersionCompatible(versionInfo.mcp.compatibilityVersion, mcpCompatibilityVersion)\n ) {\n res.status(426)\n .set(JSON_HEADERS)\n .json({\n code: 'MCP_VERSION_INCOMPATIBLE',\n message: createMcpVersionCompatibilityMessage({\n serverVersion: versionInfo.version,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n mcpCompatibilityVersion,\n mcpClientVersion,\n }),\n serverVersion: versionInfo.version,\n mcpVersion: mcpClientVersion ?? mcpCompatibilityVersion ?? null,\n mcpClientVersion: mcpClientVersion ?? null,\n mcpCompatibilityVersion: mcpCompatibilityVersion ?? null,\n requiredMcpVersion: versionInfo.mcpVersionRequirement,\n requiredMcpCompatibilityVersion: versionInfo.mcp.compatibilityRequirement,\n releaseUrl: versionInfo.releaseUrl,\n })\n .end();\n return;\n }\n\n next();\n } catch (error) {\n next(error);\n }\n };\n};\n\nexport const createReadOnlyMcpValidationRule = (): ValidationRule => {\n return (context) => {\n return {\n OperationDefinition(node) {\n if (node.operation === 'query') {\n return;\n }\n\n context.reportError(\n new GraphQLError('MCP endpoint is read-only', {\n nodes: [node],\n extensions: { code: 'FORBIDDEN' },\n }),\n );\n },\n };\n };\n};\n"],"mappings":"AAEA,SAAS,oBAAoB;AAE7B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAaP,MAAM,eAAe,EAAE,gBAAgB,mBAAmB;AACnD,MAAM,iCAAiC;AAE9C,MAAM,kBAAkB,CAAC,KAAc,eAAuB;AAC1D,QAAM,QAAQ,IAAI,QAAQ,WAAW,YAAY,CAAC;AAClD,SAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAC7C;AAEA,MAAM,8BAA8B,CAAC,QACjC,gBAAgB,KAAK,4CAA4C,KACjE,gBAAgB,KAAK,8BAA8B;AAEvD,MAAM,uBAAuB,CAAC,QAC1B,gBAAgB,KAAK,qCAAqC,KAAK,gBAAgB,KAAK,8BAA8B;AAE/G,MAAM,yBAAyB,CAAC,iCAAyC,4BAAoC;AAChH,QAAM,WAAW,uBAAuB,+BAA+B;AACvE,QAAM,MAAM,uBAAuB,uBAAuB;AAE1D,SAAO,QAAQ,YAAY,OAAO,SAAS,UAAU,IAAI,SAAS,SAAS,UAAU,IAAI,KAAK;AAClG;AAEA,MAAM,uCAAuC,CAAC;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACJ,MAKM;AACF,QAAM,8BAA8B,0BAA0B,IAAI,uBAAuB,KAAK;AAC9F,QAAM,uBAAuB,mBAAmB,IAAI,gBAAgB,KAAK;AAEzE,SAAO;AAAA,IACH;AAAA,IACA,YAAY,aAAa;AAAA,IACzB,+BAA+B,+BAA+B;AAAA,IAC9D,sBAAsB,2BAA2B;AAAA,IACjD,eAAe,oBAAoB;AAAA,IACnC;AAAA,IACA;AAAA,IACA,yBAAyB,EAAE;AAAA,EAC/B,EAAE,KAAK,IAAI;AACf;AAEA,MAAM,kBAAkB,CAAC,wBAAiC;AACtD,MAAI,CAAC,qBAAqB,WAAW,SAAS,GAAG;AAC7C,WAAO;AAAA,EACX;AAEA,SAAO,oBAAoB,MAAM,UAAU,MAAM,EAAE,KAAK,KAAK;AACjE;AAEO,MAAM,0BAA0B,CAAC,aAAyB,iBAAmD;AAChH,SAAO,OAAO,KAAc,KAAe,SAAuB;AAC9D,QAAI;AACA,YAAM,SAAS,MAAM,aAAa,UAAU;AAC5C,UAAI,CAAC,OAAO,SAAS;AACjB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,cAAc,gBAAgB,IAAI,QAAQ,aAAa;AAE7D,UAAI,CAAC,aAAa;AACd,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,aAAa,MAAM,aAAa,uBAAuB,WAAW;AAExE,UAAI,CAAC,WAAW,MAAM,WAAW,WAAW,kBAAkB;AAC1D,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,UAAI,CAAC,WAAW,IAAI;AAChB,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS;AAAA,QACb,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,YAAM,cAAc,yBAAyB;AAC7C,YAAM,0BAA0B,4BAA4B,GAAG;AAC/D,YAAM,mBAAmB,qBAAqB,GAAG;AAEjD,UACI,CAAC,2BACD,CAAC,uBAAuB,YAAY,IAAI,sBAAsB,uBAAuB,GACvF;AACE,YAAI,OAAO,GAAG,EACT,IAAI,YAAY,EAChB,KAAK;AAAA,UACF,MAAM;AAAA,UACN,SAAS,qCAAqC;AAAA,YAC1C,eAAe,YAAY;AAAA,YAC3B,iCAAiC,YAAY,IAAI;AAAA,YACjD;AAAA,YACA;AAAA,UACJ,CAAC;AAAA,UACD,eAAe,YAAY;AAAA,UAC3B,YAAY,oBAAoB,2BAA2B;AAAA,UAC3D,kBAAkB,oBAAoB;AAAA,UACtC,yBAAyB,2BAA2B;AAAA,UACpD,oBAAoB,YAAY;AAAA,UAChC,iCAAiC,YAAY,IAAI;AAAA,UACjD,YAAY,YAAY;AAAA,QAC5B,CAAC,EACA,IAAI;AACT;AAAA,MACJ;AAEA,WAAK;AAAA,IACT,SAAS,OAAO;AACZ,WAAK,KAAK;AAAA,IACd;AAAA,EACJ;AACJ;AAEO,MAAM,kCAAkC,MAAsB;AACjE,SAAO,CAAC,YAAY;AAChB,WAAO;AAAA,MACH,oBAAoB,MAAM;AACtB,YAAI,KAAK,cAAc,SAAS;AAC5B;AAAA,QACJ;AAEA,gBAAQ;AAAA,UACJ,IAAI,aAAa,6BAA6B;AAAA,YAC1C,OAAO,CAAC,IAAI;AAAA,YACZ,YAAY,EAAE,MAAM,YAAY;AAAA,UACpC,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ;AAAA,EACJ;AACJ;","names":[]}
@@ -4,6 +4,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
4
4
  const PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, "..");
5
5
  const IMAGE_DIR = process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || ".", "assets/images");
6
6
  const paths = {
7
+ packageRoot: path.resolve(PACKAGE_ROOT),
7
8
  clientDist: path.resolve(PACKAGE_ROOT, "client/dist"),
8
9
  clientIndex: path.resolve(PACKAGE_ROOT, "client/dist/index.html"),
9
10
  imageDir: path.resolve(IMAGE_DIR)
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR =\n process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR),\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,IAAI;AAGzF,MAAM,YACF,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAEvG,MAAM,QAAQ;AAAA,EACjB,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
1
+ {"version":3,"sources":["../src/paths.ts"],"sourcesContent":["import path from 'path';\nimport { fileURLToPath } from 'url';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\n\n// 패키지 루트 (dist/paths.js 기준 상위)\nconst PACKAGE_ROOT = process.env.OCEAN_BRAIN_PACKAGE_ROOT || path.resolve(__dirname, '..');\n\n// 이미지 디렉토리 (별도 지정 가능, Docker에서 분리 볼륨용)\nconst IMAGE_DIR =\n process.env.OCEAN_BRAIN_IMAGE_DIR || path.resolve(process.env.OCEAN_BRAIN_DATA_DIR || '.', 'assets/images');\n\nexport const paths = {\n packageRoot: path.resolve(PACKAGE_ROOT),\n clientDist: path.resolve(PACKAGE_ROOT, 'client/dist'),\n clientIndex: path.resolve(PACKAGE_ROOT, 'client/dist/index.html'),\n imageDir: path.resolve(IMAGE_DIR),\n};\n"],"mappings":"AAAA,OAAO,UAAU;AACjB,SAAS,qBAAqB;AAE9B,MAAM,YAAY,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAG7D,MAAM,eAAe,QAAQ,IAAI,4BAA4B,KAAK,QAAQ,WAAW,IAAI;AAGzF,MAAM,YACF,QAAQ,IAAI,yBAAyB,KAAK,QAAQ,QAAQ,IAAI,wBAAwB,KAAK,eAAe;AAEvG,MAAM,QAAQ;AAAA,EACjB,aAAa,KAAK,QAAQ,YAAY;AAAA,EACtC,YAAY,KAAK,QAAQ,cAAc,aAAa;AAAA,EACpD,aAAa,KAAK,QAAQ,cAAc,wBAAwB;AAAA,EAChE,UAAU,KAAK,QAAQ,SAAS;AACpC;","names":[]}
@@ -1,6 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from "../features/auth/http/api.js";
3
- import { createUploadImageFromSrcHandler, createUploadImageHandler } from "../features/image/http/upload.js";
3
+ import { createUploadImageHandler } from "../features/image/http/upload.js";
4
4
  import {
5
5
  createMcpAdminRevokeTokenHandler,
6
6
  createMcpAdminRotateTokenHandler,
@@ -46,13 +46,7 @@ const createApiRouter = (authConfig, mcpAdminService) => {
46
46
  requireSession,
47
47
  csrfProtection,
48
48
  useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
49
- ).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).post(
50
- "/image-from-src",
51
- sessionAccessRateLimit,
52
- requireSession,
53
- csrfProtection,
54
- useAsync(createUploadImageFromSrcHandler())
55
- ).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
49
+ ).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
56
50
  };
57
51
  export {
58
52
  createApiRouter
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/api.ts"],"sourcesContent":["import { Router } from 'express';\nimport { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from '../features/auth/http/api.js';\nimport { createUploadImageFromSrcHandler, createUploadImageHandler } from '../features/image/http/upload.js';\nimport {\n createMcpAdminRevokeTokenHandler,\n createMcpAdminRotateTokenHandler,\n createMcpAdminSetEnabledHandler,\n createMcpAdminStatusHandler,\n} from '../features/mcp-admin/http/handlers.js';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { createCsrfProtection, requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit, createSessionAccessRateLimit } from '../modules/rate-limit.js';\nimport { createServerEventsHandler } from '../modules/server-events-handler.js';\nimport useAsync from '../modules/use-async.js';\nimport { createMcpRouter } from './mcp.js';\n\ntype McpAdminApiService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken' | 'validatePresentedToken'\n>;\n\nexport const createApiRouter = (authConfig: AuthConfig, mcpAdminService: McpAdminApiService) => {\n const csrfProtection = createCsrfProtection(authConfig);\n const requireSession = requireSessionForWrite(authConfig);\n const sessionAccessRateLimit = createSessionAccessRateLimit();\n\n return Router()\n .use('/mcp', createMcpRouter(authConfig, mcpAdminService))\n .get('/auth/session', csrfProtection, useAsync(createSessionStatusHandler(authConfig)))\n .post('/auth/login', csrfProtection, createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig)))\n .post(\n '/auth/logout',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createLogoutHandler(authConfig)),\n )\n .get(\n '/mcp-admin/status',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminStatusHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/enabled',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminSetEnabledHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/rotate',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRotateTokenHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/revoke',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService)),\n )\n .post('/image', sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler()))\n .post(\n '/image-from-src',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createUploadImageFromSrcHandler()),\n )\n .get('/events', sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());\n};\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,oBAAoB,qBAAqB,kCAAkC;AACpF,SAAS,iCAAiC,gCAAgC;AAC1E;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEP,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,4BAA4B,oCAAoC;AACzE,SAAS,iCAAiC;AAC1C,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAOzB,MAAM,kBAAkB,CAAC,YAAwB,oBAAwC;AAC5F,QAAM,iBAAiB,qBAAqB,UAAU;AACtD,QAAM,iBAAiB,uBAAuB,UAAU;AACxD,QAAM,yBAAyB,6BAA6B;AAE5D,SAAO,OAAO,EACT,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,IAAI,iBAAiB,gBAAgB,SAAS,2BAA2B,UAAU,CAAC,CAAC,EACrF,KAAK,eAAe,gBAAgB,2BAA2B,GAAG,SAAS,mBAAmB,UAAU,CAAC,CAAC,EAC1G;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,oBAAoB,UAAU,CAAC;AAAA,EAC5C,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,4BAA4B,eAAe,CAAC;AAAA,EACzD,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,gCAAgC,eAAe,CAAC;AAAA,EAC7D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC,KAAK,UAAU,wBAAwB,gBAAgB,gBAAgB,SAAS,yBAAyB,CAAC,CAAC,EAC3G;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,gCAAgC,CAAC;AAAA,EAC9C,EACC,IAAI,WAAW,wBAAwB,gBAAgB,gBAAgB,0BAA0B,CAAC;AAC3G;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/api.ts"],"sourcesContent":["import { Router } from 'express';\nimport { createLoginHandler, createLogoutHandler, createSessionStatusHandler } from '../features/auth/http/api.js';\nimport { createUploadImageHandler } from '../features/image/http/upload.js';\nimport {\n createMcpAdminRevokeTokenHandler,\n createMcpAdminRotateTokenHandler,\n createMcpAdminSetEnabledHandler,\n createMcpAdminStatusHandler,\n} from '../features/mcp-admin/http/handlers.js';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { createCsrfProtection, requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit, createSessionAccessRateLimit } from '../modules/rate-limit.js';\nimport { createServerEventsHandler } from '../modules/server-events-handler.js';\nimport useAsync from '../modules/use-async.js';\nimport { createMcpRouter } from './mcp.js';\n\ntype McpAdminApiService = Pick<\n McpAdminService,\n 'getStatus' | 'setEnabled' | 'rotateToken' | 'revokeActiveToken' | 'validatePresentedToken'\n>;\n\nexport const createApiRouter = (authConfig: AuthConfig, mcpAdminService: McpAdminApiService) => {\n const csrfProtection = createCsrfProtection(authConfig);\n const requireSession = requireSessionForWrite(authConfig);\n const sessionAccessRateLimit = createSessionAccessRateLimit();\n\n return Router()\n .use('/mcp', createMcpRouter(authConfig, mcpAdminService))\n .get('/auth/session', csrfProtection, useAsync(createSessionStatusHandler(authConfig)))\n .post('/auth/login', csrfProtection, createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig)))\n .post(\n '/auth/logout',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createLogoutHandler(authConfig)),\n )\n .get(\n '/mcp-admin/status',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminStatusHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/enabled',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminSetEnabledHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/rotate',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRotateTokenHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/revoke',\n sessionAccessRateLimit,\n requireSession,\n csrfProtection,\n useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService)),\n )\n .post('/image', sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler()))\n .get('/events', sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());\n};\n"],"mappings":"AAAA,SAAS,cAAc;AACvB,SAAS,oBAAoB,qBAAqB,kCAAkC;AACpF,SAAS,gCAAgC;AACzC;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEP,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,4BAA4B,oCAAoC;AACzE,SAAS,iCAAiC;AAC1C,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAOzB,MAAM,kBAAkB,CAAC,YAAwB,oBAAwC;AAC5F,QAAM,iBAAiB,qBAAqB,UAAU;AACtD,QAAM,iBAAiB,uBAAuB,UAAU;AACxD,QAAM,yBAAyB,6BAA6B;AAE5D,SAAO,OAAO,EACT,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,IAAI,iBAAiB,gBAAgB,SAAS,2BAA2B,UAAU,CAAC,CAAC,EACrF,KAAK,eAAe,gBAAgB,2BAA2B,GAAG,SAAS,mBAAmB,UAAU,CAAC,CAAC,EAC1G;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,oBAAoB,UAAU,CAAC;AAAA,EAC5C,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,4BAA4B,eAAe,CAAC;AAAA,EACzD,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,gCAAgC,eAAe,CAAC;AAAA,EAC7D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC;AAAA,IACG;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS,iCAAiC,eAAe,CAAC;AAAA,EAC9D,EACC,KAAK,UAAU,wBAAwB,gBAAgB,gBAAgB,SAAS,yBAAyB,CAAC,CAAC,EAC3G,IAAI,WAAW,wBAAwB,gBAAgB,gBAAgB,0BAA0B,CAAC;AAC3G;","names":[]}
@@ -1,7 +1,9 @@
1
1
  import express, { Router } from "express";
2
+ import { rateLimit } from "express-rate-limit";
2
3
  import path from "path";
3
4
  import { createCsrfProtection, isAuthenticatedRequest } from "../modules/auth-guard.js";
4
5
  import { paths } from "../paths.js";
6
+ const IMAGE_ASSET_RATE_LIMIT_MESSAGE = "Too many image asset requests. Please try again later.";
5
7
  const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
6
8
  if (authConfig.mode !== "password" || authenticated) {
7
9
  return false;
@@ -11,6 +13,35 @@ const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
11
13
  }
12
14
  return path.extname(requestPath) === "";
13
15
  };
16
+ const createImageAssetAuthRateLimit = (authConfig) => rateLimit({
17
+ windowMs: 15 * 60 * 1e3,
18
+ limit: 10,
19
+ standardHeaders: true,
20
+ legacyHeaders: false,
21
+ skip: (req) => authConfig.mode !== "password" || isAuthenticatedRequest(req),
22
+ handler: (_req, res) => {
23
+ res.setHeader("Cache-Control", "no-store");
24
+ res.status(429).json({
25
+ code: "IMAGE_ASSET_RATE_LIMITED",
26
+ message: IMAGE_ASSET_RATE_LIMIT_MESSAGE
27
+ });
28
+ }
29
+ });
30
+ const createProtectedImageAssetsMiddleware = (authConfig) => {
31
+ return (req, res, next) => {
32
+ if (authConfig.mode !== "password" || isAuthenticatedRequest(req)) {
33
+ next();
34
+ return;
35
+ }
36
+ res.setHeader("Cache-Control", "no-store");
37
+ if (req.headers.accept?.includes("text/html")) {
38
+ const redirectPath = encodeURIComponent(req.originalUrl || req.url || "/");
39
+ res.redirect(303, `/login?next=${redirectPath}`);
40
+ return;
41
+ }
42
+ res.status(401).end();
43
+ };
44
+ };
14
45
  const createClientRouteCsrfTokenMiddleware = (authConfig) => {
15
46
  const csrfProtection = createCsrfProtection(authConfig);
16
47
  return (req, res, next) => {
@@ -21,7 +52,26 @@ const createClientRouteCsrfTokenMiddleware = (authConfig) => {
21
52
  csrfProtection(req, res, next);
22
53
  };
23
54
  };
24
- const createClientRouter = (authConfig) => Router().use("/assets/images", express.static(paths.imageDir)).use((req, res, next) => {
55
+ const createClientRouter = (authConfig) => Router().use(
56
+ "/assets/images",
57
+ createImageAssetAuthRateLimit(authConfig),
58
+ createProtectedImageAssetsMiddleware(authConfig),
59
+ express.static(paths.imageDir, {
60
+ setHeaders: (res) => {
61
+ res.setHeader("X-Content-Type-Options", "nosniff");
62
+ if (authConfig.mode === "password") {
63
+ res.setHeader("Cache-Control", "no-store");
64
+ }
65
+ }
66
+ }),
67
+ (_req, res) => {
68
+ res.setHeader("X-Content-Type-Options", "nosniff");
69
+ if (authConfig.mode === "password") {
70
+ res.setHeader("Cache-Control", "no-store");
71
+ }
72
+ res.status(404).end();
73
+ }
74
+ ).use((req, res, next) => {
25
75
  if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {
26
76
  const redirectPath = encodeURIComponent(req.originalUrl || "/");
27
77
  res.redirect(303, `/login?next=${redirectPath}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { Router } from 'express';\nimport path from 'path';\nimport { createCsrfProtection, isAuthenticatedRequest } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { paths } from '../paths.js';\n\nconst shouldBlockClientRoute = (authConfig: AuthConfig, requestPath: string, authenticated: boolean) => {\n if (authConfig.mode !== 'password' || authenticated) {\n return false;\n }\n\n if (\n requestPath.startsWith('/api') ||\n requestPath.startsWith('/graphql') ||\n requestPath === '/login' ||\n requestPath === '/logout'\n ) {\n return false;\n }\n\n return path.extname(requestPath) === '';\n};\n\nconst createClientRouteCsrfTokenMiddleware = (authConfig: AuthConfig) => {\n const csrfProtection = createCsrfProtection(authConfig);\n\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (path.extname(req.path) !== '') {\n next();\n return;\n }\n\n csrfProtection(req, res, next);\n };\n};\n\nexport const createClientRouter = (authConfig: AuthConfig) =>\n Router()\n .use('/assets/images', express.static(paths.imageDir))\n .use((req, res, next) => {\n if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {\n const redirectPath = encodeURIComponent(req.originalUrl || '/');\n res.redirect(303, `/login?next=${redirectPath}`);\n return;\n }\n\n next();\n })\n .use(createClientRouteCsrfTokenMiddleware(authConfig))\n .use(express.static(paths.clientDist, { extensions: ['html'] }))\n .get(/.*/, (_req, res) => {\n res.sendFile(paths.clientIndex);\n });\n"],"mappings":"AAAA,OAAO,WAAW,cAAc;AAChC,OAAO,UAAU;AACjB,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,aAAa;AAEtB,MAAM,yBAAyB,CAAC,YAAwB,aAAqB,kBAA2B;AACpG,MAAI,WAAW,SAAS,cAAc,eAAe;AACjD,WAAO;AAAA,EACX;AAEA,MACI,YAAY,WAAW,MAAM,KAC7B,YAAY,WAAW,UAAU,KACjC,gBAAgB,YAChB,gBAAgB,WAClB;AACE,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,QAAQ,WAAW,MAAM;AACzC;AAEA,MAAM,uCAAuC,CAAC,eAA2B;AACrE,QAAM,iBAAiB,qBAAqB,UAAU;AAEtD,SAAO,CAAC,KAAsB,KAAuB,SAA+B;AAChF,QAAI,KAAK,QAAQ,IAAI,IAAI,MAAM,IAAI;AAC/B,WAAK;AACL;AAAA,IACJ;AAEA,mBAAe,KAAK,KAAK,IAAI;AAAA,EACjC;AACJ;AAEO,MAAM,qBAAqB,CAAC,eAC/B,OAAO,EACF,IAAI,kBAAkB,QAAQ,OAAO,MAAM,QAAQ,CAAC,EACpD,IAAI,CAAC,KAAK,KAAK,SAAS;AACrB,MAAI,uBAAuB,YAAY,IAAI,MAAM,uBAAuB,GAAG,CAAC,GAAG;AAC3E,UAAM,eAAe,mBAAmB,IAAI,eAAe,GAAG;AAC9D,QAAI,SAAS,KAAK,eAAe,YAAY,EAAE;AAC/C;AAAA,EACJ;AAEA,OAAK;AACT,CAAC,EACA,IAAI,qCAAqC,UAAU,CAAC,EACpD,IAAI,QAAQ,OAAO,MAAM,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,EAC9D,IAAI,MAAM,CAAC,MAAM,QAAQ;AACtB,MAAI,SAAS,MAAM,WAAW;AAClC,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { type RequestHandler, Router } from 'express';\nimport { rateLimit } from 'express-rate-limit';\nimport path from 'path';\nimport { createCsrfProtection, isAuthenticatedRequest } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { paths } from '../paths.js';\n\nconst IMAGE_ASSET_RATE_LIMIT_MESSAGE = 'Too many image asset requests. Please try again later.';\n\nconst shouldBlockClientRoute = (authConfig: AuthConfig, requestPath: string, authenticated: boolean) => {\n if (authConfig.mode !== 'password' || authenticated) {\n return false;\n }\n\n if (\n requestPath.startsWith('/api') ||\n requestPath.startsWith('/graphql') ||\n requestPath === '/login' ||\n requestPath === '/logout'\n ) {\n return false;\n }\n\n return path.extname(requestPath) === '';\n};\n\nconst createImageAssetAuthRateLimit = (authConfig: AuthConfig) =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n skip: (req) => authConfig.mode !== 'password' || isAuthenticatedRequest(req),\n handler: (_req, res) => {\n res.setHeader('Cache-Control', 'no-store');\n res.status(429).json({\n code: 'IMAGE_ASSET_RATE_LIMITED',\n message: IMAGE_ASSET_RATE_LIMIT_MESSAGE,\n });\n },\n });\n\nconst createProtectedImageAssetsMiddleware = (authConfig: AuthConfig): RequestHandler => {\n return (req, res, next) => {\n if (authConfig.mode !== 'password' || isAuthenticatedRequest(req)) {\n next();\n return;\n }\n\n res.setHeader('Cache-Control', 'no-store');\n\n if (req.headers.accept?.includes('text/html')) {\n const redirectPath = encodeURIComponent(req.originalUrl || req.url || '/');\n res.redirect(303, `/login?next=${redirectPath}`);\n return;\n }\n\n res.status(401).end();\n };\n};\n\nconst createClientRouteCsrfTokenMiddleware = (authConfig: AuthConfig) => {\n const csrfProtection = createCsrfProtection(authConfig);\n\n return (req: express.Request, res: express.Response, next: express.NextFunction) => {\n if (path.extname(req.path) !== '') {\n next();\n return;\n }\n\n csrfProtection(req, res, next);\n };\n};\n\nexport const createClientRouter = (authConfig: AuthConfig) =>\n Router()\n .use(\n '/assets/images',\n createImageAssetAuthRateLimit(authConfig),\n createProtectedImageAssetsMiddleware(authConfig),\n express.static(paths.imageDir, {\n setHeaders: (res) => {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n\n if (authConfig.mode === 'password') {\n res.setHeader('Cache-Control', 'no-store');\n }\n },\n }),\n (_req, res) => {\n res.setHeader('X-Content-Type-Options', 'nosniff');\n\n if (authConfig.mode === 'password') {\n res.setHeader('Cache-Control', 'no-store');\n }\n\n res.status(404).end();\n },\n )\n .use((req, res, next) => {\n if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {\n const redirectPath = encodeURIComponent(req.originalUrl || '/');\n res.redirect(303, `/login?next=${redirectPath}`);\n return;\n }\n\n next();\n })\n .use(createClientRouteCsrfTokenMiddleware(authConfig))\n .use(express.static(paths.clientDist, { extensions: ['html'] }))\n .get(/.*/, (_req, res) => {\n res.sendFile(paths.clientIndex);\n });\n"],"mappings":"AAAA,OAAO,WAAgC,cAAc;AACrD,SAAS,iBAAiB;AAC1B,OAAO,UAAU;AACjB,SAAS,sBAAsB,8BAA8B;AAE7D,SAAS,aAAa;AAEtB,MAAM,iCAAiC;AAEvC,MAAM,yBAAyB,CAAC,YAAwB,aAAqB,kBAA2B;AACpG,MAAI,WAAW,SAAS,cAAc,eAAe;AACjD,WAAO;AAAA,EACX;AAEA,MACI,YAAY,WAAW,MAAM,KAC7B,YAAY,WAAW,UAAU,KACjC,gBAAgB,YAChB,gBAAgB,WAClB;AACE,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,QAAQ,WAAW,MAAM;AACzC;AAEA,MAAM,gCAAgC,CAAC,eACnC,UAAU;AAAA,EACN,UAAU,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,MAAM,CAAC,QAAQ,WAAW,SAAS,cAAc,uBAAuB,GAAG;AAAA,EAC3E,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,UAAU,iBAAiB,UAAU;AACzC,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;AAEL,MAAM,uCAAuC,CAAC,eAA2C;AACrF,SAAO,CAAC,KAAK,KAAK,SAAS;AACvB,QAAI,WAAW,SAAS,cAAc,uBAAuB,GAAG,GAAG;AAC/D,WAAK;AACL;AAAA,IACJ;AAEA,QAAI,UAAU,iBAAiB,UAAU;AAEzC,QAAI,IAAI,QAAQ,QAAQ,SAAS,WAAW,GAAG;AAC3C,YAAM,eAAe,mBAAmB,IAAI,eAAe,IAAI,OAAO,GAAG;AACzE,UAAI,SAAS,KAAK,eAAe,YAAY,EAAE;AAC/C;AAAA,IACJ;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI;AAAA,EACxB;AACJ;AAEA,MAAM,uCAAuC,CAAC,eAA2B;AACrE,QAAM,iBAAiB,qBAAqB,UAAU;AAEtD,SAAO,CAAC,KAAsB,KAAuB,SAA+B;AAChF,QAAI,KAAK,QAAQ,IAAI,IAAI,MAAM,IAAI;AAC/B,WAAK;AACL;AAAA,IACJ;AAEA,mBAAe,KAAK,KAAK,IAAI;AAAA,EACjC;AACJ;AAEO,MAAM,qBAAqB,CAAC,eAC/B,OAAO,EACF;AAAA,EACG;AAAA,EACA,8BAA8B,UAAU;AAAA,EACxC,qCAAqC,UAAU;AAAA,EAC/C,QAAQ,OAAO,MAAM,UAAU;AAAA,IAC3B,YAAY,CAAC,QAAQ;AACjB,UAAI,UAAU,0BAA0B,SAAS;AAEjD,UAAI,WAAW,SAAS,YAAY;AAChC,YAAI,UAAU,iBAAiB,UAAU;AAAA,MAC7C;AAAA,IACJ;AAAA,EACJ,CAAC;AAAA,EACD,CAAC,MAAM,QAAQ;AACX,QAAI,UAAU,0BAA0B,SAAS;AAEjD,QAAI,WAAW,SAAS,YAAY;AAChC,UAAI,UAAU,iBAAiB,UAAU;AAAA,IAC7C;AAEA,QAAI,OAAO,GAAG,EAAE,IAAI;AAAA,EACxB;AACJ,EACC,IAAI,CAAC,KAAK,KAAK,SAAS;AACrB,MAAI,uBAAuB,YAAY,IAAI,MAAM,uBAAuB,GAAG,CAAC,GAAG;AAC3E,UAAM,eAAe,mBAAmB,IAAI,eAAe,GAAG;AAC9D,QAAI,SAAS,KAAK,eAAe,YAAY,EAAE;AAC/C;AAAA,EACJ;AAEA,OAAK;AACT,CAAC,EACA,IAAI,qCAAqC,UAAU,CAAC,EACpD,IAAI,QAAQ,OAAO,MAAM,YAAY,EAAE,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,EAC9D,IAAI,MAAM,CAAC,MAAM,QAAQ;AACtB,MAAI,SAAS,MAAM,WAAW;AAClC,CAAC;","names":[]}
@@ -1 +0,0 @@
1
- import{j as n}from"./note-vendor-i1M2FSI2.js";import{p as m,T as d}from"./index-jIfkRdY_.js";import{a as e}from"./graph-vendor-CUxe67Lr.js";const p=new Map([["bold",e.createElement(e.Fragment,null,e.createElement("path",{d:"M108,84a16,16,0,1,1,16,16A16,16,0,0,1,108,84Zm128,44A108,108,0,1,1,128,20,108.12,108.12,0,0,1,236,128Zm-24,0a84,84,0,1,0-84,84A84.09,84.09,0,0,0,212,128Zm-72,36.68V132a20,20,0,0,0-20-20,12,12,0,0,0-4,23.32V168a20,20,0,0,0,20,20,12,12,0,0,0,4-23.32Z"}))],["duotone",e.createElement(e.Fragment,null,e.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),e.createElement("path",{d:"M144,176a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176Zm88-48A104,104,0,1,1,128,24,104.11,104.11,0,0,1,232,128Zm-16,0a88,88,0,1,0-88,88A88.1,88.1,0,0,0,216,128ZM124,96a12,12,0,1,0-12-12A12,12,0,0,0,124,96Z"}))],["fill",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-4,48a12,12,0,1,1-12,12A12,12,0,0,1,124,72Zm12,112a16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40a8,8,0,0,1,0,16Z"}))],["light",e.createElement(e.Fragment,null,e.createElement("path",{d:"M142,176a6,6,0,0,1-6,6,14,14,0,0,1-14-14V128a2,2,0,0,0-2-2,6,6,0,0,1,0-12,14,14,0,0,1,14,14v40a2,2,0,0,0,2,2A6,6,0,0,1,142,176ZM124,94a10,10,0,1,0-10-10A10,10,0,0,0,124,94Zm106,34A102,102,0,1,1,128,26,102.12,102.12,0,0,1,230,128Zm-12,0a90,90,0,1,0-90,90A90.1,90.1,0,0,0,218,128Z"}))],["regular",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm16-40a8,8,0,0,1-8,8,16,16,0,0,1-16-16V128a8,8,0,0,1,0-16,16,16,0,0,1,16,16v40A8,8,0,0,1,144,176ZM112,84a12,12,0,1,1,12,12A12,12,0,0,1,112,84Z"}))],["thin",e.createElement(e.Fragment,null,e.createElement("path",{d:"M140,176a4,4,0,0,1-4,4,12,12,0,0,1-12-12V128a4,4,0,0,0-4-4,4,4,0,0,1,0-8,12,12,0,0,1,12,12v40a4,4,0,0,0,4,4A4,4,0,0,1,140,176ZM124,92a8,8,0,1,0-8-8A8,8,0,0,0,124,92Zm104,36A100,100,0,1,1,128,28,100.11,100.11,0,0,1,228,128Zm-8,0a92,92,0,1,0-92,92A92.1,92.1,0,0,0,220,128Z"}))]]),g=new Map([["bold",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,20A108,108,0,1,0,236,128,108.12,108.12,0,0,0,128,20Zm0,192a84,84,0,1,1,84-84A84.09,84.09,0,0,1,128,212Zm-12-80V80a12,12,0,0,1,24,0v52a12,12,0,0,1-24,0Zm28,40a16,16,0,1,1-16-16A16,16,0,0,1,144,172Z"}))],["duotone",e.createElement(e.Fragment,null,e.createElement("path",{d:"M224,128a96,96,0,1,1-96-96A96,96,0,0,1,224,128Z",opacity:"0.2"}),e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["fill",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm-8,56a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm8,104a12,12,0,1,1,12-12A12,12,0,0,1,128,184Z"}))],["light",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,26A102,102,0,1,0,230,128,102.12,102.12,0,0,0,128,26Zm0,192a90,90,0,1,1,90-90A90.1,90.1,0,0,1,128,218Zm-6-82V80a6,6,0,0,1,12,0v56a6,6,0,0,1-12,0Zm16,36a10,10,0,1,1-10-10A10,10,0,0,1,138,172Z"}))],["regular",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,24A104,104,0,1,0,232,128,104.11,104.11,0,0,0,128,24Zm0,192a88,88,0,1,1,88-88A88.1,88.1,0,0,1,128,216Zm-8-80V80a8,8,0,0,1,16,0v56a8,8,0,0,1-16,0Zm20,36a12,12,0,1,1-12-12A12,12,0,0,1,140,172Z"}))],["thin",e.createElement(e.Fragment,null,e.createElement("path",{d:"M128,28A100,100,0,1,0,228,128,100.11,100.11,0,0,0,128,28Zm0,192a92,92,0,1,1,92-92A92.1,92.1,0,0,1,128,220Zm-4-84V80a4,4,0,0,1,8,0v56a4,4,0,0,1-8,0Zm12,36a8,8,0,1,1-8-8A8,8,0,0,1,136,172Z"}))]]),l=e.forwardRef((a,t)=>e.createElement(m,{ref:t,...a,weights:p}));l.displayName="InfoIcon";const E=l,c=e.forwardRef((a,t)=>e.createElement(m,{ref:t,...a,weights:g}));c.displayName="WarningCircleIcon";const i=c,f=({children:a,className:t="",tone:r="info"})=>{const o=r==="danger"?i:E,Z=r==="danger"?"border-border-error/70 bg-accent-soft-danger/60":"border-border-subtle bg-hover-subtle/50",s=r==="danger"?"text-fg-error":"text-fg-tertiary",A=r==="danger"?"error":"secondary";return n.jsxs("div",{className:`flex items-center gap-2.5 rounded-[12px] border px-4 py-3 ${Z} ${t}`,children:[n.jsx(o,{className:`h-4 w-4 shrink-0 ${s}`}),n.jsx(d,{as:"div",variant:"meta",weight:"medium",tone:A,children:a})]})};export{f as C,i as m};
@@ -1 +0,0 @@
1
- import{j as t}from"./note-vendor-i1M2FSI2.js";import{a0 as _,P as C,a3 as U,i as G,a4 as J,a5 as Y,a6 as X,a7 as ee,q as ne,N as te,J as re,T as N,a8 as oe}from"./index-jIfkRdY_.js";import{a as i,F as se}from"./graph-vendor-CUxe67Lr.js";import"./note-core-Z6kqulGB.js";const ae=s=>{let a=0;for(let r=0;r<s.length;r++)a+=s.charCodeAt(r);return a},ie={background:"#f4f6f8",nodeHub:"#2c2f36",nodeSelected:"#111318",nodeConnected:"#636b76",nodeDefault:["#d6dbe1","#c8ced6","#b7bec7","#a7afb9"],nodeDimmed:["rgba(214,219,225,0.28)","rgba(200,206,214,0.24)","rgba(183,190,199,0.24)","rgba(167,175,185,0.2)"],nodeHubDimmed:"rgba(44,47,54,0.16)",nodeStroke:"#eef1f4",nodeSelectedStroke:"#a6b0bc",labelBackground:"rgba(246,248,250,0.92)",labelText:"#222831",labelFontFamily:"Pretendard Variable, Pretendard, system-ui, sans-serif",linkIdle:"rgba(99, 107, 117, 0.34)",linkConnected:"#68717c",linkDimmed:"rgba(127, 136, 146, 0.12)",legendHub:"#2c2f36"},le={background:"#121316",nodeHub:"#d6dce3",nodeSelected:"#eef1f5",nodeConnected:"#9099a4",nodeDefault:["#343a43","#2d333c","#262c35","#20262f"],nodeDimmed:["rgba(52,58,67,0.28)","rgba(45,51,60,0.24)","rgba(38,44,53,0.22)","rgba(32,38,47,0.2)"],nodeHubDimmed:"rgba(214,220,227,0.16)",nodeStroke:"#171c23",nodeSelectedStroke:"#7f8a97",labelBackground:"rgba(16,18,22,0.9)",labelText:"#eef2f6",labelFontFamily:"Pretendard Variable, Pretendard, system-ui, sans-serif",linkIdle:"rgba(118, 127, 138, 0.42)",linkConnected:"#a2abb6",linkDimmed:"rgba(118, 127, 138, 0.1)",legendHub:"#d6dce3"};function D(s){return s==="dark"?le:ie}function de(s,a){const r=D(s),{connections:g,colorIndex:y,selectedNodeId:d,nodeId:b,isConnected:c}=a,h=d===b;return d!==null&&!h&&!c?g>3?r.nodeHubDimmed:r.nodeDimmed[y%r.nodeDimmed.length]:h?r.nodeSelected:c?r.nodeConnected:g>3?r.nodeHub:r.nodeDefault[y%r.nodeDefault.length]}function ce(s,a){const r=D(s);return a.selectedNodeId!==null&&!a.isConnected?r.linkDimmed:a.isConnected?r.linkConnected:r.linkIdle}function ue(s,a){const r=D(s);return`${a.emphasize?"700":"400"} ${a.fontSize}px ${r.labelFontFamily}`}function z(s){return s<=1?3.5:s<=3?4.5:s<=6?5.5:Math.min(7,5.5+Math.sqrt(s)*.5)}const fe=t.jsx(C,{title:"Knowledge Graph",description:t.jsx(G,{width:184,height:16,className:"rounded-full"}),children:t.jsx("div",{className:"flex h-[600px] items-center justify-center",children:t.jsx(G,{width:"100%",height:"100%"})})});function he(){const s=J(),a=i.useRef(null),r=i.useRef(null),[g,y]=i.useState({width:800,height:600}),[d,b]=i.useState(null),{theme:c}=Y(e=>e),h=D(c),v=i.useRef(h);v.current=h;const{data:w}=X({queryKey:ne.notes.graph(),queryFn:async()=>{const e=await ee();if(e.type==="error")throw e;return e.noteGraph}}),l=i.useMemo(()=>{if(w.nodes.length===0)return null;const e=w.nodes.filter(o=>o.connections>0);if(e.length===0)return null;const n=new Set(e.map(o=>o.id));return{nodes:e,links:w.links.filter(o=>n.has(o.source)&&n.has(o.target))}},[w]);i.useEffect(()=>{if(!l)return;const e=()=>{if(!a.current)return;const n=a.current.getBoundingClientRect();y({width:n.width,height:Math.max(600,window.innerHeight-150)})};return e(),window.addEventListener("resize",e),()=>window.removeEventListener("resize",e)},[l]),i.useEffect(()=>{if(!l||!r.current)return;const e=window.setTimeout(()=>{r.current?.zoomToFit(400,50)},500);return()=>window.clearTimeout(e)},[l]);const R=i.useRef(d);R.current=d;const B=i.useCallback(e=>{if(R.current===e.id){s({to:te,params:{id:e.id}});return}b(e.id)},[s]),L=i.useCallback(()=>{b(null)},[]),K=i.useCallback(e=>{a.current&&(a.current.style.cursor=e?"pointer":"default")},[]),I=i.useRef(!1),q=i.useCallback(()=>{I.current||(I.current=!0,r.current?.enableZoomInteraction(!1))},[]),A=i.useCallback(()=>{I.current=!1,r.current?.enableZoomInteraction(!0)},[]),H=i.useRef(new Map),O=i.useMemo(()=>{const e=new Map;if(l)for(const n of l.links)e.has(n.source)||e.set(n.source,new Set),e.has(n.target)||e.set(n.target,new Set),e.get(n.source)?.add(n.target),e.get(n.target)?.add(n.source);return e},[l]);H.current=O;const Z=i.useCallback((e,n,o)=>{const u=v.current,f=d,p=H.current,m=z(e.connections),x=e.x||0,S=e.y||0,k=f===e.id,T=f?p.get(f)?.has(e.id)??!1:!1,V=f!==null&&!k&&!T,W=ae(e.id);if(n.beginPath(),n.arc(x,S,m,0,Math.PI*2),n.fillStyle=de(c,{connections:e.connections,colorIndex:W,selectedNodeId:f,nodeId:e.id,isConnected:T}),n.fill(),V)return;n.strokeStyle=u.nodeStroke,n.lineWidth=(k?2:1)/o,n.stroke(),k&&(n.beginPath(),n.arc(x,S,m+2/o,0,Math.PI*2),n.strokeStyle=u.nodeSelectedStroke,n.lineWidth=1.5/o,n.stroke());const Q=T&&e.connections>=4;if(k||Q||o>2.5){const P=e.title||"Untitled",E=Math.max(10/o,2.5);n.font=ue(c,{fontSize:E,emphasize:k}),n.textAlign="center",n.textBaseline="top";const M=n.measureText(P).width,j=2/o,F=S+m+3/o;n.fillStyle=u.labelBackground,n.fillRect(x-M/2-j,F,M+j*2,E+j*2),n.fillStyle=u.labelText,n.fillText(P,x,F+j)}},[d,c]),$=i.useCallback((e,n,o)=>{const u=d,f=e.source,p=e.target,m=u?f.id===u||p.id===u:!1;n.beginPath(),n.moveTo(f.x||0,f.y||0),n.lineTo(p.x||0,p.y||0),n.strokeStyle=ce(c,{selectedNodeId:u,isConnected:m}),n.lineWidth=m?2/o:.5/o,n.stroke()},[d,c]);return l?t.jsx(C,{title:"Knowledge Graph",description:`${l.nodes.length} linked notes, ${l.links.length} connections`,children:t.jsxs("div",{ref:a,className:"surface-base graph-canvas relative overflow-hidden",style:{"--graph-bg":h.background},children:[d&&(()=>{const e=l.nodes.find(n=>n.id===d);return e?t.jsxs("div",{className:"surface-floating absolute top-3 left-3 z-10 flex items-center gap-2 px-3 py-2",children:[t.jsx(N,{as:"span",variant:"meta",weight:"semibold",truncate:!0,className:"max-w-48",children:e.title}),t.jsxs(N,{as:"span",variant:"label",tone:"tertiary",children:[e.connections," links"]}),t.jsx("button",{type:"button",onClick:()=>b(null),className:"focus-ring-soft ml-1 flex h-6 w-6 cursor-pointer items-center justify-center rounded-[8px] text-fg-tertiary transition-colors hover:bg-hover-subtle hover:text-fg-default","aria-label":"Deselect node",children:t.jsx(oe,{className:"h-3.5 w-3.5"})})]}):null})(),t.jsxs("div",{className:"surface-floating absolute top-3 right-3 z-10 flex flex-col gap-1.5 px-3 py-2.5",children:[t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"legend-dot h-2.5 w-2.5 shrink-0 rounded-full",style:{"--legend-color":h.legendHub}}),t.jsx(N,{as:"span",variant:"label",weight:"medium",tone:"secondary",children:"Hub notes (4+ connections)"})]}),t.jsxs("div",{className:"flex items-center gap-2",children:[t.jsx("span",{className:"legend-dot h-2.5 w-2.5 shrink-0 rounded-full",style:{"--legend-color":h.nodeConnected}}),t.jsx(N,{as:"span",variant:"label",weight:"medium",tone:"secondary",children:"Connected notes"})]})]}),t.jsx(se,{ref:r,graphData:l,width:g.width,height:g.height,nodeId:"id",nodeLabel:"",nodeCanvasObject:Z,nodePointerAreaPaint:(e,n,o)=>{o.beginPath(),o.arc(e.x||0,e.y||0,Math.max(z(e.connections)+4,10),0,2*Math.PI),o.fillStyle=n,o.fill()},linkCanvasObject:$,linkCanvasObjectMode:()=>"replace",linkDirectionalParticles:0,onNodeClick:B,onNodeHover:K,onBackgroundClick:L,onNodeDrag:q,onNodeDragEnd:A,warmupTicks:30,cooldownTicks:80,d3AlphaDecay:.05,d3VelocityDecay:.3,enableZoomInteraction:!0,enablePanInteraction:!0,minZoom:.3,maxZoom:5})]})}):t.jsx(C,{title:"Knowledge Graph",description:"0 linked notes, 0 connections",children:t.jsx(re,{title:"No constellations yet",description:"Link your notes together and watch your own starry sky unfold"})})}function ke(){return t.jsx(_,{fallback:fe,errorTitle:"Failed to load graph",errorDescription:"Retry loading your linked note constellation",renderError:({error:s,retry:a})=>t.jsx(C,{title:"Knowledge Graph",children:t.jsx(U,{title:"Failed to load graph",description:"Retry loading your linked note constellation",error:s,onRetry:a})}),children:t.jsx(he,{})})}export{ke as default};
@@ -1 +0,0 @@
1
- import{a}from"./graph-vendor-CUxe67Lr.js";import{p as m}from"./index-jIfkRdY_.js";const n=new Map([["bold",a.createElement(a.Fragment,null,a.createElement("path",{d:"M144,96a16,16,0,1,1,16,16A16,16,0,0,1,144,96Zm92-40V200a20,20,0,0,1-20,20H40a20,20,0,0,1-20-20V56A20,20,0,0,1,40,36H216A20,20,0,0,1,236,56ZM44,60v79.72l33.86-33.86a20,20,0,0,1,28.28,0L147.31,147l17.18-17.17a20,20,0,0,1,28.28,0L212,149.09V60Zm0,136H162.34L92,125.66l-48,48Zm168,0V183l-33.37-33.37L164.28,164l32,32Z"}))],["duotone",a.createElement(a.Fragment,null,a.createElement("path",{d:"M224,56V178.06l-39.72-39.72a8,8,0,0,0-11.31,0L147.31,164,97.66,114.34a8,8,0,0,0-11.32,0L32,168.69V56a8,8,0,0,1,8-8H216A8,8,0,0,1,224,56Z",opacity:"0.2"}),a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"}))],["fill",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40ZM156,88a12,12,0,1,1-12,12A12,12,0,0,1,156,88Zm60,112H40V160.69l46.34-46.35a8,8,0,0,1,11.32,0h0L165,181.66a8,8,0,0,0,11.32-11.32l-17.66-17.65L173,138.34a8,8,0,0,1,11.31,0L216,170.07V200Z"}))],["light",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,42H40A14,14,0,0,0,26,56V200a14,14,0,0,0,14,14H216a14,14,0,0,0,14-14V56A14,14,0,0,0,216,42ZM40,54H216a2,2,0,0,1,2,2V163.57L188.53,134.1a14,14,0,0,0-19.8,0l-21.42,21.42L101.9,110.1a14,14,0,0,0-19.8,0L38,154.2V56A2,2,0,0,1,40,54ZM38,200V171.17l52.58-52.58a2,2,0,0,1,2.84,0L176.83,202H40A2,2,0,0,1,38,200Zm178,2H193.8l-38-38,21.41-21.42a2,2,0,0,1,2.83,0l38,38V200A2,2,0,0,1,216,202ZM146,100a10,10,0,1,1,10,10A10,10,0,0,1,146,100Z"}))],["regular",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,40H40A16,16,0,0,0,24,56V200a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A16,16,0,0,0,216,40Zm0,16V158.75l-26.07-26.06a16,16,0,0,0-22.63,0l-20,20-44-44a16,16,0,0,0-22.62,0L40,149.37V56ZM40,172l52-52,80,80H40Zm176,28H194.63l-36-36,20-20L216,181.38V200ZM144,100a12,12,0,1,1,12,12A12,12,0,0,1,144,100Z"}))],["thin",a.createElement(a.Fragment,null,a.createElement("path",{d:"M216,44H40A12,12,0,0,0,28,56V200a12,12,0,0,0,12,12H216a12,12,0,0,0,12-12V56A12,12,0,0,0,216,44ZM40,52H216a4,4,0,0,1,4,4V168.4l-32.89-32.89a12,12,0,0,0-17,0l-22.83,22.83-46.82-46.83a12,12,0,0,0-17,0L36,159V56A4,4,0,0,1,40,52ZM36,200V170.34l53.17-53.17a4,4,0,0,1,5.66,0L181.66,204H40A4,4,0,0,1,36,200Zm180,4H193l-40-40,22.83-22.83a4,4,0,0,1,5.66,0L220,179.71V200A4,4,0,0,1,216,204ZM148,100a8,8,0,1,1,8,8A8,8,0,0,1,148,100Z"}))]]),e=a.forwardRef((l,t)=>a.createElement(m,{ref:t,...l,weights:n}));e.displayName="ImageIcon";const Z=e;export{Z as I};