@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.
@@ -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
- const checkDevice: express.RequestHandler = (req, _res, next) => {
151
- const token = req.headers["x-device-token"];
152
- if (typeof token === "string" && token) {
153
- try {
154
- const result = jwt.verify(token, getJwtSecret());
155
- const parsed = jwtDevicePayload.safeParse(result);
156
- if (parsed.success) {
157
- req.device = parsed.data.device;
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
- next();
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(checkDevice);
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"));
@@ -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
- * 3000 requests per 15 minutes per client IP. A human chatting via a
87
- * browser or the libvex client won't come close; a single-host DoS
88
- * gets throttled quickly.
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: 3000,
93
+ limit: 150_000,
94
94
  skip: devApiKeySkipsRateLimits,
95
95
  standardHeaders: "draft-7",
96
96
  windowMs: 15 * 60 * 1000,