@vex-chat/spire 3.0.1 → 4.1.0

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.
Files changed (90) hide show
  1. package/README.md +9 -8
  2. package/dist/Database.d.ts +20 -11
  3. package/dist/Database.js +229 -118
  4. package/dist/Database.js.map +1 -1
  5. package/dist/Spire.d.ts +4 -2
  6. package/dist/Spire.js +155 -72
  7. package/dist/Spire.js.map +1 -1
  8. package/dist/db/schema.d.ts +0 -2
  9. package/dist/migrations/2026-04-06_initial-schema.js +4 -5
  10. package/dist/migrations/2026-04-06_initial-schema.js.map +1 -1
  11. package/dist/migrations/{2026-04-14_argon2id-password-hashing.d.ts → 2026-07-14_query-indexes.d.ts} +1 -1
  12. package/dist/migrations/2026-07-14_query-indexes.js +31 -0
  13. package/dist/migrations/2026-07-14_query-indexes.js.map +1 -0
  14. package/dist/server/avatar.js +33 -6
  15. package/dist/server/avatar.js.map +1 -1
  16. package/dist/server/cliPasskeyPage.js +109 -11
  17. package/dist/server/cliPasskeyPage.js.map +1 -1
  18. package/dist/server/errors.js +8 -0
  19. package/dist/server/errors.js.map +1 -1
  20. package/dist/server/file.js +35 -12
  21. package/dist/server/file.js.map +1 -1
  22. package/dist/server/index.d.ts +8 -0
  23. package/dist/server/index.js +319 -56
  24. package/dist/server/index.js.map +1 -1
  25. package/dist/server/passkey.js +367 -29
  26. package/dist/server/passkey.js.map +1 -1
  27. package/dist/server/passkeyDevices.js +1 -0
  28. package/dist/server/passkeyDevices.js.map +1 -1
  29. package/dist/server/password.d.ts +7 -0
  30. package/dist/server/password.js +58 -0
  31. package/dist/server/password.js.map +1 -0
  32. package/dist/server/permissions.d.ts +1 -0
  33. package/dist/server/permissions.js +11 -2
  34. package/dist/server/permissions.js.map +1 -1
  35. package/dist/server/rateLimit.d.ts +11 -0
  36. package/dist/server/rateLimit.js +43 -4
  37. package/dist/server/rateLimit.js.map +1 -1
  38. package/dist/server/serverIcon.d.ts +10 -0
  39. package/dist/server/serverIcon.js +158 -0
  40. package/dist/server/serverIcon.js.map +1 -0
  41. package/dist/server/user.d.ts +1 -1
  42. package/dist/server/user.js +40 -9
  43. package/dist/server/user.js.map +1 -1
  44. package/dist/types/express.d.ts +3 -4
  45. package/dist/types/express.js.map +1 -1
  46. package/dist/utils/authJwt.d.ts +8 -0
  47. package/dist/utils/authJwt.js +25 -0
  48. package/dist/utils/authJwt.js.map +1 -0
  49. package/dist/utils/loadEnv.js +4 -0
  50. package/dist/utils/loadEnv.js.map +1 -1
  51. package/dist/utils/preKeySignature.d.ts +1 -1
  52. package/dist/utils/preKeySignature.js +7 -11
  53. package/dist/utils/preKeySignature.js.map +1 -1
  54. package/package.json +7 -7
  55. package/src/Database.ts +294 -152
  56. package/src/Spire.ts +412 -285
  57. package/src/__tests__/Database.spec.ts +126 -3
  58. package/src/__tests__/browserPasskeyAuthentication.spec.ts +192 -0
  59. package/src/__tests__/browserPasskeyRegistration.spec.ts +182 -0
  60. package/src/__tests__/cliPasskeyPage.spec.ts +6 -0
  61. package/src/__tests__/connectAuth.spec.ts +146 -4
  62. package/src/__tests__/deviceTokenRevalidation.spec.ts +57 -3
  63. package/src/__tests__/passkeyDevices.spec.ts +94 -0
  64. package/src/__tests__/passkeys.spec.ts +7 -2
  65. package/src/__tests__/password.spec.ts +223 -0
  66. package/src/__tests__/permissions.spec.ts +50 -0
  67. package/src/__tests__/preKeySignature.spec.ts +20 -3
  68. package/src/__tests__/serverManagement.spec.ts +262 -0
  69. package/src/db/schema.ts +0 -2
  70. package/src/migrations/2026-04-06_initial-schema.ts +4 -5
  71. package/src/migrations/2026-07-14_query-indexes.ts +34 -0
  72. package/src/server/avatar.ts +32 -6
  73. package/src/server/cliPasskeyPage.ts +109 -11
  74. package/src/server/errors.ts +7 -0
  75. package/src/server/file.ts +34 -13
  76. package/src/server/index.ts +494 -139
  77. package/src/server/passkey.ts +531 -31
  78. package/src/server/passkeyDevices.ts +1 -0
  79. package/src/server/password.ts +96 -0
  80. package/src/server/permissions.ts +20 -2
  81. package/src/server/rateLimit.ts +47 -4
  82. package/src/server/serverIcon.ts +199 -0
  83. package/src/server/user.ts +44 -9
  84. package/src/types/express.ts +3 -4
  85. package/src/utils/authJwt.ts +34 -0
  86. package/src/utils/loadEnv.ts +4 -0
  87. package/src/utils/preKeySignature.ts +12 -15
  88. package/dist/migrations/2026-04-14_argon2id-password-hashing.js +0 -17
  89. package/dist/migrations/2026-04-14_argon2id-password-hashing.js.map +0 -1
  90. package/src/migrations/2026-04-14_argon2id-password-hashing.ts +0 -24
@@ -14,7 +14,7 @@ import * as path from "node:path";
14
14
  import express from "express";
15
15
 
16
16
  import { XUtils } from "@vex-chat/crypto";
17
- import { FilePayloadSchema } from "@vex-chat/types";
17
+ import { FilePayloadSchema, MAX_FILE_UPLOAD_BYTES } from "@vex-chat/types";
18
18
 
19
19
  import multer from "multer";
20
20
  import { z } from "zod/v4";
@@ -27,6 +27,14 @@ import { getParam } from "./utils.ts";
27
27
  import { protect } from "./index.ts";
28
28
 
29
29
  const safePathParam = z.string().regex(/^[a-zA-Z0-9._-]+$/);
30
+ const fileUpload = multer({
31
+ limits: {
32
+ fields: 4,
33
+ files: 1,
34
+ fileSize: MAX_FILE_UPLOAD_BYTES,
35
+ parts: 5,
36
+ },
37
+ });
30
38
 
31
39
  export const getFileRouter = (db: Database) => {
32
40
  const router = express.Router();
@@ -77,7 +85,7 @@ export const getFileRouter = (db: Database) => {
77
85
  }
78
86
  });
79
87
 
80
- router.post("/json", protect, async (req, res) => {
88
+ router.post("/json", uploadLimiter, protect, async (req, res) => {
81
89
  const deviceDetails = req.device;
82
90
 
83
91
  if (!deviceDetails) {
@@ -95,17 +103,28 @@ export const getFileRouter = (db: Database) => {
95
103
  }
96
104
  const payload = parsed.data;
97
105
 
98
- if (payload.nonce === "") {
106
+ if (!payload.file) {
99
107
  res.sendStatus(400);
100
108
  return;
101
109
  }
102
-
103
- if (!payload.file) {
104
- res.sendStatus(400);
110
+ if (payload.owner !== deviceDetails.deviceID) {
111
+ res.status(400).send({
112
+ error: "File owner must match this device.",
113
+ });
105
114
  return;
106
115
  }
107
116
 
108
- const buf = Buffer.from(XUtils.decodeBase64(payload.file));
117
+ let buf: Buffer;
118
+ try {
119
+ buf = Buffer.from(XUtils.decodeBase64(payload.file));
120
+ } catch {
121
+ res.status(400).send({ error: "File must be valid base64." });
122
+ return;
123
+ }
124
+ if (buf.byteLength > MAX_FILE_UPLOAD_BYTES) {
125
+ res.sendStatus(413);
126
+ return;
127
+ }
109
128
 
110
129
  const newFile: FileSQL = {
111
130
  fileID: crypto.randomUUID(),
@@ -120,20 +139,20 @@ export const getFileRouter = (db: Database) => {
120
139
 
121
140
  // Multipart file upload — form fields are strings from multer, not full FilePayload
122
141
  const multipartFields = z.object({
123
- nonce: z.string().min(1),
124
- owner: z.string().min(1),
142
+ nonce: z.string().regex(/^[0-9a-fA-F]{48}$/),
143
+ owner: z.string().min(1).max(128),
125
144
  });
126
145
 
127
146
  router.post(
128
147
  "/",
129
148
  uploadLimiter,
130
149
  protect,
131
- multer().single("file"),
150
+ fileUpload.single("file"),
132
151
  async (req, res) => {
133
152
  const deviceDetails = req.device;
134
153
 
135
154
  if (!deviceDetails) {
136
- res.sendStatus(400);
155
+ res.sendStatus(401);
137
156
  return;
138
157
  }
139
158
 
@@ -152,8 +171,10 @@ export const getFileRouter = (db: Database) => {
152
171
  return;
153
172
  }
154
173
 
155
- if (payload.nonce === "") {
156
- res.sendStatus(400);
174
+ if (payload.owner !== deviceDetails.deviceID) {
175
+ res.status(400).send({
176
+ error: "File owner must match this device.",
177
+ });
157
178
  return;
158
179
  }
159
180