@agent-link/server 0.1.188 → 0.1.189
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.
- package/dist/auth-manager.d.ts +36 -0
- package/dist/auth-manager.js +96 -0
- package/dist/auth-manager.js.map +1 -0
- package/dist/http.d.ts +4 -0
- package/dist/http.js +85 -0
- package/dist/http.js.map +1 -0
- package/dist/index.js +5 -82
- package/dist/index.js.map +1 -1
- package/dist/message-relay.d.ts +17 -0
- package/dist/message-relay.js +23 -0
- package/dist/message-relay.js.map +1 -0
- package/dist/session-manager.d.ts +44 -0
- package/dist/session-manager.js +83 -0
- package/dist/session-manager.js.map +1 -0
- package/dist/ws-agent.js +19 -27
- package/dist/ws-agent.js.map +1 -1
- package/dist/ws-client.js +31 -37
- package/dist/ws-client.js.map +1 -1
- package/package.json +1 -1
- package/web/dist/assets/{index-C9bIrYkZ.js → index-DIO7Hox0.js} +30 -30
- package/web/dist/assets/{index-C9bIrYkZ.js.map → index-DIO7Hox0.js.map} +1 -1
- package/web/dist/index.html +1 -1
- package/dist/auth.d.ts +0 -13
- package/dist/auth.js +0 -65
- package/dist/auth.js.map +0 -1
- package/dist/context.d.ts +0 -52
- package/dist/context.js +0 -60
- package/dist/context.js.map +0 -1
package/web/dist/index.html
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
: '/vendor/github-dark.min.css';
|
|
23
23
|
})();
|
|
24
24
|
</script>
|
|
25
|
-
<script type="module" crossorigin src="/assets/index-
|
|
25
|
+
<script type="module" crossorigin src="/assets/index-DIO7Hox0.js"></script>
|
|
26
26
|
<link rel="stylesheet" crossorigin href="/assets/index-Y1FN_mFe.css">
|
|
27
27
|
</head>
|
|
28
28
|
<body>
|
package/dist/auth.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare function hashPassword(password: string): {
|
|
2
|
-
hash: string;
|
|
3
|
-
salt: string;
|
|
4
|
-
};
|
|
5
|
-
export declare function verifyPassword(submitted: string, storedHash: string, storedSalt: string): boolean;
|
|
6
|
-
export declare function generateAuthToken(sessionId: string): string;
|
|
7
|
-
export declare function verifyAuthToken(token: string, expectedSessionId: string): boolean;
|
|
8
|
-
export declare function isSessionLocked(sessionId: string): boolean;
|
|
9
|
-
export declare function recordFailure(sessionId: string): {
|
|
10
|
-
locked: boolean;
|
|
11
|
-
remaining: number;
|
|
12
|
-
};
|
|
13
|
-
export declare function clearFailures(sessionId: string): void;
|
package/dist/auth.js
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { scryptSync, randomBytes, timingSafeEqual, createHmac } from 'crypto';
|
|
2
|
-
import { authAttempts, serverSecret } from './context.js';
|
|
3
|
-
const MAX_FAILURES = 5;
|
|
4
|
-
const LOCKOUT_MS = 15 * 60 * 1000; // 15 minutes
|
|
5
|
-
const TOKEN_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
6
|
-
export function hashPassword(password) {
|
|
7
|
-
const salt = randomBytes(16).toString('base64');
|
|
8
|
-
const hash = scryptSync(password, salt, 32).toString('base64');
|
|
9
|
-
return { hash, salt };
|
|
10
|
-
}
|
|
11
|
-
export function verifyPassword(submitted, storedHash, storedSalt) {
|
|
12
|
-
const computed = scryptSync(submitted, storedSalt, 32);
|
|
13
|
-
const expected = Buffer.from(storedHash, 'base64');
|
|
14
|
-
if (computed.length !== expected.length)
|
|
15
|
-
return false;
|
|
16
|
-
return timingSafeEqual(computed, expected);
|
|
17
|
-
}
|
|
18
|
-
export function generateAuthToken(sessionId) {
|
|
19
|
-
const ts = Date.now().toString();
|
|
20
|
-
const hmac = createHmac('sha256', serverSecret).update(`${sessionId}:${ts}`).digest('base64url');
|
|
21
|
-
return `${sessionId}:${ts}:${hmac}`;
|
|
22
|
-
}
|
|
23
|
-
export function verifyAuthToken(token, expectedSessionId) {
|
|
24
|
-
const idx1 = token.indexOf(':');
|
|
25
|
-
const idx2 = token.indexOf(':', idx1 + 1);
|
|
26
|
-
if (idx1 === -1 || idx2 === -1)
|
|
27
|
-
return false;
|
|
28
|
-
const sessionId = token.slice(0, idx1);
|
|
29
|
-
const ts = token.slice(idx1 + 1, idx2);
|
|
30
|
-
const hmac = token.slice(idx2 + 1);
|
|
31
|
-
if (sessionId !== expectedSessionId)
|
|
32
|
-
return false;
|
|
33
|
-
const age = Date.now() - parseInt(ts, 10);
|
|
34
|
-
if (isNaN(age) || age < 0 || age > TOKEN_TTL_MS)
|
|
35
|
-
return false;
|
|
36
|
-
const expected = createHmac('sha256', serverSecret).update(`${sessionId}:${ts}`).digest('base64url');
|
|
37
|
-
return hmac === expected;
|
|
38
|
-
}
|
|
39
|
-
export function isSessionLocked(sessionId) {
|
|
40
|
-
const state = authAttempts.get(sessionId);
|
|
41
|
-
if (!state?.lockedUntil)
|
|
42
|
-
return false;
|
|
43
|
-
if (Date.now() > state.lockedUntil.getTime()) {
|
|
44
|
-
authAttempts.delete(sessionId);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
export function recordFailure(sessionId) {
|
|
50
|
-
let state = authAttempts.get(sessionId);
|
|
51
|
-
if (!state) {
|
|
52
|
-
state = { failures: 0, lockedUntil: null };
|
|
53
|
-
authAttempts.set(sessionId, state);
|
|
54
|
-
}
|
|
55
|
-
state.failures++;
|
|
56
|
-
if (state.failures >= MAX_FAILURES) {
|
|
57
|
-
state.lockedUntil = new Date(Date.now() + LOCKOUT_MS);
|
|
58
|
-
return { locked: true, remaining: 0 };
|
|
59
|
-
}
|
|
60
|
-
return { locked: false, remaining: MAX_FAILURES - state.failures };
|
|
61
|
-
}
|
|
62
|
-
export function clearFailures(sessionId) {
|
|
63
|
-
authAttempts.delete(sessionId);
|
|
64
|
-
}
|
|
65
|
-
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/context.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import { WebSocket } from 'ws';
|
|
2
|
-
export interface AgentSession {
|
|
3
|
-
ws: WebSocket;
|
|
4
|
-
agentId: string;
|
|
5
|
-
name: string;
|
|
6
|
-
hostname: string;
|
|
7
|
-
workDir: string;
|
|
8
|
-
version: string;
|
|
9
|
-
sessionId: string;
|
|
10
|
-
sessionKey: Uint8Array | null;
|
|
11
|
-
connectedAt: Date;
|
|
12
|
-
isAlive: boolean;
|
|
13
|
-
passwordHash: string | null;
|
|
14
|
-
passwordSalt: string | null;
|
|
15
|
-
}
|
|
16
|
-
export interface WebClient {
|
|
17
|
-
ws: WebSocket;
|
|
18
|
-
clientId: string;
|
|
19
|
-
sessionId: string;
|
|
20
|
-
sessionKey: Uint8Array | null;
|
|
21
|
-
connectedAt: Date;
|
|
22
|
-
isAlive: boolean;
|
|
23
|
-
}
|
|
24
|
-
export declare const agents: Map<string, AgentSession>;
|
|
25
|
-
export declare const sessionToAgent: Map<string, string>;
|
|
26
|
-
export declare const webClients: Map<string, WebClient>;
|
|
27
|
-
export interface AuthAttemptState {
|
|
28
|
-
failures: number;
|
|
29
|
-
lockedUntil: Date | null;
|
|
30
|
-
}
|
|
31
|
-
export declare const authAttempts: Map<string, AuthAttemptState>;
|
|
32
|
-
export declare const pendingAuth: Map<string, string>;
|
|
33
|
-
export declare const sessionAuth: Map<string, {
|
|
34
|
-
passwordHash: string;
|
|
35
|
-
passwordSalt: string;
|
|
36
|
-
}>;
|
|
37
|
-
export declare const serverSecret: NonSharedBuffer;
|
|
38
|
-
/**
|
|
39
|
-
* Generate a short, URL-safe session ID
|
|
40
|
-
*/
|
|
41
|
-
export declare function generateSessionId(): string;
|
|
42
|
-
/**
|
|
43
|
-
* Clean up dead WebSocket connections detected by heartbeat.
|
|
44
|
-
* Terminates dead sockets, removes them from maps, pings alive ones,
|
|
45
|
-
* and notifies web clients when their agent disconnects.
|
|
46
|
-
*/
|
|
47
|
-
export declare function cleanupDeadConnections(notifyFn: (client: WebClient, msg: {
|
|
48
|
-
type: string;
|
|
49
|
-
}) => void): {
|
|
50
|
-
removedAgents: string[];
|
|
51
|
-
removedClients: string[];
|
|
52
|
-
};
|
package/dist/context.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { randomBytes } from 'crypto';
|
|
2
|
-
// Agent sessions: agentId → AgentSession
|
|
3
|
-
export const agents = new Map();
|
|
4
|
-
// Session ID → agentId (reverse lookup)
|
|
5
|
-
export const sessionToAgent = new Map();
|
|
6
|
-
// Web clients: clientId → WebClient
|
|
7
|
-
export const webClients = new Map();
|
|
8
|
-
export const authAttempts = new Map();
|
|
9
|
-
// Pending auth: clientId → sessionId (web clients awaiting password verification)
|
|
10
|
-
export const pendingAuth = new Map();
|
|
11
|
-
// Session auth: sessionId → { passwordHash, passwordSalt }
|
|
12
|
-
// Persists across agent disconnects so web clients can still be required to authenticate
|
|
13
|
-
export const sessionAuth = new Map();
|
|
14
|
-
// Server secret for HMAC auth tokens (generated fresh on each server start)
|
|
15
|
-
export const serverSecret = randomBytes(32);
|
|
16
|
-
/**
|
|
17
|
-
* Generate a short, URL-safe session ID
|
|
18
|
-
*/
|
|
19
|
-
export function generateSessionId() {
|
|
20
|
-
return randomBytes(12).toString('base64url');
|
|
21
|
-
}
|
|
22
|
-
/**
|
|
23
|
-
* Clean up dead WebSocket connections detected by heartbeat.
|
|
24
|
-
* Terminates dead sockets, removes them from maps, pings alive ones,
|
|
25
|
-
* and notifies web clients when their agent disconnects.
|
|
26
|
-
*/
|
|
27
|
-
export function cleanupDeadConnections(notifyFn) {
|
|
28
|
-
const removedAgents = [];
|
|
29
|
-
const removedClients = [];
|
|
30
|
-
for (const [agentId, agent] of agents) {
|
|
31
|
-
if (!agent.isAlive) {
|
|
32
|
-
console.log(`[Heartbeat] Agent ${agent.name} timed out`);
|
|
33
|
-
agent.ws.terminate();
|
|
34
|
-
sessionToAgent.delete(agent.sessionId);
|
|
35
|
-
agents.delete(agentId);
|
|
36
|
-
removedAgents.push(agentId);
|
|
37
|
-
// Notify connected web clients that agent is gone
|
|
38
|
-
for (const [, client] of webClients) {
|
|
39
|
-
if (client.sessionId === agent.sessionId) {
|
|
40
|
-
notifyFn(client, { type: 'agent_disconnected' });
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
agent.isAlive = false;
|
|
46
|
-
agent.ws.ping();
|
|
47
|
-
}
|
|
48
|
-
for (const [clientId, client] of webClients) {
|
|
49
|
-
if (!client.isAlive) {
|
|
50
|
-
client.ws.terminate();
|
|
51
|
-
webClients.delete(clientId);
|
|
52
|
-
removedClients.push(clientId);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
client.isAlive = false;
|
|
56
|
-
client.ws.ping();
|
|
57
|
-
}
|
|
58
|
-
return { removedAgents, removedClients };
|
|
59
|
-
}
|
|
60
|
-
//# sourceMappingURL=context.js.map
|
package/dist/context.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAC;AA0BrC,yCAAyC;AACzC,MAAM,CAAC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAwB,CAAC;AAEtD,wCAAwC;AACxC,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExD,oCAAoC;AACpC,MAAM,CAAC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAqB,CAAC;AAOvD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,GAAG,EAA4B,CAAC;AAEhE,kFAAkF;AAClF,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;AAErD,2DAA2D;AAC3D,yFAAyF;AACzF,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,GAAG,EAA0D,CAAC;AAE7F,4EAA4E;AAC5E,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;AAE5C;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AAC/C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAA4D;IAE5D,MAAM,aAAa,GAAa,EAAE,CAAC;IACnC,MAAM,cAAc,GAAa,EAAE,CAAC;IAEpC,KAAK,MAAM,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,MAAM,EAAE,CAAC;QACtC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;YACnB,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,CAAC,IAAI,YAAY,CAAC,CAAC;YACzD,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;YACrB,cAAc,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;YACvC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YACvB,aAAa,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE5B,kDAAkD;YAClD,KAAK,MAAM,CAAC,EAAE,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;gBACpC,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,CAAC,SAAS,EAAE,CAAC;oBACzC,QAAQ,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBACnD,CAAC;YACH,CAAC;YACD,SAAS;QACX,CAAC;QACD,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC;QACtB,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IAClB,CAAC;IAED,KAAK,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;QAC5C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;YACpB,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC;YACtB,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC5B,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAC9B,SAAS;QACX,CAAC;QACD,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;QACvB,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IACnB,CAAC;IAED,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAC3C,CAAC"}
|