@vex-chat/spire 1.3.4 → 1.3.5
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/avatar.js +9 -7
- package/dist/server/avatar.js.map +1 -1
- package/package.json +5 -5
- package/src/server/avatar.ts +10 -8
package/dist/server/avatar.js
CHANGED
|
@@ -7,7 +7,6 @@ import * as fs from "node:fs";
|
|
|
7
7
|
import * as fsp from "node:fs/promises";
|
|
8
8
|
import express from "express";
|
|
9
9
|
import { XUtils } from "@vex-chat/crypto";
|
|
10
|
-
import { FilePayloadSchema } from "@vex-chat/types";
|
|
11
10
|
import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
|
|
12
11
|
import multer from "multer";
|
|
13
12
|
import { z } from "zod/v4";
|
|
@@ -15,6 +14,13 @@ import { uploadLimiter } from "./rateLimit.js";
|
|
|
15
14
|
import { getParam, getUser } from "./utils.js";
|
|
16
15
|
import { ALLOWED_IMAGE_TYPES, protect } from "./index.js";
|
|
17
16
|
const safePathParam = z.string().regex(/^[a-zA-Z0-9._-]+$/);
|
|
17
|
+
// Avatars are public, unencrypted images. The body for the JSON path is just a
|
|
18
|
+
// base64-encoded image; nothing else. Do NOT reuse FilePayloadSchema here —
|
|
19
|
+
// that schema is for encrypted user-file uploads and requires nonce/owner/signed,
|
|
20
|
+
// which the avatar client never sends (and shouldn't).
|
|
21
|
+
const avatarJsonPayload = z.object({
|
|
22
|
+
file: z.string().min(1),
|
|
23
|
+
});
|
|
18
24
|
export const getAvatarRouter = () => {
|
|
19
25
|
const router = express.Router();
|
|
20
26
|
router.get("/:userID", async (req, res) => {
|
|
@@ -39,10 +45,10 @@ export const getAvatarRouter = () => {
|
|
|
39
45
|
stream.pipe(res);
|
|
40
46
|
});
|
|
41
47
|
router.post("/:userID/json", protect, async (req, res) => {
|
|
42
|
-
const parsed =
|
|
48
|
+
const parsed = avatarJsonPayload.safeParse(req.body);
|
|
43
49
|
if (!parsed.success) {
|
|
44
50
|
res.status(400).json({
|
|
45
|
-
error: "Invalid
|
|
51
|
+
error: "Invalid avatar payload",
|
|
46
52
|
issues: parsed.error.issues,
|
|
47
53
|
});
|
|
48
54
|
return;
|
|
@@ -54,10 +60,6 @@ export const getAvatarRouter = () => {
|
|
|
54
60
|
res.sendStatus(401);
|
|
55
61
|
return;
|
|
56
62
|
}
|
|
57
|
-
if (!payload.file) {
|
|
58
|
-
res.sendStatus(400);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
63
|
const buf = Buffer.from(XUtils.decodeBase64(payload.file));
|
|
62
64
|
const mimeType = await fileTypeFromBuffer(buf);
|
|
63
65
|
if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"avatar.js","sourceRoot":"","sources":["../../src/server/avatar.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,GAAG,MAAM,kBAAkB,CAAC;AAExC,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"avatar.js","sourceRoot":"","sources":["../../src/server/avatar.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vex-chat/spire",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.5",
|
|
4
4
|
"description": "Vex server implementation in NodeJS.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -55,8 +55,8 @@
|
|
|
55
55
|
"typescript": "6.0.2",
|
|
56
56
|
"typescript-eslint": "8.58.1",
|
|
57
57
|
"vitest": "4.1.4",
|
|
58
|
-
"@vex-chat/
|
|
59
|
-
"@vex-chat/
|
|
58
|
+
"@vex-chat/libvex": "^6.1.6",
|
|
59
|
+
"@vex-chat/eslint-config": "0.0.1"
|
|
60
60
|
},
|
|
61
61
|
"typeCoverage": {
|
|
62
62
|
"atLeast": 95,
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"uuid": "^14.0.0",
|
|
84
84
|
"ws": "8.20.0",
|
|
85
85
|
"zod": "^4.3.6",
|
|
86
|
-
"@vex-chat/
|
|
87
|
-
"@vex-chat/
|
|
86
|
+
"@vex-chat/crypto": "^4.0.1",
|
|
87
|
+
"@vex-chat/types": "^3.1.0"
|
|
88
88
|
},
|
|
89
89
|
"scripts": {
|
|
90
90
|
"rebuild:sqlite": "pnpm rebuild better-sqlite3",
|
package/src/server/avatar.ts
CHANGED
|
@@ -10,7 +10,6 @@ import * as fsp from "node:fs/promises";
|
|
|
10
10
|
import express from "express";
|
|
11
11
|
|
|
12
12
|
import { XUtils } from "@vex-chat/crypto";
|
|
13
|
-
import { FilePayloadSchema } from "@vex-chat/types";
|
|
14
13
|
|
|
15
14
|
import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
|
|
16
15
|
import multer from "multer";
|
|
@@ -23,6 +22,14 @@ import { ALLOWED_IMAGE_TYPES, protect } from "./index.ts";
|
|
|
23
22
|
|
|
24
23
|
const safePathParam = z.string().regex(/^[a-zA-Z0-9._-]+$/);
|
|
25
24
|
|
|
25
|
+
// Avatars are public, unencrypted images. The body for the JSON path is just a
|
|
26
|
+
// base64-encoded image; nothing else. Do NOT reuse FilePayloadSchema here —
|
|
27
|
+
// that schema is for encrypted user-file uploads and requires nonce/owner/signed,
|
|
28
|
+
// which the avatar client never sends (and shouldn't).
|
|
29
|
+
const avatarJsonPayload = z.object({
|
|
30
|
+
file: z.string().min(1),
|
|
31
|
+
});
|
|
32
|
+
|
|
26
33
|
export const getAvatarRouter = () => {
|
|
27
34
|
const router = express.Router();
|
|
28
35
|
|
|
@@ -50,10 +57,10 @@ export const getAvatarRouter = () => {
|
|
|
50
57
|
});
|
|
51
58
|
|
|
52
59
|
router.post("/:userID/json", protect, async (req, res) => {
|
|
53
|
-
const parsed =
|
|
60
|
+
const parsed = avatarJsonPayload.safeParse(req.body);
|
|
54
61
|
if (!parsed.success) {
|
|
55
62
|
res.status(400).json({
|
|
56
|
-
error: "Invalid
|
|
63
|
+
error: "Invalid avatar payload",
|
|
57
64
|
issues: parsed.error.issues,
|
|
58
65
|
});
|
|
59
66
|
return;
|
|
@@ -67,11 +74,6 @@ export const getAvatarRouter = () => {
|
|
|
67
74
|
return;
|
|
68
75
|
}
|
|
69
76
|
|
|
70
|
-
if (!payload.file) {
|
|
71
|
-
res.sendStatus(400);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
77
|
const buf = Buffer.from(XUtils.decodeBase64(payload.file));
|
|
76
78
|
const mimeType = await fileTypeFromBuffer(buf);
|
|
77
79
|
if (!ALLOWED_IMAGE_TYPES.includes(mimeType?.mime || "no/type")) {
|