@vex-chat/spire 1.10.0 → 1.10.2
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/ClientManager.d.ts +3 -0
- package/dist/ClientManager.js +15 -0
- package/dist/ClientManager.js.map +1 -1
- package/dist/Database.js +3 -0
- package/dist/Database.js.map +1 -1
- package/dist/Spire.d.ts +1 -0
- package/dist/Spire.js +40 -24
- package/dist/Spire.js.map +1 -1
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +27 -14
- package/dist/server/index.js.map +1 -1
- package/dist/server/rateLimit.d.ts +3 -3
- package/dist/server/rateLimit.js +4 -4
- package/dist/server/rateLimit.js.map +1 -1
- package/package.json +3 -3
- package/src/ClientManager.ts +18 -0
- package/src/Database.ts +3 -0
- package/src/Spire.ts +43 -23
- package/src/__tests__/Database.spec.ts +77 -1
- package/src/__tests__/deviceTokenRevalidation.spec.ts +104 -0
- package/src/__tests__/notifyFanout.spec.ts +98 -0
- package/src/server/index.ts +38 -14
- package/src/server/rateLimit.ts +4 -4
package/src/server/index.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import type { Database } from "../Database.ts";
|
|
8
|
-
import type { Emoji } from "@vex-chat/types";
|
|
8
|
+
import type { Device, Emoji } from "@vex-chat/types";
|
|
9
9
|
|
|
10
10
|
import * as fs from "node:fs";
|
|
11
11
|
import * as fsp from "node:fs/promises";
|
|
@@ -147,21 +147,45 @@ const checkAuth: express.RequestHandler = (req, _res, next) => {
|
|
|
147
147
|
next();
|
|
148
148
|
};
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
150
|
+
export function createCheckDevice(
|
|
151
|
+
db: Pick<Database, "retrieveDevice">,
|
|
152
|
+
): express.RequestHandler {
|
|
153
|
+
return async (req, _res, next) => {
|
|
154
|
+
const token = req.headers["x-device-token"];
|
|
155
|
+
if (typeof token === "string" && token) {
|
|
156
|
+
try {
|
|
157
|
+
const result = jwt.verify(token, getJwtSecret());
|
|
158
|
+
const parsed = jwtDevicePayload.safeParse(result);
|
|
159
|
+
if (parsed.success) {
|
|
160
|
+
const device = await currentTokenDevice(
|
|
161
|
+
db,
|
|
162
|
+
parsed.data.device,
|
|
163
|
+
);
|
|
164
|
+
if (
|
|
165
|
+
device &&
|
|
166
|
+
(!req.user || device.owner === req.user.userID)
|
|
167
|
+
) {
|
|
168
|
+
req.device = device;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
} catch {
|
|
172
|
+
// Device token verification/revalidation failed — continue without device.
|
|
158
173
|
}
|
|
159
|
-
} catch {
|
|
160
|
-
// Device token verification failed — continue without device
|
|
161
174
|
}
|
|
175
|
+
next();
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function currentTokenDevice(
|
|
180
|
+
db: Pick<Database, "retrieveDevice">,
|
|
181
|
+
tokenDevice: Device,
|
|
182
|
+
): Promise<Device | null> {
|
|
183
|
+
const device = await db.retrieveDevice(tokenDevice.deviceID);
|
|
184
|
+
if (!device || device.signKey !== tokenDevice.signKey) {
|
|
185
|
+
return null;
|
|
162
186
|
}
|
|
163
|
-
|
|
164
|
-
}
|
|
187
|
+
return device;
|
|
188
|
+
}
|
|
165
189
|
|
|
166
190
|
export const protect: express.RequestHandler = (req, res, next) => {
|
|
167
191
|
if (!req.user) {
|
|
@@ -282,7 +306,7 @@ export const initApp = (
|
|
|
282
306
|
api.use(helmet());
|
|
283
307
|
api.use(msgpackParser);
|
|
284
308
|
api.use(checkAuth);
|
|
285
|
-
api.use(
|
|
309
|
+
api.use(createCheckDevice(db));
|
|
286
310
|
|
|
287
311
|
api.get("/server/:id", protect, async (req, res) => {
|
|
288
312
|
const server = await db.retrieveServer(getParam(req, "id"));
|
package/src/server/rateLimit.ts
CHANGED
|
@@ -83,14 +83,14 @@ const keyByIp = (req: Request): string => ipKeyGenerator(req.ip ?? "");
|
|
|
83
83
|
/**
|
|
84
84
|
* Global per-IP limiter. Applied app-wide via `api.use(globalLimiter)`.
|
|
85
85
|
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
86
|
+
* 150,000 requests per 15 minutes per client IP. This leaves room for
|
|
87
|
+
* high-throughput clients and algorithmic applications while still
|
|
88
|
+
* putting a ceiling on runaway single-host traffic.
|
|
89
89
|
*/
|
|
90
90
|
export const globalLimiter = rateLimit({
|
|
91
91
|
keyGenerator: keyByIp,
|
|
92
92
|
legacyHeaders: false,
|
|
93
|
-
limit:
|
|
93
|
+
limit: 150_000,
|
|
94
94
|
skip: devApiKeySkipsRateLimits,
|
|
95
95
|
standardHeaders: "draft-7",
|
|
96
96
|
windowMs: 15 * 60 * 1000,
|