ocean-brain 0.7.4 → 0.7.5

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 (53) hide show
  1. package/package.json +1 -1
  2. package/server/client/dist/assets/{Calendar-Bz7jheg6.js → Calendar-Bc2zJueR.js} +1 -1
  3. package/server/client/dist/assets/{Callout-DlFLkTCG.js → Callout-B4kA_Cvr.js} +1 -1
  4. package/server/client/dist/assets/{Graph-BdxtJvog.js → Graph-uq5jfJgt.js} +1 -1
  5. package/server/client/dist/assets/{Image.es-BFodqKS5.js → Image.es-BpAOJjJT.js} +1 -1
  6. package/server/client/dist/assets/{Note-BgMxsWZT.js → Note-Bo8ZYRP-.js} +1 -1
  7. package/server/client/dist/assets/{Plus.es-Yq7J1Ur4.js → Plus.es-Dc_js8AC.js} +1 -1
  8. package/server/client/dist/assets/{Reminders-CUsR56ff.js → Reminders-lj7Equld.js} +1 -1
  9. package/server/client/dist/assets/{Search-DncrGgOZ.js → Search-1wcRO5--.js} +1 -1
  10. package/server/client/dist/assets/{SurfaceCard-CfL1jD-O.js → SurfaceCard-CkAVMLPs.js} +1 -1
  11. package/server/client/dist/assets/{Tag-CKDLs2Y6.js → Tag-CeygwFfv.js} +1 -1
  12. package/server/client/dist/assets/{TagNotes-ByX_Qias.js → TagNotes-B8DqItxw.js} +1 -1
  13. package/server/client/dist/assets/{Trash.es-BPrF6TPV.js → Trash.es-OuQVb_SX.js} +1 -1
  14. package/server/client/dist/assets/{ViewNotes-r1kxKZ8O.js → ViewNotes-BH7PbdOD.js} +1 -1
  15. package/server/client/dist/assets/{ViewSectionTableRenderer-DQ4UeCnm.js → ViewSectionTableRenderer-BJwhd5be.js} +1 -1
  16. package/server/client/dist/assets/{Views-FzFBtNT2.js → Views-eYFtsdFj.js} +1 -1
  17. package/server/client/dist/assets/{image.api-Bcg3TFCG.js → image.api-C0eYGCeH.js} +1 -1
  18. package/server/client/dist/assets/{index-DiMt3Dz4.js → index-D3BfroIT.js} +1 -1
  19. package/server/client/dist/assets/{index-BFfQKAUY.js → index-jIfkRdY_.js} +42 -42
  20. package/server/client/dist/assets/{manage-image-DzDlncCM.js → manage-image-C0YwkLIP.js} +1 -1
  21. package/server/client/dist/assets/{manage-image-detail-Bqw8NURO.js → manage-image-detail-C8Q7K7I3.js} +1 -1
  22. package/server/client/dist/assets/{mcp-BqasqTWC.js → mcp-DHHhTLC8.js} +1 -1
  23. package/server/client/dist/assets/{placeholder-Ro00byT7.js → placeholder-Cr_nI8UI.js} +1 -1
  24. package/server/client/dist/assets/{properties-Tid6-ghT.js → properties-CvYWJWUM.js} +1 -1
  25. package/server/client/dist/assets/{trash-bHK02-Up.js → trash-DA27aW7L.js} +1 -1
  26. package/server/client/dist/assets/{useReminderMutate-BWymIujb.js → useReminderMutate-CDmIEwsw.js} +1 -1
  27. package/server/client/dist/index.html +1 -1
  28. package/server/dist/app.js +2 -2
  29. package/server/dist/app.js.map +1 -1
  30. package/server/dist/features/auth/http/api.js +5 -1
  31. package/server/dist/features/auth/http/api.js.map +1 -1
  32. package/server/dist/features/auth/http/pages.js +8 -1
  33. package/server/dist/features/auth/http/pages.js.map +1 -1
  34. package/server/dist/features/auth/service.js +16 -7
  35. package/server/dist/features/auth/service.js.map +1 -1
  36. package/server/dist/modules/auth-guard.js +21 -2
  37. package/server/dist/modules/auth-guard.js.map +1 -1
  38. package/server/dist/modules/auth-redirect.js +10 -0
  39. package/server/dist/modules/auth-redirect.js.map +1 -0
  40. package/server/dist/modules/rate-limit.js +15 -1
  41. package/server/dist/modules/rate-limit.js.map +1 -1
  42. package/server/dist/modules/server-events-handler.js +17 -0
  43. package/server/dist/modules/server-events-handler.js.map +1 -1
  44. package/server/dist/modules/session-store.js +149 -0
  45. package/server/dist/modules/session-store.js.map +1 -0
  46. package/server/dist/routes/api.js +44 -19
  47. package/server/dist/routes/api.js.map +1 -1
  48. package/server/dist/routes/auth-pages.js +13 -2
  49. package/server/dist/routes/auth-pages.js.map +1 -1
  50. package/server/dist/routes/client.js +12 -2
  51. package/server/dist/routes/client.js.map +1 -1
  52. package/server/dist/routes/graphql.js +23 -19
  53. package/server/dist/routes/graphql.js.map +1 -1
@@ -1,5 +1,6 @@
1
1
  import { rateLimit } from "express-rate-limit";
2
2
  const AUTH_RATE_LIMIT_MESSAGE = "Too many authentication attempts. Please try again later.";
3
+ const SESSION_ACCESS_RATE_LIMIT_MESSAGE = "Too many authenticated requests. Please try again later.";
3
4
  const createAuthAttemptRateLimit = () => rateLimit({
4
5
  windowMs: 15 * 60 * 1e3,
5
6
  limit: 10,
@@ -12,7 +13,20 @@ const createAuthAttemptRateLimit = () => rateLimit({
12
13
  });
13
14
  }
14
15
  });
16
+ const createSessionAccessRateLimit = () => rateLimit({
17
+ windowMs: 60 * 1e3,
18
+ limit: 300,
19
+ standardHeaders: true,
20
+ legacyHeaders: false,
21
+ handler: (_req, res) => {
22
+ res.status(429).json({
23
+ code: "SESSION_RATE_LIMITED",
24
+ message: SESSION_ACCESS_RATE_LIMIT_MESSAGE
25
+ });
26
+ }
27
+ });
15
28
  export {
16
- createAuthAttemptRateLimit
29
+ createAuthAttemptRateLimit,
30
+ createSessionAccessRateLimit
17
31
  };
18
32
  //# sourceMappingURL=rate-limit.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/rate-limit.ts"],"sourcesContent":["import { rateLimit } from 'express-rate-limit';\n\nconst AUTH_RATE_LIMIT_MESSAGE = 'Too many authentication attempts. Please try again later.';\n\nexport const createAuthAttemptRateLimit = () =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'AUTH_RATE_LIMITED',\n message: AUTH_RATE_LIMIT_MESSAGE,\n });\n },\n });\n"],"mappings":"AAAA,SAAS,iBAAiB;AAE1B,MAAM,0BAA0B;AAEzB,MAAM,6BAA6B,MACtC,UAAU;AAAA,EACN,UAAU,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/rate-limit.ts"],"sourcesContent":["import { rateLimit } from 'express-rate-limit';\n\nconst AUTH_RATE_LIMIT_MESSAGE = 'Too many authentication attempts. Please try again later.';\nconst SESSION_ACCESS_RATE_LIMIT_MESSAGE = 'Too many authenticated requests. Please try again later.';\n\nexport const createAuthAttemptRateLimit = () =>\n rateLimit({\n windowMs: 15 * 60 * 1000,\n limit: 10,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'AUTH_RATE_LIMITED',\n message: AUTH_RATE_LIMIT_MESSAGE,\n });\n },\n });\n\nexport const createSessionAccessRateLimit = () =>\n rateLimit({\n windowMs: 60 * 1000,\n limit: 300,\n standardHeaders: true,\n legacyHeaders: false,\n handler: (_req, res) => {\n res.status(429).json({\n code: 'SESSION_RATE_LIMITED',\n message: SESSION_ACCESS_RATE_LIMIT_MESSAGE,\n });\n },\n });\n"],"mappings":"AAAA,SAAS,iBAAiB;AAE1B,MAAM,0BAA0B;AAChC,MAAM,oCAAoC;AAEnC,MAAM,6BAA6B,MACtC,UAAU;AAAA,EACN,UAAU,KAAK,KAAK;AAAA,EACpB,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;AAEE,MAAM,+BAA+B,MACxC,UAAU;AAAA,EACN,UAAU,KAAK;AAAA,EACf,OAAO;AAAA,EACP,iBAAiB;AAAA,EACjB,eAAe;AAAA,EACf,SAAS,CAAC,MAAM,QAAQ;AACpB,QAAI,OAAO,GAAG,EAAE,KAAK;AAAA,MACjB,MAAM;AAAA,MACN,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AACJ,CAAC;","names":[]}
@@ -1,5 +1,16 @@
1
1
  import { serializeServerEvent, subscribeServerEvents } from "./server-events.js";
2
+ import { AUTH_SESSION_IDLE_TIMEOUT_MS } from "./session-store.js";
2
3
  const KEEP_ALIVE_INTERVAL_MS = 3e4;
4
+ const getEventStreamLifetimeMs = (expires) => {
5
+ if (!expires) {
6
+ return AUTH_SESSION_IDLE_TIMEOUT_MS;
7
+ }
8
+ const expiresAt = expires instanceof Date ? expires : new Date(expires);
9
+ if (Number.isNaN(expiresAt.getTime())) {
10
+ return AUTH_SESSION_IDLE_TIMEOUT_MS;
11
+ }
12
+ return Math.max(0, Math.min(expiresAt.getTime() - Date.now(), AUTH_SESSION_IDLE_TIMEOUT_MS));
13
+ };
3
14
  const createServerEventsHandler = () => {
4
15
  return async (req, res) => {
5
16
  res.status(200);
@@ -15,6 +26,11 @@ const createServerEventsHandler = () => {
15
26
  const keepAliveTimer = setInterval(() => {
16
27
  res.write(": keepalive\n\n");
17
28
  }, KEEP_ALIVE_INTERVAL_MS);
29
+ keepAliveTimer.unref?.();
30
+ const sessionExpiryTimer = setTimeout(() => {
31
+ res.end();
32
+ }, getEventStreamLifetimeMs(req.session?.cookie.expires));
33
+ sessionExpiryTimer.unref?.();
18
34
  let cleanedUp = false;
19
35
  const cleanup = () => {
20
36
  if (cleanedUp) {
@@ -22,6 +38,7 @@ const createServerEventsHandler = () => {
22
38
  }
23
39
  cleanedUp = true;
24
40
  clearInterval(keepAliveTimer);
41
+ clearTimeout(sessionExpiryTimer);
25
42
  unsubscribe();
26
43
  };
27
44
  req.on("close", cleanup);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/modules/server-events-handler.ts"],"sourcesContent":["import type { Controller } from '~/types/index.js';\nimport { serializeServerEvent, subscribeServerEvents } from './server-events.js';\n\nconst KEEP_ALIVE_INTERVAL_MS = 30_000;\n\nexport const createServerEventsHandler = (): Controller => {\n return async (req, res) => {\n res.status(200);\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders?.();\n res.write(': connected\\n\\n');\n\n const unsubscribe = subscribeServerEvents((event) => {\n res.write(serializeServerEvent(event));\n });\n\n const keepAliveTimer = setInterval(() => {\n res.write(': keepalive\\n\\n');\n }, KEEP_ALIVE_INTERVAL_MS);\n\n let cleanedUp = false;\n\n const cleanup = () => {\n if (cleanedUp) {\n return;\n }\n\n cleanedUp = true;\n clearInterval(keepAliveTimer);\n unsubscribe();\n };\n\n req.on('close', cleanup);\n res.on('close', cleanup);\n };\n};\n"],"mappings":"AACA,SAAS,sBAAsB,6BAA6B;AAE5D,MAAM,yBAAyB;AAExB,MAAM,4BAA4B,MAAkB;AACvD,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,OAAO,GAAG;AACd,QAAI,UAAU,gBAAgB,kCAAkC;AAChE,QAAI,UAAU,iBAAiB,wBAAwB;AACvD,QAAI,UAAU,cAAc,YAAY;AACxC,QAAI,UAAU,qBAAqB,IAAI;AACvC,QAAI,eAAe;AACnB,QAAI,MAAM,iBAAiB;AAE3B,UAAM,cAAc,sBAAsB,CAAC,UAAU;AACjD,UAAI,MAAM,qBAAqB,KAAK,CAAC;AAAA,IACzC,CAAC;AAED,UAAM,iBAAiB,YAAY,MAAM;AACrC,UAAI,MAAM,iBAAiB;AAAA,IAC/B,GAAG,sBAAsB;AAEzB,QAAI,YAAY;AAEhB,UAAM,UAAU,MAAM;AAClB,UAAI,WAAW;AACX;AAAA,MACJ;AAEA,kBAAY;AACZ,oBAAc,cAAc;AAC5B,kBAAY;AAAA,IAChB;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAAA,EAC3B;AACJ;","names":[]}
1
+ {"version":3,"sources":["../../src/modules/server-events-handler.ts"],"sourcesContent":["import type { Controller } from '~/types/index.js';\nimport { serializeServerEvent, subscribeServerEvents } from './server-events.js';\nimport { AUTH_SESSION_IDLE_TIMEOUT_MS } from './session-store.js';\n\nconst KEEP_ALIVE_INTERVAL_MS = 30_000;\n\nconst getEventStreamLifetimeMs = (expires?: Date | string | null) => {\n if (!expires) {\n return AUTH_SESSION_IDLE_TIMEOUT_MS;\n }\n\n const expiresAt = expires instanceof Date ? expires : new Date(expires);\n\n if (Number.isNaN(expiresAt.getTime())) {\n return AUTH_SESSION_IDLE_TIMEOUT_MS;\n }\n\n return Math.max(0, Math.min(expiresAt.getTime() - Date.now(), AUTH_SESSION_IDLE_TIMEOUT_MS));\n};\n\nexport const createServerEventsHandler = (): Controller => {\n return async (req, res) => {\n res.status(200);\n res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');\n res.setHeader('Cache-Control', 'no-cache, no-transform');\n res.setHeader('Connection', 'keep-alive');\n res.setHeader('X-Accel-Buffering', 'no');\n res.flushHeaders?.();\n res.write(': connected\\n\\n');\n\n const unsubscribe = subscribeServerEvents((event) => {\n res.write(serializeServerEvent(event));\n });\n\n const keepAliveTimer = setInterval(() => {\n res.write(': keepalive\\n\\n');\n }, KEEP_ALIVE_INTERVAL_MS);\n keepAliveTimer.unref?.();\n\n const sessionExpiryTimer = setTimeout(() => {\n res.end();\n }, getEventStreamLifetimeMs(req.session?.cookie.expires));\n sessionExpiryTimer.unref?.();\n\n let cleanedUp = false;\n\n const cleanup = () => {\n if (cleanedUp) {\n return;\n }\n\n cleanedUp = true;\n clearInterval(keepAliveTimer);\n clearTimeout(sessionExpiryTimer);\n unsubscribe();\n };\n\n req.on('close', cleanup);\n res.on('close', cleanup);\n };\n};\n"],"mappings":"AACA,SAAS,sBAAsB,6BAA6B;AAC5D,SAAS,oCAAoC;AAE7C,MAAM,yBAAyB;AAE/B,MAAM,2BAA2B,CAAC,YAAmC;AACjE,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AAEA,QAAM,YAAY,mBAAmB,OAAO,UAAU,IAAI,KAAK,OAAO;AAEtE,MAAI,OAAO,MAAM,UAAU,QAAQ,CAAC,GAAG;AACnC,WAAO;AAAA,EACX;AAEA,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,UAAU,QAAQ,IAAI,KAAK,IAAI,GAAG,4BAA4B,CAAC;AAC/F;AAEO,MAAM,4BAA4B,MAAkB;AACvD,SAAO,OAAO,KAAK,QAAQ;AACvB,QAAI,OAAO,GAAG;AACd,QAAI,UAAU,gBAAgB,kCAAkC;AAChE,QAAI,UAAU,iBAAiB,wBAAwB;AACvD,QAAI,UAAU,cAAc,YAAY;AACxC,QAAI,UAAU,qBAAqB,IAAI;AACvC,QAAI,eAAe;AACnB,QAAI,MAAM,iBAAiB;AAE3B,UAAM,cAAc,sBAAsB,CAAC,UAAU;AACjD,UAAI,MAAM,qBAAqB,KAAK,CAAC;AAAA,IACzC,CAAC;AAED,UAAM,iBAAiB,YAAY,MAAM;AACrC,UAAI,MAAM,iBAAiB;AAAA,IAC/B,GAAG,sBAAsB;AACzB,mBAAe,QAAQ;AAEvB,UAAM,qBAAqB,WAAW,MAAM;AACxC,UAAI,IAAI;AAAA,IACZ,GAAG,yBAAyB,IAAI,SAAS,OAAO,OAAO,CAAC;AACxD,uBAAmB,QAAQ;AAE3B,QAAI,YAAY;AAEhB,UAAM,UAAU,MAAM;AAClB,UAAI,WAAW;AACX;AAAA,MACJ;AAEA,kBAAY;AACZ,oBAAc,cAAc;AAC5B,mBAAa,kBAAkB;AAC/B,kBAAY;AAAA,IAChB;AAEA,QAAI,GAAG,SAAS,OAAO;AACvB,QAAI,GAAG,SAAS,OAAO;AAAA,EAC3B;AACJ;","names":[]}
@@ -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":[]}
@@ -7,28 +7,53 @@ import {
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())).post(
50
+ "/image-from-src",
51
+ sessionAccessRateLimit,
52
+ requireSession,
53
+ csrfProtection,
54
+ useAsync(createUploadImageFromSrcHandler())
55
+ ).get("/events", sessionAccessRateLimit, requireSession, csrfProtection, createServerEventsHandler());
56
+ };
32
57
  export {
33
58
  createApiRouter
34
59
  };
@@ -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 { 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":[]}
@@ -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,6 +1,6 @@
1
1
  import express, { Router } from "express";
2
2
  import path from "path";
3
- import { isAuthenticatedRequest } from "../modules/auth-guard.js";
3
+ import { createCsrfProtection, isAuthenticatedRequest } from "../modules/auth-guard.js";
4
4
  import { paths } from "../paths.js";
5
5
  const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
6
6
  if (authConfig.mode !== "password" || authenticated) {
@@ -11,6 +11,16 @@ const shouldBlockClientRoute = (authConfig, requestPath, authenticated) => {
11
11
  }
12
12
  return path.extname(requestPath) === "";
13
13
  };
14
+ const createClientRouteCsrfTokenMiddleware = (authConfig) => {
15
+ const csrfProtection = createCsrfProtection(authConfig);
16
+ return (req, res, next) => {
17
+ if (path.extname(req.path) !== "") {
18
+ next();
19
+ return;
20
+ }
21
+ csrfProtection(req, res, next);
22
+ };
23
+ };
14
24
  const createClientRouter = (authConfig) => Router().use("/assets/images", express.static(paths.imageDir)).use((req, res, next) => {
15
25
  if (shouldBlockClientRoute(authConfig, req.path, isAuthenticatedRequest(req))) {
16
26
  const redirectPath = encodeURIComponent(req.originalUrl || "/");
@@ -18,7 +28,7 @@ const createClientRouter = (authConfig) => Router().use("/assets/images", expres
18
28
  return;
19
29
  }
20
30
  next();
21
- }).use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (_req, res) => {
31
+ }).use(createClientRouteCsrfTokenMiddleware(authConfig)).use(express.static(paths.clientDist, { extensions: ["html"] })).get(/.*/, (_req, res) => {
22
32
  res.sendFile(paths.clientIndex);
23
33
  });
24
34
  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, { 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,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":[]}