@byoky/relay 0.4.16 → 0.4.18
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/server.js +27 -11
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
- package/src/server.ts +25 -11
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ const PORT = parseInt(process.env.PORT || "8787", 10);
|
|
|
5
5
|
const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
6
6
|
const rooms = new Map();
|
|
7
7
|
const authAttempts = new Map();
|
|
8
|
-
const AUTH_RATE_LIMIT =
|
|
8
|
+
const AUTH_RATE_LIMIT = 10;
|
|
9
9
|
const AUTH_RATE_WINDOW = 60_000;
|
|
10
10
|
function send(ws, data) {
|
|
11
11
|
if (ws.readyState === WebSocket.OPEN) {
|
|
@@ -48,6 +48,8 @@ wss.on("connection", (ws) => {
|
|
|
48
48
|
if (msg.type !== "relay:auth")
|
|
49
49
|
return;
|
|
50
50
|
const { roomId, authToken, role } = msg;
|
|
51
|
+
// Clamp priority to 0–1: 0 = fallback (vault), 1 = primary (extension)
|
|
52
|
+
const priority = typeof msg.priority === "number" ? Math.max(0, Math.min(1, Math.floor(msg.priority))) : 1;
|
|
51
53
|
if (typeof roomId !== "string" ||
|
|
52
54
|
typeof authToken !== "string" ||
|
|
53
55
|
(role !== "sender" && role !== "recipient")) {
|
|
@@ -55,17 +57,18 @@ wss.on("connection", (ws) => {
|
|
|
55
57
|
send(ws, { type: "relay:auth:result", success: false, error: "invalid auth payload" });
|
|
56
58
|
return;
|
|
57
59
|
}
|
|
58
|
-
console.log(`[auth] attempt: ${role} for room ${roomId.slice(0, 8)}...`);
|
|
59
|
-
// Rate limit auth attempts per room
|
|
60
|
+
console.log(`[auth] attempt: ${role} (priority ${priority}) for room ${roomId.slice(0, 8)}...`);
|
|
61
|
+
// Rate limit auth attempts per room+role
|
|
60
62
|
const now = Date.now();
|
|
61
|
-
const
|
|
63
|
+
const rateLimitKey = `${roomId}:${role}`;
|
|
64
|
+
const attempts = (authAttempts.get(rateLimitKey) ?? []).filter((t) => now - t < AUTH_RATE_WINDOW);
|
|
62
65
|
if (attempts.length >= AUTH_RATE_LIMIT) {
|
|
63
|
-
console.log(`[auth] rejected: rate limited for room ${roomId.slice(0, 8)}...`);
|
|
66
|
+
console.log(`[auth] rejected: rate limited for ${role} in room ${roomId.slice(0, 8)}...`);
|
|
64
67
|
send(ws, { type: "relay:auth:result", success: false, error: "too many auth attempts" });
|
|
65
68
|
return;
|
|
66
69
|
}
|
|
67
70
|
attempts.push(now);
|
|
68
|
-
authAttempts.set(
|
|
71
|
+
authAttempts.set(rateLimitKey, attempts);
|
|
69
72
|
let room = rooms.get(roomId);
|
|
70
73
|
if (room) {
|
|
71
74
|
// Constant-time comparison — pad to equal length so the length
|
|
@@ -87,7 +90,7 @@ wss.on("connection", (ws) => {
|
|
|
87
90
|
rooms.delete(roomId);
|
|
88
91
|
console.log(`[auth] deleted stale room ${roomId.slice(0, 8)}... (token mismatch, idle ${Math.round(staleMs / 1000)}s, no active peers)`);
|
|
89
92
|
// Create fresh room with the new token
|
|
90
|
-
room = { authToken, lastActivity: Date.now() };
|
|
93
|
+
room = { authToken, senderPriority: 0, lastActivity: Date.now() };
|
|
91
94
|
rooms.set(roomId, room);
|
|
92
95
|
}
|
|
93
96
|
else {
|
|
@@ -97,16 +100,27 @@ wss.on("connection", (ws) => {
|
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
if (room[role] && room[role].readyState === WebSocket.OPEN) {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
// Sender priority takeover: higher priority kicks lower priority
|
|
104
|
+
if (role === "sender" && priority > room.senderPriority) {
|
|
105
|
+
console.log(`[auth] takeover: new sender (priority ${priority}) kicks existing (priority ${room.senderPriority}) in room ${roomId.slice(0, 8)}...`);
|
|
106
|
+
send(room.sender, { type: "relay:peer:status", online: false, kicked: true });
|
|
107
|
+
room.sender.close(4001, "replaced by higher-priority sender");
|
|
108
|
+
room.sender = undefined;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
console.log(`[auth] rejected: ${role} already connected in room ${roomId.slice(0, 8)}...`);
|
|
112
|
+
send(ws, { type: "relay:auth:result", success: false, error: `${role} already connected` });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
103
115
|
}
|
|
104
116
|
}
|
|
105
117
|
else {
|
|
106
|
-
room = { authToken, lastActivity: Date.now() };
|
|
118
|
+
room = { authToken, senderPriority: 0, lastActivity: Date.now() };
|
|
107
119
|
rooms.set(roomId, room);
|
|
108
120
|
}
|
|
109
121
|
room[role] = ws;
|
|
122
|
+
if (role === "sender")
|
|
123
|
+
room.senderPriority = priority;
|
|
110
124
|
touchRoom(room);
|
|
111
125
|
authedRoomId = roomId;
|
|
112
126
|
authedRole = role;
|
|
@@ -153,6 +167,8 @@ wss.on("connection", (ws) => {
|
|
|
153
167
|
if (!room)
|
|
154
168
|
return;
|
|
155
169
|
room[authedRole] = undefined;
|
|
170
|
+
if (authedRole === "sender")
|
|
171
|
+
room.senderPriority = 0;
|
|
156
172
|
const peer = authedRole === "sender" ? room.recipient : room.sender;
|
|
157
173
|
if (peer && peer.readyState === WebSocket.OPEN) {
|
|
158
174
|
send(peer, { type: "relay:peer:status", online: false });
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAChD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { timingSafeEqual } from "node:crypto";
|
|
|
5
5
|
|
|
6
6
|
interface Room {
|
|
7
7
|
sender?: WebSocket;
|
|
8
|
+
senderPriority: number;
|
|
8
9
|
recipient?: WebSocket;
|
|
9
10
|
authToken: string;
|
|
10
11
|
lastActivity: number;
|
|
@@ -15,7 +16,7 @@ const IDLE_TIMEOUT_MS = 5 * 60 * 1000;
|
|
|
15
16
|
|
|
16
17
|
const rooms = new Map<string, Room>();
|
|
17
18
|
const authAttempts = new Map<string, number[]>();
|
|
18
|
-
const AUTH_RATE_LIMIT =
|
|
19
|
+
const AUTH_RATE_LIMIT = 10;
|
|
19
20
|
const AUTH_RATE_WINDOW = 60_000;
|
|
20
21
|
|
|
21
22
|
function send(ws: WebSocket, data: unknown): void {
|
|
@@ -64,6 +65,8 @@ wss.on("connection", (ws) => {
|
|
|
64
65
|
if (msg.type !== "relay:auth") return;
|
|
65
66
|
|
|
66
67
|
const { roomId, authToken, role } = msg;
|
|
68
|
+
// Clamp priority to 0–1: 0 = fallback (vault), 1 = primary (extension)
|
|
69
|
+
const priority = typeof msg.priority === "number" ? Math.max(0, Math.min(1, Math.floor(msg.priority))) : 1;
|
|
67
70
|
if (
|
|
68
71
|
typeof roomId !== "string" ||
|
|
69
72
|
typeof authToken !== "string" ||
|
|
@@ -74,18 +77,19 @@ wss.on("connection", (ws) => {
|
|
|
74
77
|
return;
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
console.log(`[auth] attempt: ${role} for room ${roomId.slice(0, 8)}...`);
|
|
80
|
+
console.log(`[auth] attempt: ${role} (priority ${priority}) for room ${roomId.slice(0, 8)}...`);
|
|
78
81
|
|
|
79
|
-
// Rate limit auth attempts per room
|
|
82
|
+
// Rate limit auth attempts per room+role
|
|
80
83
|
const now = Date.now();
|
|
81
|
-
const
|
|
84
|
+
const rateLimitKey = `${roomId}:${role}`;
|
|
85
|
+
const attempts = (authAttempts.get(rateLimitKey) ?? []).filter((t) => now - t < AUTH_RATE_WINDOW);
|
|
82
86
|
if (attempts.length >= AUTH_RATE_LIMIT) {
|
|
83
|
-
console.log(`[auth] rejected: rate limited for room ${roomId.slice(0, 8)}...`);
|
|
87
|
+
console.log(`[auth] rejected: rate limited for ${role} in room ${roomId.slice(0, 8)}...`);
|
|
84
88
|
send(ws, { type: "relay:auth:result", success: false, error: "too many auth attempts" });
|
|
85
89
|
return;
|
|
86
90
|
}
|
|
87
91
|
attempts.push(now);
|
|
88
|
-
authAttempts.set(
|
|
92
|
+
authAttempts.set(rateLimitKey, attempts);
|
|
89
93
|
|
|
90
94
|
let room = rooms.get(roomId);
|
|
91
95
|
|
|
@@ -109,7 +113,7 @@ wss.on("connection", (ws) => {
|
|
|
109
113
|
rooms.delete(roomId);
|
|
110
114
|
console.log(`[auth] deleted stale room ${roomId.slice(0, 8)}... (token mismatch, idle ${Math.round(staleMs / 1000)}s, no active peers)`);
|
|
111
115
|
// Create fresh room with the new token
|
|
112
|
-
room = { authToken, lastActivity: Date.now() };
|
|
116
|
+
room = { authToken, senderPriority: 0, lastActivity: Date.now() };
|
|
113
117
|
rooms.set(roomId, room);
|
|
114
118
|
} else {
|
|
115
119
|
console.log(`[auth] rejected: token mismatch for room ${roomId.slice(0, 8)}...`);
|
|
@@ -118,16 +122,25 @@ wss.on("connection", (ws) => {
|
|
|
118
122
|
}
|
|
119
123
|
}
|
|
120
124
|
if (room[role] && room[role]!.readyState === WebSocket.OPEN) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
125
|
+
// Sender priority takeover: higher priority kicks lower priority
|
|
126
|
+
if (role === "sender" && priority > room.senderPriority) {
|
|
127
|
+
console.log(`[auth] takeover: new sender (priority ${priority}) kicks existing (priority ${room.senderPriority}) in room ${roomId.slice(0, 8)}...`);
|
|
128
|
+
send(room.sender!, { type: "relay:peer:status", online: false, kicked: true });
|
|
129
|
+
room.sender!.close(4001, "replaced by higher-priority sender");
|
|
130
|
+
room.sender = undefined;
|
|
131
|
+
} else {
|
|
132
|
+
console.log(`[auth] rejected: ${role} already connected in room ${roomId.slice(0, 8)}...`);
|
|
133
|
+
send(ws, { type: "relay:auth:result", success: false, error: `${role} already connected` });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
124
136
|
}
|
|
125
137
|
} else {
|
|
126
|
-
room = { authToken, lastActivity: Date.now() };
|
|
138
|
+
room = { authToken, senderPriority: 0, lastActivity: Date.now() };
|
|
127
139
|
rooms.set(roomId, room);
|
|
128
140
|
}
|
|
129
141
|
|
|
130
142
|
room[role] = ws;
|
|
143
|
+
if (role === "sender") room.senderPriority = priority;
|
|
131
144
|
touchRoom(room);
|
|
132
145
|
authedRoomId = roomId;
|
|
133
146
|
authedRole = role;
|
|
@@ -185,6 +198,7 @@ wss.on("connection", (ws) => {
|
|
|
185
198
|
if (!room) return;
|
|
186
199
|
|
|
187
200
|
room[authedRole] = undefined;
|
|
201
|
+
if (authedRole === "sender") room.senderPriority = 0;
|
|
188
202
|
|
|
189
203
|
const peer = authedRole === "sender" ? room.recipient : room.sender;
|
|
190
204
|
if (peer && peer.readyState === WebSocket.OPEN) {
|