ocean-brain 0.7.4 → 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 (111) 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-Bz7jheg6.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-CfL1jD-O.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-DiMt3Dz4.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-Bqw8NURO.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-Ro00byT7.js → placeholder-DeKgSBnC.js} +4 -4
  34. package/server/client/dist/assets/{properties-Tid6-ghT.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-BWymIujb.js → useReminderMutate-CoXXqJY2.js} +1 -1
  38. package/server/client/dist/index.html +5 -5
  39. package/server/dist/app.js +2 -2
  40. package/server/dist/app.js.map +1 -1
  41. package/server/dist/features/auth/http/api.js +5 -1
  42. package/server/dist/features/auth/http/api.js.map +1 -1
  43. package/server/dist/features/auth/http/login-page.js +1 -1
  44. package/server/dist/features/auth/http/pages.js +8 -1
  45. package/server/dist/features/auth/http/pages.js.map +1 -1
  46. package/server/dist/features/auth/service.js +16 -7
  47. package/server/dist/features/auth/service.js.map +1 -1
  48. package/server/dist/features/image/http/upload.js +34 -23
  49. package/server/dist/features/image/http/upload.js.map +1 -1
  50. package/server/dist/features/image/services/validation.js +111 -0
  51. package/server/dist/features/image/services/validation.js.map +1 -0
  52. package/server/dist/features/mcp-admin/http/handlers.js +11 -3
  53. package/server/dist/features/mcp-admin/http/handlers.js.map +1 -1
  54. package/server/dist/features/note/http/mcp.js +8 -43
  55. package/server/dist/features/note/http/mcp.js.map +1 -1
  56. package/server/dist/features/note/services/cleanup.js +2 -2
  57. package/server/dist/features/note/services/cleanup.js.map +1 -1
  58. package/server/dist/features/note/services/markdown-intent-write.js +11 -43
  59. package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
  60. package/server/dist/features/note/services/markdown-patch.js +4 -123
  61. package/server/dist/features/note/services/markdown-patch.js.map +1 -1
  62. package/server/dist/features/note/services/write.js +13 -13
  63. package/server/dist/features/note/services/write.js.map +1 -1
  64. package/server/dist/features/tag/graphql/tag.query.resolver.js +16 -1
  65. package/server/dist/features/tag/graphql/tag.query.resolver.js.map +1 -1
  66. package/server/dist/features/tag/graphql/tag.type-defs.js +2 -0
  67. package/server/dist/features/tag/graphql/tag.type-defs.js.map +1 -1
  68. package/server/dist/modules/app-version.js +105 -0
  69. package/server/dist/modules/app-version.js.map +1 -0
  70. package/server/dist/modules/auth-guard.js +21 -2
  71. package/server/dist/modules/auth-guard.js.map +1 -1
  72. package/server/dist/modules/auth-redirect.js +10 -0
  73. package/server/dist/modules/auth-redirect.js.map +1 -0
  74. package/server/dist/modules/mcp-auth.js +62 -1
  75. package/server/dist/modules/mcp-auth.js.map +1 -1
  76. package/server/dist/modules/rate-limit.js +15 -1
  77. package/server/dist/modules/rate-limit.js.map +1 -1
  78. package/server/dist/modules/server-events-handler.js +17 -0
  79. package/server/dist/modules/server-events-handler.js.map +1 -1
  80. package/server/dist/modules/session-store.js +149 -0
  81. package/server/dist/modules/session-store.js.map +1 -0
  82. package/server/dist/paths.js +1 -0
  83. package/server/dist/paths.js.map +1 -1
  84. package/server/dist/routes/api.js +39 -20
  85. package/server/dist/routes/api.js.map +1 -1
  86. package/server/dist/routes/auth-pages.js +13 -2
  87. package/server/dist/routes/auth-pages.js.map +1 -1
  88. package/server/dist/routes/client.js +63 -3
  89. package/server/dist/routes/client.js.map +1 -1
  90. package/server/dist/routes/graphql.js +23 -19
  91. package/server/dist/routes/graphql.js.map +1 -1
  92. package/server/client/dist/assets/Callout-DlFLkTCG.js +0 -1
  93. package/server/client/dist/assets/Graph-BdxtJvog.js +0 -1
  94. package/server/client/dist/assets/Image.es-BFodqKS5.js +0 -1
  95. package/server/client/dist/assets/Note-BgMxsWZT.js +0 -22
  96. package/server/client/dist/assets/Plus.es-Yq7J1Ur4.js +0 -1
  97. package/server/client/dist/assets/Reminders-CUsR56ff.js +0 -1
  98. package/server/client/dist/assets/Search-DncrGgOZ.js +0 -1
  99. package/server/client/dist/assets/Tag-CKDLs2Y6.js +0 -1
  100. package/server/client/dist/assets/TagNotes-ByX_Qias.js +0 -1
  101. package/server/client/dist/assets/Trash.es-BPrF6TPV.js +0 -1
  102. package/server/client/dist/assets/ViewNotes-r1kxKZ8O.js +0 -1
  103. package/server/client/dist/assets/ViewSectionTableRenderer-DQ4UeCnm.js +0 -102
  104. package/server/client/dist/assets/Views-FzFBtNT2.js +0 -1
  105. package/server/client/dist/assets/image.api-Bcg3TFCG.js +0 -17
  106. package/server/client/dist/assets/index-BFfQKAUY.js +0 -361
  107. package/server/client/dist/assets/manage-image-DzDlncCM.js +0 -1
  108. package/server/client/dist/assets/mcp-BqasqTWC.js +0 -1
  109. package/server/client/dist/assets/trash-bHK02-Up.js +0 -1
  110. package/server/dist/features/image/services/remote-fetch.js +0 -176
  111. package/server/dist/features/image/services/remote-fetch.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ import session from "express-session";
2
+ const AUTH_SESSION_IDLE_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1e3;
3
+ const ANONYMOUS_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1e3;
4
+ const AUTH_SESSION_PRUNE_INTERVAL_MS = 60 * 60 * 1e3;
5
+ const DEFAULT_MAX_SESSIONS = 1e3;
6
+ const serializeSession = (data) => JSON.stringify(data);
7
+ const deserializeSession = (data) => {
8
+ const parsed = JSON.parse(data);
9
+ const expires = parsed.cookie?.expires;
10
+ if (typeof expires === "string") {
11
+ parsed.cookie.expires = new Date(expires);
12
+ }
13
+ return parsed;
14
+ };
15
+ const isAuthenticatedSession = (data) => Boolean(data.authenticated);
16
+ const getSessionExpiry = (data, now = Date.now()) => {
17
+ const expires = data.cookie?.expires;
18
+ const ttlMs = isAuthenticatedSession(data) ? AUTH_SESSION_IDLE_TIMEOUT_MS : ANONYMOUS_SESSION_IDLE_TIMEOUT_MS;
19
+ const fallbackExpiresAt = now + ttlMs;
20
+ if (expires) {
21
+ const expiresAt = expires instanceof Date ? expires : new Date(expires);
22
+ if (!Number.isNaN(expiresAt.getTime())) {
23
+ return Math.min(expiresAt.getTime(), fallbackExpiresAt);
24
+ }
25
+ }
26
+ return fallbackExpiresAt;
27
+ };
28
+ class TtlMemorySessionStore extends session.Store {
29
+ constructor(options = {}) {
30
+ super();
31
+ this.options = options;
32
+ this.pruneTimer = setInterval(() => {
33
+ this.pruneExpiredSessions();
34
+ }, options.pruneIntervalMs ?? AUTH_SESSION_PRUNE_INTERVAL_MS);
35
+ this.pruneTimer.unref?.();
36
+ }
37
+ options;
38
+ sessions = /* @__PURE__ */ new Map();
39
+ pruneTimer;
40
+ get(sid, callback) {
41
+ try {
42
+ const entry = this.sessions.get(sid);
43
+ if (!entry) {
44
+ callback(null, null);
45
+ return;
46
+ }
47
+ if (entry.expiresAt <= Date.now()) {
48
+ this.sessions.delete(sid);
49
+ callback(null, null);
50
+ return;
51
+ }
52
+ callback(null, deserializeSession(entry.data));
53
+ } catch (error) {
54
+ callback(error);
55
+ }
56
+ }
57
+ set(sid, data, callback) {
58
+ try {
59
+ const now = Date.now();
60
+ this.sessions.set(sid, {
61
+ data: serializeSession(data),
62
+ expiresAt: getSessionExpiry(data, now),
63
+ authenticated: isAuthenticatedSession(data),
64
+ updatedAt: now
65
+ });
66
+ this.pruneExpiredSessions();
67
+ this.evictOldestSessions();
68
+ callback?.();
69
+ } catch (error) {
70
+ callback?.(error);
71
+ }
72
+ }
73
+ destroy(sid, callback) {
74
+ this.sessions.delete(sid);
75
+ callback?.();
76
+ }
77
+ touch(sid, data, callback) {
78
+ const entry = this.sessions.get(sid);
79
+ if (entry) {
80
+ const now = Date.now();
81
+ this.sessions.set(sid, {
82
+ data: serializeSession(data),
83
+ expiresAt: getSessionExpiry(data, now),
84
+ authenticated: isAuthenticatedSession(data),
85
+ updatedAt: now
86
+ });
87
+ }
88
+ callback?.();
89
+ }
90
+ all(callback) {
91
+ try {
92
+ this.pruneExpiredSessions();
93
+ const sessions = {};
94
+ for (const [sid, entry] of this.sessions) {
95
+ sessions[sid] = deserializeSession(entry.data);
96
+ }
97
+ callback(null, sessions);
98
+ } catch (error) {
99
+ callback(error);
100
+ }
101
+ }
102
+ length(callback) {
103
+ this.pruneExpiredSessions();
104
+ callback(null, this.sessions.size);
105
+ }
106
+ clear(callback) {
107
+ this.sessions.clear();
108
+ callback?.();
109
+ }
110
+ pruneExpiredSessions(now = Date.now()) {
111
+ for (const [sid, entry] of this.sessions) {
112
+ if (entry.expiresAt <= now) {
113
+ this.sessions.delete(sid);
114
+ }
115
+ }
116
+ }
117
+ stopPruning() {
118
+ clearInterval(this.pruneTimer);
119
+ }
120
+ evictOldestSessions() {
121
+ const maxSessions = this.options.maxSessions ?? DEFAULT_MAX_SESSIONS;
122
+ while (this.sessions.size > maxSessions) {
123
+ const oldestSid = this.findOldestSessionId((entry) => !entry.authenticated) ?? this.findOldestSessionId();
124
+ if (!oldestSid) {
125
+ return;
126
+ }
127
+ this.sessions.delete(oldestSid);
128
+ }
129
+ }
130
+ findOldestSessionId(filter = () => true) {
131
+ let oldestSid;
132
+ let oldestUpdatedAt = Number.POSITIVE_INFINITY;
133
+ for (const [sid, entry] of this.sessions) {
134
+ if (filter(entry) && entry.updatedAt < oldestUpdatedAt) {
135
+ oldestSid = sid;
136
+ oldestUpdatedAt = entry.updatedAt;
137
+ }
138
+ }
139
+ return oldestSid;
140
+ }
141
+ }
142
+ const createSessionStore = () => new TtlMemorySessionStore();
143
+ export {
144
+ ANONYMOUS_SESSION_IDLE_TIMEOUT_MS,
145
+ AUTH_SESSION_IDLE_TIMEOUT_MS,
146
+ TtlMemorySessionStore,
147
+ createSessionStore
148
+ };
149
+ //# sourceMappingURL=session-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/modules/session-store.ts"],"sourcesContent":["import session, { type SessionData } from 'express-session';\n\nexport const AUTH_SESSION_IDLE_TIMEOUT_MS = 7 * 24 * 60 * 60 * 1000;\nexport const ANONYMOUS_SESSION_IDLE_TIMEOUT_MS = 30 * 60 * 1000;\nconst AUTH_SESSION_PRUNE_INTERVAL_MS = 60 * 60 * 1000;\nconst DEFAULT_MAX_SESSIONS = 1000;\n\ntype StoredSession = {\n data: string;\n expiresAt: number;\n authenticated: boolean;\n updatedAt: number;\n};\n\ntype AuthSessionData = SessionData & {\n authenticated?: boolean;\n};\n\nconst serializeSession = (data: SessionData) => JSON.stringify(data);\n\nconst deserializeSession = (data: string): SessionData => {\n const parsed = JSON.parse(data) as SessionData;\n const expires = parsed.cookie?.expires;\n\n if (typeof expires === 'string') {\n parsed.cookie.expires = new Date(expires);\n }\n\n return parsed;\n};\n\nconst isAuthenticatedSession = (data: SessionData) => Boolean((data as AuthSessionData).authenticated);\n\nconst getSessionExpiry = (data: SessionData, now = Date.now()) => {\n const expires = data.cookie?.expires;\n const ttlMs = isAuthenticatedSession(data) ? AUTH_SESSION_IDLE_TIMEOUT_MS : ANONYMOUS_SESSION_IDLE_TIMEOUT_MS;\n const fallbackExpiresAt = now + ttlMs;\n\n if (expires) {\n const expiresAt = expires instanceof Date ? expires : new Date(expires);\n\n if (!Number.isNaN(expiresAt.getTime())) {\n return Math.min(expiresAt.getTime(), fallbackExpiresAt);\n }\n }\n\n return fallbackExpiresAt;\n};\n\nexport class TtlMemorySessionStore extends session.Store {\n private readonly sessions = new Map<string, StoredSession>();\n private readonly pruneTimer: NodeJS.Timeout;\n\n constructor(private readonly options: { maxSessions?: number; pruneIntervalMs?: number } = {}) {\n super();\n\n this.pruneTimer = setInterval(() => {\n this.pruneExpiredSessions();\n }, options.pruneIntervalMs ?? AUTH_SESSION_PRUNE_INTERVAL_MS);\n this.pruneTimer.unref?.();\n }\n\n get(sid: string, callback: (error: unknown, session?: SessionData | null) => void) {\n try {\n const entry = this.sessions.get(sid);\n\n if (!entry) {\n callback(null, null);\n return;\n }\n\n if (entry.expiresAt <= Date.now()) {\n this.sessions.delete(sid);\n callback(null, null);\n return;\n }\n\n callback(null, deserializeSession(entry.data));\n } catch (error) {\n callback(error);\n }\n }\n\n set(sid: string, data: SessionData, callback?: (error?: unknown) => void) {\n try {\n const now = Date.now();\n\n this.sessions.set(sid, {\n data: serializeSession(data),\n expiresAt: getSessionExpiry(data, now),\n authenticated: isAuthenticatedSession(data),\n updatedAt: now,\n });\n this.pruneExpiredSessions();\n this.evictOldestSessions();\n callback?.();\n } catch (error) {\n callback?.(error);\n }\n }\n\n destroy(sid: string, callback?: (error?: unknown) => void) {\n this.sessions.delete(sid);\n callback?.();\n }\n\n touch(sid: string, data: SessionData, callback?: () => void) {\n const entry = this.sessions.get(sid);\n\n if (entry) {\n const now = Date.now();\n\n this.sessions.set(sid, {\n data: serializeSession(data),\n expiresAt: getSessionExpiry(data, now),\n authenticated: isAuthenticatedSession(data),\n updatedAt: now,\n });\n }\n\n callback?.();\n }\n\n all(callback: (error: unknown, sessions?: SessionData[] | { [sid: string]: SessionData } | null) => void) {\n try {\n this.pruneExpiredSessions();\n\n const sessions: Record<string, SessionData> = {};\n for (const [sid, entry] of this.sessions) {\n sessions[sid] = deserializeSession(entry.data);\n }\n\n callback(null, sessions);\n } catch (error) {\n callback(error);\n }\n }\n\n length(callback: (error: unknown, length?: number) => void) {\n this.pruneExpiredSessions();\n callback(null, this.sessions.size);\n }\n\n clear(callback?: (error?: unknown) => void) {\n this.sessions.clear();\n callback?.();\n }\n\n pruneExpiredSessions(now = Date.now()) {\n for (const [sid, entry] of this.sessions) {\n if (entry.expiresAt <= now) {\n this.sessions.delete(sid);\n }\n }\n }\n\n stopPruning() {\n clearInterval(this.pruneTimer);\n }\n\n private evictOldestSessions() {\n const maxSessions = this.options.maxSessions ?? DEFAULT_MAX_SESSIONS;\n\n while (this.sessions.size > maxSessions) {\n const oldestSid = this.findOldestSessionId((entry) => !entry.authenticated) ?? this.findOldestSessionId();\n\n if (!oldestSid) {\n return;\n }\n\n this.sessions.delete(oldestSid);\n }\n }\n\n private findOldestSessionId(filter: (entry: StoredSession) => boolean = () => true) {\n let oldestSid: string | undefined;\n let oldestUpdatedAt = Number.POSITIVE_INFINITY;\n\n for (const [sid, entry] of this.sessions) {\n if (filter(entry) && entry.updatedAt < oldestUpdatedAt) {\n oldestSid = sid;\n oldestUpdatedAt = entry.updatedAt;\n }\n }\n\n return oldestSid;\n }\n}\n\nexport const createSessionStore = (): session.Store => new TtlMemorySessionStore();\n"],"mappings":"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","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,34 +1,53 @@
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,
7
7
  createMcpAdminSetEnabledHandler,
8
8
  createMcpAdminStatusHandler
9
9
  } from "../features/mcp-admin/http/handlers.js";
10
- import { requireSessionForWrite } from "../modules/auth-guard.js";
11
- import { createAuthAttemptRateLimit } from "../modules/rate-limit.js";
10
+ import { createCsrfProtection, requireSessionForWrite } from "../modules/auth-guard.js";
11
+ import { createAuthAttemptRateLimit, createSessionAccessRateLimit } from "../modules/rate-limit.js";
12
12
  import { createServerEventsHandler } from "../modules/server-events-handler.js";
13
13
  import useAsync from "../modules/use-async.js";
14
14
  import { createMcpRouter } from "./mcp.js";
15
- const createApiRouter = (authConfig, mcpAdminService) => Router().use("/mcp", createMcpRouter(authConfig, mcpAdminService)).post("/auth/login", createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig))).post("/auth/logout", useAsync(createLogoutHandler(authConfig))).get("/auth/session", useAsync(createSessionStatusHandler(authConfig))).get(
16
- "/mcp-admin/status",
17
- requireSessionForWrite(authConfig),
18
- useAsync(createMcpAdminStatusHandler(mcpAdminService))
19
- ).post(
20
- "/mcp-admin/enabled",
21
- requireSessionForWrite(authConfig),
22
- useAsync(createMcpAdminSetEnabledHandler(mcpAdminService))
23
- ).post(
24
- "/mcp-admin/token/rotate",
25
- requireSessionForWrite(authConfig),
26
- useAsync(createMcpAdminRotateTokenHandler(mcpAdminService))
27
- ).post(
28
- "/mcp-admin/token/revoke",
29
- requireSessionForWrite(authConfig),
30
- useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
31
- ).post("/image", requireSessionForWrite(authConfig), useAsync(createUploadImageHandler())).post("/image-from-src", requireSessionForWrite(authConfig), useAsync(createUploadImageFromSrcHandler())).get("/events", requireSessionForWrite(authConfig), createServerEventsHandler());
15
+ const createApiRouter = (authConfig, mcpAdminService) => {
16
+ const csrfProtection = createCsrfProtection(authConfig);
17
+ const requireSession = requireSessionForWrite(authConfig);
18
+ const sessionAccessRateLimit = createSessionAccessRateLimit();
19
+ return Router().use("/mcp", createMcpRouter(authConfig, mcpAdminService)).get("/auth/session", csrfProtection, useAsync(createSessionStatusHandler(authConfig))).post("/auth/login", csrfProtection, createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig))).post(
20
+ "/auth/logout",
21
+ sessionAccessRateLimit,
22
+ requireSession,
23
+ csrfProtection,
24
+ useAsync(createLogoutHandler(authConfig))
25
+ ).get(
26
+ "/mcp-admin/status",
27
+ sessionAccessRateLimit,
28
+ requireSession,
29
+ csrfProtection,
30
+ useAsync(createMcpAdminStatusHandler(mcpAdminService))
31
+ ).post(
32
+ "/mcp-admin/enabled",
33
+ sessionAccessRateLimit,
34
+ requireSession,
35
+ csrfProtection,
36
+ useAsync(createMcpAdminSetEnabledHandler(mcpAdminService))
37
+ ).post(
38
+ "/mcp-admin/token/rotate",
39
+ sessionAccessRateLimit,
40
+ requireSession,
41
+ csrfProtection,
42
+ useAsync(createMcpAdminRotateTokenHandler(mcpAdminService))
43
+ ).post(
44
+ "/mcp-admin/token/revoke",
45
+ sessionAccessRateLimit,
46
+ requireSession,
47
+ csrfProtection,
48
+ useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService))
49
+ ).post("/image", sessionAccessRateLimit, requireSession, csrfProtection, useAsync(createUploadImageHandler())).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
50
+ };
32
51
  export {
33
52
  createApiRouter
34
53
  };
@@ -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 { requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit } 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 Router()\n .use('/mcp', createMcpRouter(authConfig, mcpAdminService))\n .post('/auth/login', createAuthAttemptRateLimit(), useAsync(createLoginHandler(authConfig)))\n .post('/auth/logout', useAsync(createLogoutHandler(authConfig)))\n .get('/auth/session', useAsync(createSessionStatusHandler(authConfig)))\n .get(\n '/mcp-admin/status',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminStatusHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/enabled',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminSetEnabledHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/rotate',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminRotateTokenHandler(mcpAdminService)),\n )\n .post(\n '/mcp-admin/token/revoke',\n requireSessionForWrite(authConfig),\n useAsync(createMcpAdminRevokeTokenHandler(mcpAdminService)),\n )\n .post('/image', requireSessionForWrite(authConfig), useAsync(createUploadImageHandler()))\n .post('/image-from-src', requireSessionForWrite(authConfig), useAsync(createUploadImageFromSrcHandler()))\n .get('/events', requireSessionForWrite(authConfig), createServerEventsHandler());\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,8BAA8B;AAEvC,SAAS,kCAAkC;AAC3C,SAAS,iCAAiC;AAC1C,OAAO,cAAc;AACrB,SAAS,uBAAuB;AAOzB,MAAM,kBAAkB,CAAC,YAAwB,oBACpD,OAAO,EACF,IAAI,QAAQ,gBAAgB,YAAY,eAAe,CAAC,EACxD,KAAK,eAAe,2BAA2B,GAAG,SAAS,mBAAmB,UAAU,CAAC,CAAC,EAC1F,KAAK,gBAAgB,SAAS,oBAAoB,UAAU,CAAC,CAAC,EAC9D,IAAI,iBAAiB,SAAS,2BAA2B,UAAU,CAAC,CAAC,EACrE;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,4BAA4B,eAAe,CAAC;AACzD,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,gCAAgC,eAAe,CAAC;AAC7D,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,iCAAiC,eAAe,CAAC;AAC9D,EACC;AAAA,EACG;AAAA,EACA,uBAAuB,UAAU;AAAA,EACjC,SAAS,iCAAiC,eAAe,CAAC;AAC9D,EACC,KAAK,UAAU,uBAAuB,UAAU,GAAG,SAAS,yBAAyB,CAAC,CAAC,EACvF,KAAK,mBAAmB,uBAAuB,UAAU,GAAG,SAAS,gCAAgC,CAAC,CAAC,EACvG,IAAI,WAAW,uBAAuB,UAAU,GAAG,0BAA0B,CAAC;","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":[]}
@@ -4,8 +4,19 @@ import {
4
4
  createLoginPageSubmitHandler,
5
5
  createLogoutPageHandler
6
6
  } from "../features/auth/http/pages.js";
7
- import { createAuthAttemptRateLimit } from "../modules/rate-limit.js";
8
- const createAuthPagesRouter = (authConfig) => Router().get("/login", createLoginPageHandler(authConfig)).post("/login", createAuthAttemptRateLimit(), createLoginPageSubmitHandler(authConfig)).post("/logout", createLogoutPageHandler(authConfig));
7
+ import { createCsrfProtection, createLoginCsrfFailureHandler, requireSessionForWrite } from "../modules/auth-guard.js";
8
+ import { createAuthAttemptRateLimit, createSessionAccessRateLimit } from "../modules/rate-limit.js";
9
+ 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));
19
+ };
9
20
  export {
10
21
  createAuthPagesRouter
11
22
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/auth-pages.ts"],"sourcesContent":["import { Router } from 'express';\nimport {\n createLoginPageHandler,\n createLoginPageSubmitHandler,\n createLogoutPageHandler,\n} from '../features/auth/http/pages.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit } from '../modules/rate-limit.js';\n\nexport const createAuthPagesRouter = (authConfig: AuthConfig) =>\n Router()\n .get('/login', createLoginPageHandler(authConfig))\n .post('/login', createAuthAttemptRateLimit(), createLoginPageSubmitHandler(authConfig))\n .post('/logout', createLogoutPageHandler(authConfig));\n"],"mappings":"AAAA,SAAS,cAAc;AACvB;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AAEP,SAAS,kCAAkC;AAEpC,MAAM,wBAAwB,CAAC,eAClC,OAAO,EACF,IAAI,UAAU,uBAAuB,UAAU,CAAC,EAChD,KAAK,UAAU,2BAA2B,GAAG,6BAA6B,UAAU,CAAC,EACrF,KAAK,WAAW,wBAAwB,UAAU,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/auth-pages.ts"],"sourcesContent":["import { Router } from 'express';\nimport {\n createLoginPageHandler,\n createLoginPageSubmitHandler,\n createLogoutPageHandler,\n} from '../features/auth/http/pages.js';\nimport { createCsrfProtection, createLoginCsrfFailureHandler, requireSessionForWrite } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createAuthAttemptRateLimit, createSessionAccessRateLimit } from '../modules/rate-limit.js';\n\nexport const createAuthPagesRouter = (authConfig: AuthConfig) => {\n const csrfProtection = createCsrfProtection(authConfig);\n const sessionAccessRateLimit = createSessionAccessRateLimit();\n\n return Router()\n .get('/login', csrfProtection, createLoginPageHandler(authConfig))\n .post('/login', csrfProtection, createAuthAttemptRateLimit(), createLoginPageSubmitHandler(authConfig))\n .post(\n '/logout',\n sessionAccessRateLimit,\n requireSessionForWrite(authConfig),\n csrfProtection,\n createLogoutPageHandler(authConfig),\n )\n .use(createLoginCsrfFailureHandler(authConfig));\n};\n"],"mappings":"AAAA,SAAS,cAAc;AACvB;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,OACG;AACP,SAAS,sBAAsB,+BAA+B,8BAA8B;AAE5F,SAAS,4BAA4B,oCAAoC;AAElE,MAAM,wBAAwB,CAAC,eAA2B;AAC7D,QAAM,iBAAiB,qBAAqB,UAAU;AACtD,QAAM,yBAAyB,6BAA6B;AAE5D,SAAO,OAAO,EACT,IAAI,UAAU,gBAAgB,uBAAuB,UAAU,CAAC,EAChE,KAAK,UAAU,gBAAgB,2BAA2B,GAAG,6BAA6B,UAAU,CAAC,EACrG;AAAA,IACG;AAAA,IACA;AAAA,IACA,uBAAuB,UAAU;AAAA,IACjC;AAAA,IACA,wBAAwB,UAAU;AAAA,EACtC,EACC,IAAI,8BAA8B,UAAU,CAAC;AACtD;","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
- import { isAuthenticatedRequest } from "../modules/auth-guard.js";
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,14 +13,72 @@ const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
11
13
  }
12
14
  return path.extname(requestPath) === "";
13
15
  };
14
- const createClientRouter = (authConfig) => Router().use("/assets/images", express.static(paths.imageDir)).use((req, res, next) => {
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
+ };
45
+ const createClientRouteCsrfTokenMiddleware = (authConfig) => {
46
+ const csrfProtection = createCsrfProtection(authConfig);
47
+ return (req, res, next) => {
48
+ if (path.extname(req.path) !== "") {
49
+ next();
50
+ return;
51
+ }
52
+ csrfProtection(req, res, next);
53
+ };
54
+ };
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) => {
15
75
  if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {
16
76
  const redirectPath = encodeURIComponent(req.originalUrl || "/");
17
77
  res.redirect(303, `/login?next=${redirectPath}`);
18
78
  return;
19
79
  }
20
80
  next();
21
- }).use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (_req, res) => {
81
+ }).use(createClientRouteCsrfTokenMiddleware(authConfig)).use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (_req, res) => {
22
82
  res.sendFile(paths.clientIndex);
23
83
  });
24
84
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/client.ts"],"sourcesContent":["import express, { Router } from 'express';\nimport path from 'path';\nimport { 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\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(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,8BAA8B;AAEvC,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;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,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,6 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import { createHandler } from "graphql-http/lib/use/express";
3
- import { isAuthenticatedRequest, requireSessionForGraphql } from "../modules/auth-guard.js";
3
+ import { createCsrfProtection, isAuthenticatedRequest, requireSessionForGraphql } from "../modules/auth-guard.js";
4
4
  import { createMcpAuthMiddleware, createReadOnlyMcpValidationRule } from "../modules/mcp-auth.js";
5
5
  import schema from "../schema/index.js";
6
6
  const createGraphqlContext = (authConfig) => (req) => ({
@@ -9,24 +9,28 @@ const createGraphqlContext = (authConfig) => (req) => ({
9
9
  req: req.raw,
10
10
  res: req.context.res
11
11
  });
12
- const createGraphqlRouter = (authConfig, mcpAdminService) => Router().use(
13
- "/mcp",
14
- createMcpAuthMiddleware(authConfig, mcpAdminService),
15
- createHandler({
16
- schema,
17
- context: createGraphqlContext(authConfig),
18
- validationRules: (_req, _args, specifiedRules) => {
19
- return [...specifiedRules, createReadOnlyMcpValidationRule()];
20
- }
21
- })
22
- ).use(
23
- "/",
24
- requireSessionForGraphql(authConfig),
25
- createHandler({
26
- schema,
27
- context: createGraphqlContext(authConfig)
28
- })
29
- );
12
+ const createGraphqlRouter = (authConfig, mcpAdminService) => {
13
+ const csrfProtection = createCsrfProtection(authConfig);
14
+ return Router().use(
15
+ "/mcp",
16
+ createMcpAuthMiddleware(authConfig, mcpAdminService),
17
+ createHandler({
18
+ schema,
19
+ context: createGraphqlContext(authConfig),
20
+ validationRules: (_req, _args, specifiedRules) => {
21
+ return [...specifiedRules, createReadOnlyMcpValidationRule()];
22
+ }
23
+ })
24
+ ).use(
25
+ "/",
26
+ requireSessionForGraphql(authConfig),
27
+ csrfProtection,
28
+ createHandler({
29
+ schema,
30
+ context: createGraphqlContext(authConfig)
31
+ })
32
+ );
33
+ };
30
34
  export {
31
35
  createGraphqlRouter
32
36
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/routes/graphql.ts"],"sourcesContent":["import { type Request, type Response, Router } from 'express';\nimport { createHandler } from 'graphql-http/lib/use/express';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { isAuthenticatedRequest, requireSessionForGraphql } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createMcpAuthMiddleware, createReadOnlyMcpValidationRule } from '../modules/mcp-auth.js';\nimport schema from '../schema/index.js';\n\ntype McpGraphqlService = Pick<McpAdminService, 'getStatus' | 'validatePresentedToken'>;\ntype GraphqlRequestContext = { raw: Request; context: { res: Response } };\n\nconst createGraphqlContext = (authConfig: AuthConfig) => (req: GraphqlRequestContext) => ({\n authMode: authConfig.mode,\n isAuthenticated: isAuthenticatedRequest(req.raw),\n req: req.raw,\n res: req.context.res,\n});\n\nexport const createGraphqlRouter = (authConfig: AuthConfig, mcpAdminService: McpGraphqlService) =>\n Router()\n .use(\n '/mcp',\n createMcpAuthMiddleware(authConfig, mcpAdminService),\n createHandler({\n schema,\n context: createGraphqlContext(authConfig),\n validationRules: (_req, _args, specifiedRules) => {\n return [...specifiedRules, createReadOnlyMcpValidationRule()];\n },\n }),\n )\n .use(\n '/',\n requireSessionForGraphql(authConfig),\n createHandler({\n schema,\n context: createGraphqlContext(authConfig),\n }),\n );\n"],"mappings":"AAAA,SAAsC,cAAc;AACpD,SAAS,qBAAqB;AAE9B,SAAS,wBAAwB,gCAAgC;AAEjE,SAAS,yBAAyB,uCAAuC;AACzE,OAAO,YAAY;AAKnB,MAAM,uBAAuB,CAAC,eAA2B,CAAC,SAAgC;AAAA,EACtF,UAAU,WAAW;AAAA,EACrB,iBAAiB,uBAAuB,IAAI,GAAG;AAAA,EAC/C,KAAK,IAAI;AAAA,EACT,KAAK,IAAI,QAAQ;AACrB;AAEO,MAAM,sBAAsB,CAAC,YAAwB,oBACxD,OAAO,EACF;AAAA,EACG;AAAA,EACA,wBAAwB,YAAY,eAAe;AAAA,EACnD,cAAc;AAAA,IACV;AAAA,IACA,SAAS,qBAAqB,UAAU;AAAA,IACxC,iBAAiB,CAAC,MAAM,OAAO,mBAAmB;AAC9C,aAAO,CAAC,GAAG,gBAAgB,gCAAgC,CAAC;AAAA,IAChE;AAAA,EACJ,CAAC;AACL,EACC;AAAA,EACG;AAAA,EACA,yBAAyB,UAAU;AAAA,EACnC,cAAc;AAAA,IACV;AAAA,IACA,SAAS,qBAAqB,UAAU;AAAA,EAC5C,CAAC;AACL;","names":[]}
1
+ {"version":3,"sources":["../../src/routes/graphql.ts"],"sourcesContent":["import { type Request, type Response, Router } from 'express';\nimport { createHandler } from 'graphql-http/lib/use/express';\nimport type { McpAdminService } from '../features/mcp-admin/service.js';\nimport { createCsrfProtection, isAuthenticatedRequest, requireSessionForGraphql } from '../modules/auth-guard.js';\nimport type { AuthConfig } from '../modules/auth-mode.js';\nimport { createMcpAuthMiddleware, createReadOnlyMcpValidationRule } from '../modules/mcp-auth.js';\nimport schema from '../schema/index.js';\n\ntype McpGraphqlService = Pick<McpAdminService, 'getStatus' | 'validatePresentedToken'>;\ntype GraphqlRequestContext = { raw: Request; context: { res: Response } };\n\nconst createGraphqlContext = (authConfig: AuthConfig) => (req: GraphqlRequestContext) => ({\n authMode: authConfig.mode,\n isAuthenticated: isAuthenticatedRequest(req.raw),\n req: req.raw,\n res: req.context.res,\n});\n\nexport const createGraphqlRouter = (authConfig: AuthConfig, mcpAdminService: McpGraphqlService) => {\n const csrfProtection = createCsrfProtection(authConfig);\n\n return Router()\n .use(\n '/mcp',\n createMcpAuthMiddleware(authConfig, mcpAdminService),\n createHandler({\n schema,\n context: createGraphqlContext(authConfig),\n validationRules: (_req, _args, specifiedRules) => {\n return [...specifiedRules, createReadOnlyMcpValidationRule()];\n },\n }),\n )\n .use(\n '/',\n requireSessionForGraphql(authConfig),\n csrfProtection,\n createHandler({\n schema,\n context: createGraphqlContext(authConfig),\n }),\n );\n};\n"],"mappings":"AAAA,SAAsC,cAAc;AACpD,SAAS,qBAAqB;AAE9B,SAAS,sBAAsB,wBAAwB,gCAAgC;AAEvF,SAAS,yBAAyB,uCAAuC;AACzE,OAAO,YAAY;AAKnB,MAAM,uBAAuB,CAAC,eAA2B,CAAC,SAAgC;AAAA,EACtF,UAAU,WAAW;AAAA,EACrB,iBAAiB,uBAAuB,IAAI,GAAG;AAAA,EAC/C,KAAK,IAAI;AAAA,EACT,KAAK,IAAI,QAAQ;AACrB;AAEO,MAAM,sBAAsB,CAAC,YAAwB,oBAAuC;AAC/F,QAAM,iBAAiB,qBAAqB,UAAU;AAEtD,SAAO,OAAO,EACT;AAAA,IACG;AAAA,IACA,wBAAwB,YAAY,eAAe;AAAA,IACnD,cAAc;AAAA,MACV;AAAA,MACA,SAAS,qBAAqB,UAAU;AAAA,MACxC,iBAAiB,CAAC,MAAM,OAAO,mBAAmB;AAC9C,eAAO,CAAC,GAAG,gBAAgB,gCAAgC,CAAC;AAAA,MAChE;AAAA,IACJ,CAAC;AAAA,EACL,EACC;AAAA,IACG;AAAA,IACA,yBAAyB,UAAU;AAAA,IACnC;AAAA,IACA,cAAc;AAAA,MACV;AAAA,MACA,SAAS,qBAAqB,UAAU;AAAA,IAC5C,CAAC;AAAA,EACL;AACR;","names":[]}
@@ -1 +0,0 @@
1
- import{j as n}from"./note-vendor-i1M2FSI2.js";import{p as m,T as d}from"./index-BFfQKAUY.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-BFfQKAUY.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-BFfQKAUY.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};