nterminal 1.2.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 (197) hide show
  1. package/.env.example +12 -0
  2. package/LICENSE +674 -0
  3. package/README.md +181 -0
  4. package/assets/brand/app-icon-1024.png +0 -0
  5. package/assets/brand/app-icon-384.png +0 -0
  6. package/assets/brand/apple-touch-icon-360.png +0 -0
  7. package/assets/brand/favicon-32.png +0 -0
  8. package/assets/brand/favicon-64.png +0 -0
  9. package/assets/brand/favicon-96.png +0 -0
  10. package/assets/brand/favicon.svg +4 -0
  11. package/assets/brand/nterminal-mark-64.png +0 -0
  12. package/assets/brand/nterminal-mark.svg +4 -0
  13. package/assets/brand/nterminal-wordmark-486x68.png +0 -0
  14. package/assets/brand/nterminal-wordmark.svg +3 -0
  15. package/assets/screenshot/scr.png +0 -0
  16. package/bin/nterminal.js +114 -0
  17. package/dist/client/apple-touch-icon.png +0 -0
  18. package/dist/client/assets/MarkdownPreview-BeDi-V7k.js +29 -0
  19. package/dist/client/assets/MesloLGS-NF-Bold-Italic-DwFsXcwX.ttf +0 -0
  20. package/dist/client/assets/MesloLGS-NF-Bold-kN-HYz-g.ttf +0 -0
  21. package/dist/client/assets/MesloLGS-NF-Italic-CMg1T6-G.ttf +0 -0
  22. package/dist/client/assets/MesloLGS-NF-Regular-Cxr8pvCI.ttf +0 -0
  23. package/dist/client/assets/index-BQkKYjXb.js +33 -0
  24. package/dist/client/assets/index-WqeS39wU.css +1 -0
  25. package/dist/client/assets/notifications/character-2258.mp4 +0 -0
  26. package/dist/client/assets/notifications/character-2260.mp4 +0 -0
  27. package/dist/client/assets/notifications/character-2272.mp4 +0 -0
  28. package/dist/client/brand/nterminal-mark-64.png +0 -0
  29. package/dist/client/brand/nterminal-mark.svg +4 -0
  30. package/dist/client/brand/nterminal-wordmark-486x68.png +0 -0
  31. package/dist/client/brand/nterminal-wordmark.svg +3 -0
  32. package/dist/client/icons/app-icon-1024.png +0 -0
  33. package/dist/client/icons/app-icon-384.png +0 -0
  34. package/dist/client/icons/favicon-32.png +0 -0
  35. package/dist/client/icons/favicon-64.png +0 -0
  36. package/dist/client/icons/favicon-96.png +0 -0
  37. package/dist/client/icons/favicon.svg +4 -0
  38. package/dist/client/index.html +21 -0
  39. package/dist/client/manifest.webmanifest +24 -0
  40. package/dist/scripts/generate-secrets.js +3 -0
  41. package/dist/scripts/generate-secrets.js.map +1 -0
  42. package/dist/scripts/onboarding.js +814 -0
  43. package/dist/scripts/onboarding.js.map +1 -0
  44. package/dist/scripts/proxySetup.js +1007 -0
  45. package/dist/scripts/proxySetup.js.map +1 -0
  46. package/dist/server/agent/agentAuth.d.ts +6 -0
  47. package/dist/server/agent/agentAuth.js +35 -0
  48. package/dist/server/agent/agentAuth.js.map +1 -0
  49. package/dist/server/agent/agentProxy.d.ts +5 -0
  50. package/dist/server/agent/agentProxy.js +63 -0
  51. package/dist/server/agent/agentProxy.js.map +1 -0
  52. package/dist/server/agent/agentRoutes.d.ts +9 -0
  53. package/dist/server/agent/agentRoutes.js +327 -0
  54. package/dist/server/agent/agentRoutes.js.map +1 -0
  55. package/dist/server/agent/agentWebSocketProxy.d.ts +3 -0
  56. package/dist/server/agent/agentWebSocketProxy.js +65 -0
  57. package/dist/server/agent/agentWebSocketProxy.js.map +1 -0
  58. package/dist/server/auth/authService.d.ts +100 -0
  59. package/dist/server/auth/authService.js +415 -0
  60. package/dist/server/auth/authService.js.map +1 -0
  61. package/dist/server/auth/cookies.d.ts +11 -0
  62. package/dist/server/auth/cookies.js +39 -0
  63. package/dist/server/auth/cookies.js.map +1 -0
  64. package/dist/server/auth/ipMatch.d.ts +14 -0
  65. package/dist/server/auth/ipMatch.js +103 -0
  66. package/dist/server/auth/ipMatch.js.map +1 -0
  67. package/dist/server/auth/rateLimit.d.ts +17 -0
  68. package/dist/server/auth/rateLimit.js +25 -0
  69. package/dist/server/auth/rateLimit.js.map +1 -0
  70. package/dist/server/auth/totpService.d.ts +10 -0
  71. package/dist/server/auth/totpService.js +37 -0
  72. package/dist/server/auth/totpService.js.map +1 -0
  73. package/dist/server/config.d.ts +27 -0
  74. package/dist/server/config.js +138 -0
  75. package/dist/server/config.js.map +1 -0
  76. package/dist/server/files/fileExplorerService.d.ts +38 -0
  77. package/dist/server/files/fileExplorerService.js +551 -0
  78. package/dist/server/files/fileExplorerService.js.map +1 -0
  79. package/dist/server/files/rootToken.d.ts +51 -0
  80. package/dist/server/files/rootToken.js +139 -0
  81. package/dist/server/files/rootToken.js.map +1 -0
  82. package/dist/server/http.d.ts +13 -0
  83. package/dist/server/http.js +69 -0
  84. package/dist/server/http.js.map +1 -0
  85. package/dist/server/index.d.ts +1 -0
  86. package/dist/server/index.js +45 -0
  87. package/dist/server/index.js.map +1 -0
  88. package/dist/server/routes/agentManagementRoutes.d.ts +9 -0
  89. package/dist/server/routes/agentManagementRoutes.js +304 -0
  90. package/dist/server/routes/agentManagementRoutes.js.map +1 -0
  91. package/dist/server/routes/authRoutes.d.ts +10 -0
  92. package/dist/server/routes/authRoutes.js +95 -0
  93. package/dist/server/routes/authRoutes.js.map +1 -0
  94. package/dist/server/routes/fileRoutes.d.ts +11 -0
  95. package/dist/server/routes/fileRoutes.js +185 -0
  96. package/dist/server/routes/fileRoutes.js.map +1 -0
  97. package/dist/server/routes/notificationAssetRoutes.d.ts +9 -0
  98. package/dist/server/routes/notificationAssetRoutes.js +280 -0
  99. package/dist/server/routes/notificationAssetRoutes.js.map +1 -0
  100. package/dist/server/routes/securityRoutes.d.ts +7 -0
  101. package/dist/server/routes/securityRoutes.js +53 -0
  102. package/dist/server/routes/securityRoutes.js.map +1 -0
  103. package/dist/server/routes/socketBackpressure.d.ts +26 -0
  104. package/dist/server/routes/socketBackpressure.js +63 -0
  105. package/dist/server/routes/socketBackpressure.js.map +1 -0
  106. package/dist/server/routes/terminalLayoutRoutes.d.ts +9 -0
  107. package/dist/server/routes/terminalLayoutRoutes.js +108 -0
  108. package/dist/server/routes/terminalLayoutRoutes.js.map +1 -0
  109. package/dist/server/routes/terminalRoutes.d.ts +14 -0
  110. package/dist/server/routes/terminalRoutes.js +177 -0
  111. package/dist/server/routes/terminalRoutes.js.map +1 -0
  112. package/dist/server/routes/terminalWebSocket.d.ts +9 -0
  113. package/dist/server/routes/terminalWebSocket.js +129 -0
  114. package/dist/server/routes/terminalWebSocket.js.map +1 -0
  115. package/dist/server/routes/totpRoutes.d.ts +7 -0
  116. package/dist/server/routes/totpRoutes.js +46 -0
  117. package/dist/server/routes/totpRoutes.js.map +1 -0
  118. package/dist/server/routes/updateRoutes.d.ts +7 -0
  119. package/dist/server/routes/updateRoutes.js +24 -0
  120. package/dist/server/routes/updateRoutes.js.map +1 -0
  121. package/dist/server/routes/uploadRoutes.d.ts +9 -0
  122. package/dist/server/routes/uploadRoutes.js +95 -0
  123. package/dist/server/routes/uploadRoutes.js.map +1 -0
  124. package/dist/server/storage/fileStore.d.ts +90 -0
  125. package/dist/server/storage/fileStore.js +275 -0
  126. package/dist/server/storage/fileStore.js.map +1 -0
  127. package/dist/server/system/stats.d.ts +2 -0
  128. package/dist/server/system/stats.js +37 -0
  129. package/dist/server/system/stats.js.map +1 -0
  130. package/dist/server/terminal/NodePtyAdapter.d.ts +4 -0
  131. package/dist/server/terminal/NodePtyAdapter.js +14 -0
  132. package/dist/server/terminal/NodePtyAdapter.js.map +1 -0
  133. package/dist/server/terminal/PtyAdapter.d.ts +57 -0
  134. package/dist/server/terminal/PtyAdapter.js +2 -0
  135. package/dist/server/terminal/PtyAdapter.js.map +1 -0
  136. package/dist/server/terminal/TerminalManager.d.ts +74 -0
  137. package/dist/server/terminal/TerminalManager.js +561 -0
  138. package/dist/server/terminal/TerminalManager.js.map +1 -0
  139. package/dist/server/terminal/TmuxPtyAdapter.d.ts +25 -0
  140. package/dist/server/terminal/TmuxPtyAdapter.js +543 -0
  141. package/dist/server/terminal/TmuxPtyAdapter.js.map +1 -0
  142. package/dist/server/terminal/codexTranscriptSource.d.ts +9 -0
  143. package/dist/server/terminal/codexTranscriptSource.js +144 -0
  144. package/dist/server/terminal/codexTranscriptSource.js.map +1 -0
  145. package/dist/server/terminal/cwdResolver.d.ts +8 -0
  146. package/dist/server/terminal/cwdResolver.js +37 -0
  147. package/dist/server/terminal/cwdResolver.js.map +1 -0
  148. package/dist/server/terminal/outputBuffer.d.ts +7 -0
  149. package/dist/server/terminal/outputBuffer.js +17 -0
  150. package/dist/server/terminal/outputBuffer.js.map +1 -0
  151. package/dist/server/terminal/transcriptHistory.d.ts +7 -0
  152. package/dist/server/terminal/transcriptHistory.js +315 -0
  153. package/dist/server/terminal/transcriptHistory.js.map +1 -0
  154. package/dist/server/update/gitUpdate.d.ts +27 -0
  155. package/dist/server/update/gitUpdate.js +241 -0
  156. package/dist/server/update/gitUpdate.js.map +1 -0
  157. package/dist/server/uploads/uploadPaths.d.ts +18 -0
  158. package/dist/server/uploads/uploadPaths.js +116 -0
  159. package/dist/server/uploads/uploadPaths.js.map +1 -0
  160. package/dist/server/uploads/uploadService.d.ts +21 -0
  161. package/dist/server/uploads/uploadService.js +230 -0
  162. package/dist/server/uploads/uploadService.js.map +1 -0
  163. package/dist/shared/layoutState.d.ts +6 -0
  164. package/dist/shared/layoutState.js +115 -0
  165. package/dist/shared/layoutState.js.map +1 -0
  166. package/dist/shared/notificationAssets.d.ts +9 -0
  167. package/dist/shared/notificationAssets.js +27 -0
  168. package/dist/shared/notificationAssets.js.map +1 -0
  169. package/dist/shared/protocol.d.ts +308 -0
  170. package/dist/shared/protocol.js +29 -0
  171. package/dist/shared/protocol.js.map +1 -0
  172. package/dist/shared/types.d.ts +56 -0
  173. package/dist/shared/types.js +2 -0
  174. package/dist/shared/types.js.map +1 -0
  175. package/docs/assets/nterminal-workspace.png +0 -0
  176. package/docs/configuration.md +97 -0
  177. package/docs/features.md +126 -0
  178. package/docs/onboarding.md +122 -0
  179. package/docs/operations.md +112 -0
  180. package/docs/terminal-history.md +54 -0
  181. package/package.json +85 -0
  182. package/public/apple-touch-icon.png +0 -0
  183. package/public/assets/notifications/character-2258.mp4 +0 -0
  184. package/public/assets/notifications/character-2260.mp4 +0 -0
  185. package/public/assets/notifications/character-2272.mp4 +0 -0
  186. package/public/brand/nterminal-mark-64.png +0 -0
  187. package/public/brand/nterminal-mark.svg +4 -0
  188. package/public/brand/nterminal-wordmark-486x68.png +0 -0
  189. package/public/brand/nterminal-wordmark.svg +3 -0
  190. package/public/icons/app-icon-1024.png +0 -0
  191. package/public/icons/app-icon-384.png +0 -0
  192. package/public/icons/favicon-32.png +0 -0
  193. package/public/icons/favicon-64.png +0 -0
  194. package/public/icons/favicon-96.png +0 -0
  195. package/public/icons/favicon.svg +4 -0
  196. package/public/manifest.webmanifest +24 -0
  197. package/scripts/nterminalctl +588 -0
@@ -0,0 +1,39 @@
1
+ export const cookieNames = {
2
+ session: 'nterminal_session',
3
+ device: 'nterminal_device'
4
+ };
5
+ const legacyCookieNames = ['nterminal_device_id', 'nterminal_device_token'];
6
+ const deviceCookieMaxAgeSeconds = 30 * 24 * 60 * 60;
7
+ export function authCookieOptions(config, maxAgeSeconds) {
8
+ const options = {
9
+ httpOnly: true,
10
+ sameSite: 'lax',
11
+ secure: config.cookieSecure,
12
+ path: '/'
13
+ };
14
+ if (maxAgeSeconds !== undefined) {
15
+ options.maxAge = maxAgeSeconds;
16
+ }
17
+ return options;
18
+ }
19
+ export function setSessionCookie(reply, config, sessionId) {
20
+ clearLegacyAuthCookies(reply, config);
21
+ reply.setCookie(cookieNames.session, sessionId, authCookieOptions(config, Math.ceil(config.sessionTtlMs / 1000)));
22
+ }
23
+ export function setDeviceCookie(reply, config, token) {
24
+ reply.setCookie(cookieNames.device, token, authCookieOptions(config, deviceCookieMaxAgeSeconds));
25
+ }
26
+ export function clearSessionCookie(reply, config) {
27
+ reply.clearCookie(cookieNames.session, authCookieOptions(config));
28
+ }
29
+ export function clearAuthCookies(reply, config) {
30
+ reply.clearCookie(cookieNames.session, authCookieOptions(config));
31
+ reply.clearCookie(cookieNames.device, authCookieOptions(config));
32
+ clearLegacyAuthCookies(reply, config);
33
+ }
34
+ function clearLegacyAuthCookies(reply, config) {
35
+ for (const name of legacyCookieNames) {
36
+ reply.clearCookie(name, authCookieOptions(config));
37
+ }
38
+ }
39
+ //# sourceMappingURL=cookies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../../src/server/auth/cookies.ts"],"names":[],"mappings":"AAIA,MAAM,CAAC,MAAM,WAAW,GAAG;IACzB,OAAO,EAAE,mBAAmB;IAC5B,MAAM,EAAE,kBAAkB;CAClB,CAAC;AAEX,MAAM,iBAAiB,GAAG,CAAC,qBAAqB,EAAE,wBAAwB,CAAU,CAAC;AAErF,MAAM,yBAAyB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAEpD,MAAM,UAAU,iBAAiB,CAAC,MAAiB,EAAE,aAAsB;IACzE,MAAM,OAAO,GAAsD;QACjE,QAAQ,EAAE,IAAI;QACd,QAAQ,EAAE,KAAc;QACxB,MAAM,EAAE,MAAM,CAAC,YAAY;QAC3B,IAAI,EAAE,GAAG;KACV,CAAC;IACF,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,CAAC,MAAM,GAAG,aAAa,CAAC;IACjC,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,MAAiB,EAAE,SAAiB;IACxF,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtC,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,OAAO,EAAE,SAAS,EAAE,iBAAiB,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;AACpH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAmB,EAAE,MAAiB,EAAE,KAAa;IACnF,KAAK,CAAC,SAAS,CAAC,WAAW,CAAC,MAAM,EAAE,KAAK,EAAE,iBAAiB,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC,CAAC;AACnG,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,KAAmB,EAAE,MAAiB;IACvE,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAmB,EAAE,MAAiB;IACrE,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IAClE,KAAK,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IACjE,sBAAsB,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,SAAS,sBAAsB,CAAC,KAAmB,EAAE,MAAiB;IACpE,KAAK,MAAM,IAAI,IAAI,iBAAiB,EAAE,CAAC;QACrC,KAAK,CAAC,WAAW,CAAC,IAAI,EAAE,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;IACrD,CAAC;AACH,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Normalize a raw IP into a canonical string. IPv4-mapped IPv6 addresses
3
+ * (e.g. "::ffff:1.2.3.4") collapse to their IPv4 form so the same client
4
+ * compares equal regardless of socket family.
5
+ */
6
+ export declare function normalizeIp(raw: string | undefined | null): string | null;
7
+ /** Validate that a string is a bare IP or CIDR (e.g. "1.2.3.4" or "10.0.0.0/8"). */
8
+ export declare function isValidIpRule(value: string): boolean;
9
+ export declare function ipMatchesAny(ip: string | null, rules: Array<{
10
+ value: string;
11
+ }>): boolean;
12
+ export declare const DEFAULT_SESSION_IP_MASK_V4 = 24;
13
+ export declare const DEFAULT_SESSION_IP_MASK_V6 = 48;
14
+ export declare function isWithinSessionNetwork(candidate: string, reference: string, ipv4Bits?: number, ipv6Bits?: number): boolean;
@@ -0,0 +1,103 @@
1
+ import ipaddr from 'ipaddr.js';
2
+ /**
3
+ * Normalize a raw IP into a canonical string. IPv4-mapped IPv6 addresses
4
+ * (e.g. "::ffff:1.2.3.4") collapse to their IPv4 form so the same client
5
+ * compares equal regardless of socket family.
6
+ */
7
+ export function normalizeIp(raw) {
8
+ if (!raw) {
9
+ return null;
10
+ }
11
+ const trimmed = raw.trim();
12
+ if (!ipaddr.isValid(trimmed)) {
13
+ return null;
14
+ }
15
+ let addr = ipaddr.parse(trimmed);
16
+ if (addr.kind() === 'ipv6' && addr.isIPv4MappedAddress()) {
17
+ addr = addr.toIPv4Address();
18
+ }
19
+ return addr.toNormalizedString();
20
+ }
21
+ /** Validate that a string is a bare IP or CIDR (e.g. "1.2.3.4" or "10.0.0.0/8"). */
22
+ export function isValidIpRule(value) {
23
+ const trimmed = value.trim();
24
+ if (trimmed.includes('/')) {
25
+ try {
26
+ ipaddr.parseCIDR(trimmed);
27
+ return true;
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
33
+ return ipaddr.isValid(trimmed);
34
+ }
35
+ /** True if `ip` matches the rule, which may be a bare IP or a CIDR range. */
36
+ function ipMatchesRule(ip, rule) {
37
+ const trimmed = rule.trim();
38
+ let candidate;
39
+ try {
40
+ candidate = ipaddr.parse(ip);
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ if (trimmed.includes('/')) {
46
+ let range;
47
+ try {
48
+ range = ipaddr.parseCIDR(trimmed);
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ if (candidate.kind() !== range[0].kind()) {
54
+ return false;
55
+ }
56
+ return candidate.match(range);
57
+ }
58
+ const normalizedRule = normalizeIp(trimmed);
59
+ return normalizedRule !== null && normalizedRule === normalizeIp(ip);
60
+ }
61
+ export function ipMatchesAny(ip, rules) {
62
+ if (!ip) {
63
+ return false;
64
+ }
65
+ return rules.some((rule) => ipMatchesRule(ip, rule.value));
66
+ }
67
+ // Default prefix lengths used to decide whether a new IP "looks like" the
68
+ // same network as the one a session was bound to. /24 for IPv4 covers the
69
+ // common residential / CGNAT rotation case (ISP keeps reassigning addresses
70
+ // inside the same /24 every few minutes) without letting a totally unrelated
71
+ // network reuse the session. /48 for IPv6 matches the canonical site prefix.
72
+ export const DEFAULT_SESSION_IP_MASK_V4 = 24;
73
+ export const DEFAULT_SESSION_IP_MASK_V6 = 48;
74
+ // True when `candidate` falls inside `reference`'s /prefix subnet of the
75
+ // matching address family. Both inputs must already be normalized strings
76
+ // (use `normalizeIp` first). Returns false when families differ, either
77
+ // input fails to parse, or the prefix is out of range — callers treat that
78
+ // as "no match" rather than throwing, so a malformed value can't accidentally
79
+ // let a session through.
80
+ export function isWithinSessionNetwork(candidate, reference, ipv4Bits = DEFAULT_SESSION_IP_MASK_V4, ipv6Bits = DEFAULT_SESSION_IP_MASK_V6) {
81
+ if (candidate === reference) {
82
+ return true;
83
+ }
84
+ let candAddr;
85
+ let refAddr;
86
+ try {
87
+ candAddr = ipaddr.parse(candidate);
88
+ refAddr = ipaddr.parse(reference);
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ if (candAddr.kind() !== refAddr.kind()) {
94
+ return false;
95
+ }
96
+ const bits = candAddr.kind() === 'ipv4' ? ipv4Bits : ipv6Bits;
97
+ const maxBits = candAddr.kind() === 'ipv4' ? 32 : 128;
98
+ if (!Number.isInteger(bits) || bits < 0 || bits > maxBits) {
99
+ return false;
100
+ }
101
+ return candAddr.match([refAddr, bits]);
102
+ }
103
+ //# sourceMappingURL=ipMatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ipMatch.js","sourceRoot":"","sources":["../../../src/server/auth/ipMatch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ export interface RateLimitOptions {
2
+ limit: number;
3
+ windowMs: number;
4
+ now?: () => number;
5
+ }
6
+ export interface RateLimitResult {
7
+ allowed: boolean;
8
+ retryAfterMs: number;
9
+ }
10
+ export declare class InMemoryRateLimit {
11
+ private readonly options;
12
+ private readonly buckets;
13
+ private readonly now;
14
+ constructor(options: RateLimitOptions);
15
+ consume(key: string): RateLimitResult;
16
+ reset(key: string): void;
17
+ }
@@ -0,0 +1,25 @@
1
+ export class InMemoryRateLimit {
2
+ options;
3
+ buckets = new Map();
4
+ now;
5
+ constructor(options) {
6
+ this.options = options;
7
+ this.now = options.now ?? Date.now;
8
+ }
9
+ consume(key) {
10
+ const now = this.now();
11
+ const existing = this.buckets.get(key);
12
+ const bucket = existing && existing.resetAt > now ? existing : { count: 0, resetAt: now + this.options.windowMs };
13
+ if (bucket.count >= this.options.limit) {
14
+ this.buckets.set(key, bucket);
15
+ return { allowed: false, retryAfterMs: Math.max(0, bucket.resetAt - now) };
16
+ }
17
+ bucket.count += 1;
18
+ this.buckets.set(key, bucket);
19
+ return { allowed: true, retryAfterMs: 0 };
20
+ }
21
+ reset(key) {
22
+ this.buckets.delete(key);
23
+ }
24
+ }
25
+ //# sourceMappingURL=rateLimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../../../src/server/auth/rateLimit.ts"],"names":[],"mappings":"AAgBA,MAAM,OAAO,iBAAiB;IAIC;IAHZ,OAAO,GAAG,IAAI,GAAG,EAAkB,CAAC;IACpC,GAAG,CAAe;IAEnC,YAA6B,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;QACpD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED,OAAO,CAAC,GAAW;QACjB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,QAAQ,IAAI,QAAQ,CAAC,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QAElH,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACvC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;YAC9B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,OAAO,GAAG,GAAG,CAAC,EAAE,CAAC;QAC7E,CAAC;QAED,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;QAClB,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9B,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,EAAE,CAAC;IAC5C,CAAC;IAED,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ export interface TotpSetup {
2
+ secret: string;
3
+ qrDataUrl: string;
4
+ }
5
+ export declare class TotpService {
6
+ private readonly issuer;
7
+ constructor(issuer?: string);
8
+ generateSetup(accountName: string): Promise<TotpSetup>;
9
+ verify(secret: string, token: string): boolean;
10
+ }
@@ -0,0 +1,37 @@
1
+ import * as OTPAuth from 'otpauth';
2
+ import QRCode from 'qrcode';
3
+ export class TotpService {
4
+ issuer;
5
+ constructor(issuer = 'NTerminal') {
6
+ this.issuer = issuer;
7
+ }
8
+ async generateSetup(accountName) {
9
+ const totp = new OTPAuth.TOTP({
10
+ issuer: this.issuer,
11
+ label: accountName,
12
+ algorithm: 'SHA1',
13
+ digits: 6,
14
+ period: 30,
15
+ secret: new OTPAuth.Secret()
16
+ });
17
+ const uri = totp.toString();
18
+ const qrDataUrl = await QRCode.toDataURL(uri, { width: 256, margin: 2 });
19
+ return { secret: totp.secret.base32, qrDataUrl };
20
+ }
21
+ verify(secret, token) {
22
+ try {
23
+ const totp = new OTPAuth.TOTP({
24
+ algorithm: 'SHA1',
25
+ digits: 6,
26
+ period: 30,
27
+ secret: OTPAuth.Secret.fromBase32(secret)
28
+ });
29
+ const delta = totp.validate({ token, window: 1 });
30
+ return delta !== null;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ }
37
+ //# sourceMappingURL=totpService.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"totpService.js","sourceRoot":"","sources":["../../../src/server/auth/totpService.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,MAAM,MAAM,QAAQ,CAAC;AAO5B,MAAM,OAAO,WAAW;IACL,MAAM,CAAS;IAEhC,YAAY,MAAM,GAAG,WAAW;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,WAAmB;QACrC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;YAC5B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,KAAK,EAAE,WAAW;YAClB,SAAS,EAAE,MAAM;YACjB,MAAM,EAAE,CAAC;YACT,MAAM,EAAE,EAAE;YACV,MAAM,EAAE,IAAI,OAAO,CAAC,MAAM,EAAE;SAC7B,CAAC,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;QACzE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;IACnD,CAAC;IAED,MAAM,CAAC,MAAc,EAAE,KAAa;QAClC,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,IAAI,CAAC;gBAC5B,SAAS,EAAE,MAAM;gBACjB,MAAM,EAAE,CAAC;gBACT,MAAM,EAAE,EAAE;gBACV,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC;aAC1C,CAAC,CAAC;YACH,MAAM,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;YAClD,OAAO,KAAK,KAAK,IAAI,CAAC;QACxB,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ export interface AppConfig {
2
+ host: string;
3
+ port: number;
4
+ workspaceRoot: string;
5
+ shell: string;
6
+ statePath: string;
7
+ sessionSecret: string;
8
+ sessionTtlMs: number;
9
+ cookieSecure: boolean;
10
+ allowedOrigins: string[];
11
+ staticRoot: string;
12
+ isProduction: boolean;
13
+ agentToken: string | null;
14
+ trustProxy: boolean | string;
15
+ uploadMaxFiles: number;
16
+ uploadMaxFileBytes: number;
17
+ uploadMaxBatchBytes: number;
18
+ fileListMaxEntries: number;
19
+ fileTextMaxBytes: number;
20
+ filePreviewMaxBytes: number;
21
+ }
22
+ export interface ConfigEnv {
23
+ [key: string]: string | undefined;
24
+ }
25
+ export declare function loadDotEnvFile(filePath?: string, baseEnv?: ConfigEnv): ConfigEnv;
26
+ export declare function loadConfig(env?: ConfigEnv): AppConfig;
27
+ export declare function isAllowedOrigin(config: Pick<AppConfig, 'allowedOrigins'>, origin: string | undefined): boolean;
@@ -0,0 +1,138 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ function readInt(value, fallback, name) {
5
+ if (value === undefined || value.trim() === '') {
6
+ return fallback;
7
+ }
8
+ const parsed = Number(value);
9
+ if (!Number.isInteger(parsed) || parsed <= 0) {
10
+ throw new Error(`${name} must be a positive integer`);
11
+ }
12
+ return parsed;
13
+ }
14
+ function requireSecret(env, name, fallbackForTest) {
15
+ const value = env[name]?.trim();
16
+ const secret = value || (env.NODE_ENV === 'test' ? fallbackForTest : undefined);
17
+ if (!secret) {
18
+ throw new Error(`${name} is required`);
19
+ }
20
+ if (secret.length < 16) {
21
+ throw new Error(`${name} must be at least 16 characters`);
22
+ }
23
+ if (name === 'NTERMINAL_SESSION_SECRET' && secret.length < 24) {
24
+ throw new Error(`${name} must be at least 24 characters`);
25
+ }
26
+ return secret;
27
+ }
28
+ function parseTrustProxy(raw) {
29
+ const value = raw?.trim();
30
+ if (!value || value === 'false') {
31
+ return false;
32
+ }
33
+ if (value === 'true') {
34
+ return true;
35
+ }
36
+ // Number of hops or a comma-separated list of trusted IPs/CIDRs — passed through to Fastify.
37
+ return value;
38
+ }
39
+ function resolveShell(env) {
40
+ return env.NTERMINAL_SHELL?.trim() || env.SHELL?.trim() || (os.platform() === 'win32' ? 'powershell.exe' : '/bin/bash');
41
+ }
42
+ function parseOrigins(raw, host, port) {
43
+ const configured = raw
44
+ ?.split(',')
45
+ .map((origin) => origin.trim())
46
+ .filter(Boolean);
47
+ if (configured && configured.length > 0) {
48
+ return configured;
49
+ }
50
+ return [`http://${host}:${port}`, `http://localhost:${port}`, `http://127.0.0.1:${port}`];
51
+ }
52
+ export function loadDotEnvFile(filePath = '.env', baseEnv = process.env) {
53
+ if (!fs.existsSync(filePath)) {
54
+ return baseEnv;
55
+ }
56
+ const parsed = { ...baseEnv };
57
+ const content = fs.readFileSync(filePath, 'utf8');
58
+ for (const rawLine of content.split(/\r?\n/)) {
59
+ const line = rawLine.trim();
60
+ if (!line || line.startsWith('#')) {
61
+ continue;
62
+ }
63
+ const separator = line.indexOf('=');
64
+ if (separator === -1) {
65
+ continue;
66
+ }
67
+ const key = line.slice(0, separator).trim();
68
+ if (!key || parsed[key] !== undefined) {
69
+ continue;
70
+ }
71
+ parsed[key] = stripEnvQuotes(line.slice(separator + 1).trim());
72
+ }
73
+ return parsed;
74
+ }
75
+ export function loadConfig(env = process.env) {
76
+ const host = env.NTERMINAL_HOST?.trim() || '127.0.0.1';
77
+ const port = readInt(env.NTERMINAL_PORT, 3107, 'NTERMINAL_PORT');
78
+ // Default to the operator's HOME — process.cwd() was confusing because
79
+ // nterminalctl deploy starts the server from the repo directory, so new
80
+ // terminals opened in 127.0.0.1:3107 would start in the NTerminal repo
81
+ // instead of the operator's working directory.
82
+ const workspaceRoot = path.resolve(expandHomePath(env.NTERMINAL_WORKSPACE_ROOT?.trim() || os.homedir()));
83
+ const statePath = path.resolve(env.NTERMINAL_STATE_PATH?.trim() || '.nterminal/state.json');
84
+ const sessionTtlSeconds = readInt(env.NTERMINAL_SESSION_TTL_SECONDS, 43_200, 'NTERMINAL_SESSION_TTL_SECONDS');
85
+ const uploadMaxFiles = readInt(env.NTERMINAL_UPLOAD_MAX_FILES, 1024, 'NTERMINAL_UPLOAD_MAX_FILES');
86
+ const uploadMaxFileBytes = readInt(env.NTERMINAL_UPLOAD_MAX_FILE_BYTES, 536_870_912, 'NTERMINAL_UPLOAD_MAX_FILE_BYTES');
87
+ const uploadMaxBatchBytes = readInt(env.NTERMINAL_UPLOAD_MAX_BATCH_BYTES, 2_147_483_648, 'NTERMINAL_UPLOAD_MAX_BATCH_BYTES');
88
+ const fileListMaxEntries = readInt(env.NTERMINAL_FILE_LIST_MAX_ENTRIES, 2000, 'NTERMINAL_FILE_LIST_MAX_ENTRIES');
89
+ const fileTextMaxBytes = readInt(env.NTERMINAL_FILE_TEXT_MAX_BYTES, 1_048_576, 'NTERMINAL_FILE_TEXT_MAX_BYTES');
90
+ const filePreviewMaxBytes = readInt(env.NTERMINAL_FILE_PREVIEW_MAX_BYTES, 52_428_800, 'NTERMINAL_FILE_PREVIEW_MAX_BYTES');
91
+ const isProduction = env.NODE_ENV === 'production';
92
+ const rawAgentToken = env.NTERMINAL_AGENT_TOKEN?.trim() || null;
93
+ const agentToken = rawAgentToken && rawAgentToken.length >= 32 ? rawAgentToken : null;
94
+ const trustProxy = parseTrustProxy(env.NTERMINAL_TRUST_PROXY);
95
+ return {
96
+ host,
97
+ port,
98
+ workspaceRoot,
99
+ shell: resolveShell(env),
100
+ statePath,
101
+ sessionSecret: requireSecret(env, 'NTERMINAL_SESSION_SECRET', 'test-session-secret-that-is-long-enough'),
102
+ sessionTtlMs: sessionTtlSeconds * 1000,
103
+ cookieSecure: env.NTERMINAL_COOKIE_SECURE === 'true' || (isProduction && env.NTERMINAL_COOKIE_SECURE !== 'false'),
104
+ allowedOrigins: parseOrigins(env.NTERMINAL_ALLOWED_ORIGINS, host, port),
105
+ staticRoot: path.resolve(env.NTERMINAL_STATIC_ROOT?.trim() || 'dist/client'),
106
+ isProduction,
107
+ agentToken,
108
+ trustProxy,
109
+ uploadMaxFiles,
110
+ uploadMaxFileBytes,
111
+ uploadMaxBatchBytes,
112
+ fileListMaxEntries,
113
+ fileTextMaxBytes,
114
+ filePreviewMaxBytes
115
+ };
116
+ }
117
+ export function isAllowedOrigin(config, origin) {
118
+ if (!origin) {
119
+ return true;
120
+ }
121
+ return config.allowedOrigins.includes(origin);
122
+ }
123
+ function stripEnvQuotes(value) {
124
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
125
+ return value.slice(1, -1);
126
+ }
127
+ return value;
128
+ }
129
+ function expandHomePath(value) {
130
+ if (value === '~') {
131
+ return os.homedir();
132
+ }
133
+ if (value.startsWith('~/')) {
134
+ return path.join(os.homedir(), value.slice(2));
135
+ }
136
+ return value;
137
+ }
138
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import type { AppConfig } from '../config.js';
2
+ import type { FileDeletePreviewResponse, FileDeleteResponse, FileEntry, FileListResponse, FileReadResponse, FileVersion, FileWriteResponse } from '../../shared/protocol.js';
3
+ export type FileExplorerConfig = Pick<AppConfig, 'sessionSecret' | 'fileListMaxEntries' | 'fileTextMaxBytes' | 'filePreviewMaxBytes'>;
4
+ export interface FilePreviewResult {
5
+ absolutePath: string;
6
+ contentType: string;
7
+ size: number;
8
+ stream: NodeJS.ReadableStream;
9
+ }
10
+ export declare class FileExplorerError extends Error {
11
+ readonly statusCode: number;
12
+ readonly code: string;
13
+ constructor(statusCode: number, code: string);
14
+ }
15
+ export declare class FileExplorerService {
16
+ private readonly config;
17
+ constructor(config: FileExplorerConfig);
18
+ resolveRootPath(rootPath: string): Promise<string>;
19
+ list(rootPath: string, requestPath: string): Promise<FileListResponse>;
20
+ readText(rootPath: string, requestPath: string): Promise<FileReadResponse>;
21
+ resolveFileForTerminalOpen(rootPath: string, requestPath: string): Promise<{
22
+ path: string;
23
+ absolutePath: string;
24
+ }>;
25
+ writeText(rootPath: string, requestPath: string, content: string, expectedVersion: FileVersion): Promise<FileWriteResponse>;
26
+ createEntry(rootPath: string, requestPath: string, kind: 'file' | 'directory'): Promise<FileEntry>;
27
+ moveEntry(rootPath: string, sourcePath: string, destinationPath: string): Promise<FileEntry>;
28
+ previewDelete(rootPath: string, requestPath: string, rootToken: string): Promise<FileDeletePreviewResponse>;
29
+ deleteEntry(rootPath: string, requestPath: string, previewToken: string, rootToken: string): Promise<FileDeleteResponse>;
30
+ previewFile(rootPath: string, requestPath: string): Promise<FilePreviewResult>;
31
+ private resolveExistingPath;
32
+ private resolveNewPath;
33
+ private assertNoSymlinkSegments;
34
+ private entryForPath;
35
+ private inspectDeleteTarget;
36
+ private countDescendants;
37
+ private openNoFollow;
38
+ }