@vex-chat/spire 1.0.3 → 1.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.
- package/README.md +44 -36
- package/dist/ClientManager.js +2 -2
- package/dist/ClientManager.js.map +1 -1
- package/dist/Database.d.ts +14 -16
- package/dist/Database.js +73 -101
- package/dist/Database.js.map +1 -1
- package/dist/Spire.d.ts +13 -1
- package/dist/Spire.js +89 -10
- package/dist/Spire.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/run.js +24 -6
- package/dist/run.js.map +1 -1
- package/dist/server/index.js +20 -13
- package/dist/server/index.js.map +1 -1
- package/dist/server/user.d.ts +1 -1
- package/dist/server/user.js +238 -11
- package/dist/server/user.js.map +1 -1
- package/dist/spireListenPort.d.ts +14 -0
- package/dist/spireListenPort.js +22 -0
- package/dist/spireListenPort.js.map +1 -0
- package/dist/utils/spireXSignOpenAsync.d.ts +9 -0
- package/dist/utils/spireXSignOpenAsync.js +16 -0
- package/dist/utils/spireXSignOpenAsync.js.map +1 -0
- package/package.json +110 -119
- package/src/ClientManager.ts +2 -2
- package/src/Database.ts +84 -116
- package/src/Spire.ts +116 -13
- package/src/__tests__/Database.spec.ts +2 -1
- package/src/__tests__/spireListenPort.spec.ts +22 -0
- package/src/index.ts +4 -0
- package/src/run.ts +28 -6
- package/src/server/index.ts +25 -14
- package/src/server/user.ts +313 -10
- package/src/spireListenPort.ts +25 -0
- package/src/utils/spireXSignOpenAsync.ts +20 -0
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# @vex-chat/spire
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@vex-chat/spire)
|
|
4
|
-
[](https://github.com/vex-protocol/protocol/actions/workflows/build.yml)
|
|
5
|
+
[](https://github.com/vex-protocol/protocol/releases)
|
|
6
6
|
[](./LICENSE)
|
|
7
|
-
[](https://github.com/plantain-00/type-coverage)
|
|
8
8
|
[](./package.json)
|
|
9
|
-
[](https://securityscorecards.dev/viewer/?uri=github.com/vex-protocol/protocol)
|
|
10
10
|
[](https://socket.dev/npm/package/@vex-chat/spire)
|
|
11
11
|
|
|
12
12
|
Reference server implementation for the [Vex](https://vex.wtf) protocol.
|
|
@@ -17,32 +17,37 @@ Reference server implementation for the [Vex](https://vex.wtf) protocol.
|
|
|
17
17
|
|
|
18
18
|
## Install
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Spire is part of the [vex-protocol/protocol](https://github.com/vex-protocol/protocol) monorepo. Clone the monorepo and install workspace deps:
|
|
21
21
|
|
|
22
22
|
```sh
|
|
23
|
-
git clone git@github.com:vex-protocol/
|
|
24
|
-
cd
|
|
25
|
-
|
|
23
|
+
git clone git@github.com:vex-protocol/protocol
|
|
24
|
+
cd protocol
|
|
25
|
+
pnpm install
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
+
Spire's source lives at `apps/spire/`. Most commands below run from the monorepo root.
|
|
29
|
+
|
|
28
30
|
## Running the server (Docker)
|
|
29
31
|
|
|
30
|
-
|
|
32
|
+
The Dockerfile uses the monorepo as build context so the build can resolve `workspace:^` deps for `@vex-chat/{types,crypto,libvex}`. From `apps/spire/`, with Docker and Docker Compose installed:
|
|
31
33
|
|
|
32
34
|
```sh
|
|
33
35
|
cp .env.example .env
|
|
34
|
-
# set SPK, JWT_SECRET, DB_TYPE, … (see Configuration)
|
|
36
|
+
# set SPK, JWT_SECRET, DB_TYPE, SPIRE_FIPS, … (see Configuration)
|
|
35
37
|
docker compose up --build
|
|
36
38
|
```
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
**Crypto mode (tweetnacl vs FIPS):** `SPIRE_FIPS` in `.env` selects the server profile. It must match how you generated `SPK` — **`pnpm --filter @vex-chat/spire gen-spk`** (Ed25519) for tweetnacl, or **`pnpm --filter @vex-chat/spire gen-spk-fips`** (P-256) with **`SPIRE_FIPS=true`**. You can override for one run without editing `.env`: `SPIRE_FIPS=true docker compose up` (or `=false`).
|
|
41
|
+
|
|
42
|
+
Compose builds the image (context: monorepo root, dockerfile: `apps/spire/Dockerfile`), starts Spire with a persistent **`spire-data`** volume mounted at `/data` (SQLite + `files/`, `avatars/`, `emoji/`), and fronts it with **nginx** on host **port 16777** (see `ports` in `docker-compose.yml`). Spire itself listens on **16777** inside the `internal` network (same for tweetnacl and FIPS — `GET /status` reports the crypto profile). Nginx and the health check use `deploy/resolve-spire-listen-port.sh` to match. Use **http://127.0.0.1:16777** for HTTP and WebSocket.
|
|
39
43
|
|
|
40
44
|
## Running without Docker
|
|
41
45
|
|
|
42
46
|
For local development or if you installed from npm, Spire runs with `node --experimental-strip-types` (no separate compile step):
|
|
43
47
|
|
|
44
48
|
```sh
|
|
45
|
-
|
|
49
|
+
pnpm --filter @vex-chat/spire start
|
|
50
|
+
# or, from apps/spire/: pnpm start
|
|
46
51
|
# or: node --experimental-strip-types src/run.ts
|
|
47
52
|
```
|
|
48
53
|
|
|
@@ -58,50 +63,53 @@ Spire reads configuration from environment variables. **Docker Compose:** put th
|
|
|
58
63
|
|
|
59
64
|
### Required
|
|
60
65
|
|
|
61
|
-
| Variable | Description
|
|
62
|
-
| ------------ |
|
|
63
|
-
| `SPK` | Server private key, hex-encoded.
|
|
64
|
-
| `JWT_SECRET` | Hex or string used as the **HMAC secret for JWTs** — **required** and must be **separate from `SPK`**. `
|
|
65
|
-
| `DB_TYPE` | `sqlite3` or `sqlite3mem`. All values use **SQLite** via `better-sqlite3` (file or `:memory:`).
|
|
66
|
+
| Variable | Description |
|
|
67
|
+
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| `SPK` | Server private key, hex-encoded. **tweetnacl:** `pnpm --filter @vex-chat/spire gen-spk` (Ed25519). **FIPS:** `pnpm --filter @vex-chat/spire gen-spk-fips` and set `SPIRE_FIPS=true` (P-256 PKCS#8). Each command prints `SPK` and `JWT_SECRET` lines. |
|
|
69
|
+
| `JWT_SECRET` | Hex or string used as the **HMAC secret for JWTs** — **required** and must be **separate from `SPK`**. `pnpm --filter @vex-chat/spire gen-spk` emits a dedicated value; do not reuse `SPK` here. |
|
|
70
|
+
| `DB_TYPE` | `sqlite3` or `sqlite3mem`. All values use **SQLite** via `better-sqlite3` (file or `:memory:`). |
|
|
66
71
|
|
|
67
72
|
### Optional
|
|
68
73
|
|
|
69
|
-
| Variable | Default
|
|
70
|
-
| -------------- |
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
75
|
-
| `
|
|
74
|
+
| Variable | Default | Description |
|
|
75
|
+
| -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
76
|
+
| `SPIRE_FIPS` | _falsy_ | If `true` or `1`, run the **FIPS** profile (P-256, Web Crypto). `SPK` must come from `pnpm --filter @vex-chat/spire gen-spk-fips`, not `gen-spk`. Any other value uses **tweetnacl** (Ed25519) and `gen-spk`. In Docker Compose, the service passes `SPIRE_FIPS=${SPIRE_FIPS:-false}` so the shell or `.env` can set the mode. |
|
|
77
|
+
| `API_PORT` | (see text) | If unset, Spire listens on **16777** (all crypto profiles; use `GET /status` to see which). In Docker, nginx and the image healthcheck use `deploy/resolve-spire-listen-port.sh` to follow the same rule. Set explicitly to override. |
|
|
78
|
+
| `NODE_ENV` | _(unset)_ | Set to `production` to disable interactive `/docs` / `/async-docs`. If unset or any other value, doc viewers are mounted. `helmet()` runs in all modes. |
|
|
79
|
+
| `CORS_ORIGINS` | _(empty)_ | Comma-separated allowed `Origin` values. If set, only those origins may use credentialed browser requests. If unset, Spire **reflects the request `Origin`** so self-hosted Spire and arbitrary app origins (Tauri, localhost, etc.) work without configuration — appropriate for bearer-token APIs; set an allowlist if you need to restrict which sites may call your instance from users' browsers. |
|
|
80
|
+
| `DEV_API_KEY` | _(empty)_ | When set, requests that send header `x-dev-api-key` with the same value (constant-time compare) **skip in-process rate limiters**. The same gate enables **`GET /status/process`** (404 without a valid key): a small JSON snapshot of the Spire Node process (PID, uptime, `memoryUsage`, cumulative `resourceUsage`, WebSocket client count). Dev / load-testing only — never set in production. |
|
|
81
|
+
| `CANARY` | _(unset)_ | |
|
|
76
82
|
|
|
77
83
|
### Sample `.env`
|
|
78
84
|
|
|
79
85
|
```sh
|
|
80
|
-
# Run `
|
|
86
|
+
# Run `pnpm --filter @vex-chat/spire gen-spk` and paste the two lines it prints (SPK + JWT_SECRET).
|
|
81
87
|
SPK=a1b2c3...
|
|
82
88
|
JWT_SECRET=d4e5f6...
|
|
83
89
|
DB_TYPE=sqlite
|
|
84
90
|
# CANARY=true
|
|
85
|
-
API_PORT=16777
|
|
91
|
+
# API_PORT= # unset = 16777 unless you override
|
|
86
92
|
NODE_ENV=production
|
|
87
93
|
```
|
|
88
94
|
|
|
89
95
|
## Development
|
|
90
96
|
|
|
97
|
+
From the monorepo root (or use `pnpm <script>` from `apps/spire/`):
|
|
98
|
+
|
|
91
99
|
```sh
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
npm test # vitest run
|
|
98
|
-
npx type-coverage # type-coverage (≥95%)
|
|
100
|
+
pnpm install # install workspace deps
|
|
101
|
+
pnpm --filter @vex-chat/spire build # tsc (sanity check — runtime uses --experimental-strip-types)
|
|
102
|
+
pnpm --filter @vex-chat/spire lint # eslint strictTypeChecked
|
|
103
|
+
pnpm --filter @vex-chat/spire lint:fix # eslint --fix
|
|
104
|
+
pnpm --filter @vex-chat/spire test # vitest run
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
Workspace-wide commands from root: `pnpm test`, `pnpm lint`, `pnpm format:check`, `pnpm -r build`.
|
|
108
|
+
|
|
109
|
+
See the root [AGENTS.md](../../AGENTS.md) and this package's [AGENTS.md](./AGENTS.md) for the release flow (changesets → publish via OIDC) and the rules for writing changesets.
|
|
102
110
|
|
|
103
|
-
|
|
111
|
+
Outside contributors should follow the root [CONTRIBUTING.md](../../CONTRIBUTING.md) (including the [CLA](../../CLA.md)).
|
|
104
112
|
|
|
105
113
|
## License
|
|
106
114
|
|
|
107
|
-
Open source default: **[AGPL-3.0](./LICENSE)** (full text; see `package.json` for SPDX). Commercial licenses from **Vex Heavy Industries LLC**: [**LICENSE-COMMERCIAL**](./LICENSE-COMMERCIAL), [**LICENSING.md**](./LICENSING.md), [vex.wtf](https://vex.wtf).
|
|
115
|
+
Open source default: **[AGPL-3.0](./LICENSE)** (full text; see `package.json` for SPDX). Commercial licenses from **Vex Heavy Industries LLC**: [**LICENSE-COMMERCIAL**](./LICENSE-COMMERCIAL), [**LICENSING.md**](./LICENSING.md), [vex.wtf](https://vex.wtf/licensing).
|
package/dist/ClientManager.js
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
import { EventEmitter } from "events";
|
|
7
7
|
import { setTimeout as sleep } from "node:timers/promises";
|
|
8
8
|
import { xConcat, XUtils } from "@vex-chat/crypto";
|
|
9
|
-
import { xSignOpen } from "@vex-chat/crypto";
|
|
10
9
|
import { MailWSSchema, SocketAuthErrors } from "@vex-chat/types";
|
|
11
10
|
import { parse as uuidParse, validate as uuidValidate } from "uuid";
|
|
12
11
|
import { TOKEN_EXPIRY } from "./Spire.js";
|
|
13
12
|
import { createUint8UUID } from "./utils/createUint8UUID.js";
|
|
14
13
|
import { msgpack } from "./utils/msgpack.js";
|
|
14
|
+
import { spireXSignOpenAsync } from "./utils/spireXSignOpenAsync.js";
|
|
15
15
|
export const POWER_LEVELS = {
|
|
16
16
|
CREATE: 50,
|
|
17
17
|
DELETE: 50,
|
|
@@ -247,7 +247,7 @@ export class ClientManager extends EventEmitter {
|
|
|
247
247
|
const devices = await this.db.retrieveUserDeviceList([user.userID]);
|
|
248
248
|
let message = null;
|
|
249
249
|
for (const device of devices) {
|
|
250
|
-
const verified =
|
|
250
|
+
const verified = await spireXSignOpenAsync(msg.signed, XUtils.decodeHex(device.signKey));
|
|
251
251
|
if (verified) {
|
|
252
252
|
message = verified;
|
|
253
253
|
this.device = device;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../src/ClientManager.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAiBH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,UAAU,IAAI,KAAK,EAAE,MAAM,sBAAsB,CAAC;AAE3D,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AACnD,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"ClientManager.js","sourceRoot":"","sources":["../src/ClientManager.ts"],"names":[],"mappings":"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"}
|
package/dist/Database.d.ts
CHANGED
|
@@ -11,16 +11,8 @@ export interface InternalUserRecord extends UserRecord {
|
|
|
11
11
|
hashAlgo: string;
|
|
12
12
|
}
|
|
13
13
|
export declare class Database extends EventEmitter {
|
|
14
|
+
private static readonly EMOJI_LIST_CACHE_TTL_MS;
|
|
14
15
|
private db;
|
|
15
|
-
/** Underlying better-sqlite3 handle (file or :memory:). */
|
|
16
|
-
private readonly rawSqlite;
|
|
17
|
-
/**
|
|
18
|
-
* In-process cache for {@link retrieveUserDeviceList} when queried for a
|
|
19
|
-
* single owner. Libvex calls GET /user/:id/devices on every outgoing
|
|
20
|
-
* `forward()`; stress runs otherwise hammer SQLite with identical reads.
|
|
21
|
-
* Cleared on device create/delete and when the DB is closed.
|
|
22
|
-
*/
|
|
23
|
-
private readonly userDeviceListCache;
|
|
24
16
|
/**
|
|
25
17
|
* Short TTL cache for {@link retrieveEmojiList} (key = server id, stored as
|
|
26
18
|
* `emojis.owner`). The noise harness fires `emoji.retrieveList` as a
|
|
@@ -28,14 +20,9 @@ export declare class Database extends EventEmitter {
|
|
|
28
20
|
* before heavier surfaces. Invalidated on emoji create/delete.
|
|
29
21
|
*/
|
|
30
22
|
private readonly emojiListByServerCache;
|
|
31
|
-
|
|
32
|
-
private
|
|
23
|
+
/** Underlying better-sqlite3 handle (file or :memory:). */
|
|
24
|
+
private readonly rawSqlite;
|
|
33
25
|
constructor(options?: SpireOptions);
|
|
34
|
-
/**
|
|
35
|
-
* Dev-only snapshot of SQLite files + pragmas (same process as Spire).
|
|
36
|
-
* Not for production callers.
|
|
37
|
-
*/
|
|
38
|
-
getDevSqliteMonitor(): Record<string, unknown>;
|
|
39
26
|
close(): Promise<void>;
|
|
40
27
|
createChannel(name: string, serverID: string): Promise<Channel>;
|
|
41
28
|
createDevice(owner: string, payload: DevicePayload): Promise<Device>;
|
|
@@ -53,6 +40,11 @@ export declare class Database extends EventEmitter {
|
|
|
53
40
|
deletePermission(permissionID: string): Promise<void>;
|
|
54
41
|
deletePermissions(resourceID: string): Promise<void>;
|
|
55
42
|
deleteServer(serverID: string): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Dev-only snapshot of SQLite files + pragmas (same process as Spire).
|
|
45
|
+
* Not for production callers.
|
|
46
|
+
*/
|
|
47
|
+
getDevSqliteMonitor(): Record<string, unknown>;
|
|
56
48
|
getKeyBundle(deviceID: string): Promise<KeyBundle | null>;
|
|
57
49
|
getOTK(deviceID: string): Promise<null | PreKeysWS>;
|
|
58
50
|
getOTKCount(deviceID: string): Promise<number>;
|
|
@@ -86,6 +78,12 @@ export declare class Database extends EventEmitter {
|
|
|
86
78
|
retrieveServerInvites(serverID: string): Promise<Invite[]>;
|
|
87
79
|
retrieveServers(userID: string): Promise<Server[]>;
|
|
88
80
|
retrieveUser(userIdentifier: string): Promise<InternalUserRecord | null>;
|
|
81
|
+
/**
|
|
82
|
+
* All devices for the given user ID(s), **no in-process cache** — a prior
|
|
83
|
+
* 10s TTL was removed: concurrent GET + POST could end with a stale
|
|
84
|
+
* snapshot overwriting the map after a new device was inserted (automation
|
|
85
|
+
* and `POST /user/:id/devices` must see a fresh list on the next read).
|
|
86
|
+
*/
|
|
89
87
|
retrieveUserDeviceList(userIDs: string[]): Promise<Device[]>;
|
|
90
88
|
retrieveUsers(): Promise<InternalUserRecord[]>;
|
|
91
89
|
saveMail(mail: MailWS, header: Uint8Array, deviceID: string, userID: string): Promise<void>;
|
package/dist/Database.js
CHANGED
|
@@ -17,7 +17,7 @@ import { statSync } from "node:fs";
|
|
|
17
17
|
import * as fs from "node:fs/promises";
|
|
18
18
|
import path from "node:path";
|
|
19
19
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
-
import { XUtils } from "@vex-chat/crypto";
|
|
20
|
+
import { fipsEcdhRawPublicKeyFromEcdsaSpkiAsync, getCryptoProfile, XUtils, } from "@vex-chat/crypto";
|
|
21
21
|
import { MailType } from "@vex-chat/types";
|
|
22
22
|
import argon2 from "argon2";
|
|
23
23
|
/**
|
|
@@ -89,16 +89,8 @@ const pubkeyRegex = /[0-9a-f]{64}/;
|
|
|
89
89
|
/** Legacy iteration count kept only for verifying old PBKDF2 hashes. */
|
|
90
90
|
const PBKDF2_ITERATIONS = 1000;
|
|
91
91
|
export class Database extends EventEmitter {
|
|
92
|
+
static EMOJI_LIST_CACHE_TTL_MS = 10_000;
|
|
92
93
|
db;
|
|
93
|
-
/** Underlying better-sqlite3 handle (file or :memory:). */
|
|
94
|
-
rawSqlite;
|
|
95
|
-
/**
|
|
96
|
-
* In-process cache for {@link retrieveUserDeviceList} when queried for a
|
|
97
|
-
* single owner. Libvex calls GET /user/:id/devices on every outgoing
|
|
98
|
-
* `forward()`; stress runs otherwise hammer SQLite with identical reads.
|
|
99
|
-
* Cleared on device create/delete and when the DB is closed.
|
|
100
|
-
*/
|
|
101
|
-
userDeviceListCache = new Map();
|
|
102
94
|
/**
|
|
103
95
|
* Short TTL cache for {@link retrieveEmojiList} (key = server id, stored as
|
|
104
96
|
* `emojis.owner`). The noise harness fires `emoji.retrieveList` as a
|
|
@@ -106,8 +98,8 @@ export class Database extends EventEmitter {
|
|
|
106
98
|
* before heavier surfaces. Invalidated on emoji create/delete.
|
|
107
99
|
*/
|
|
108
100
|
emojiListByServerCache = new Map();
|
|
109
|
-
|
|
110
|
-
|
|
101
|
+
/** Underlying better-sqlite3 handle (file or :memory:). */
|
|
102
|
+
rawSqlite;
|
|
111
103
|
constructor(options) {
|
|
112
104
|
super();
|
|
113
105
|
const dbType = options?.dbType || "sqlite3";
|
|
@@ -136,65 +128,7 @@ export class Database extends EventEmitter {
|
|
|
136
128
|
});
|
|
137
129
|
void this.init();
|
|
138
130
|
}
|
|
139
|
-
/**
|
|
140
|
-
* Dev-only snapshot of SQLite files + pragmas (same process as Spire).
|
|
141
|
-
* Not for production callers.
|
|
142
|
-
*/
|
|
143
|
-
getDevSqliteMonitor() {
|
|
144
|
-
const openedAs = this.rawSqlite.name;
|
|
145
|
-
const absPath = openedAs === ":memory:"
|
|
146
|
-
? ":memory:"
|
|
147
|
-
: path.isAbsolute(openedAs)
|
|
148
|
-
? openedAs
|
|
149
|
-
: path.resolve(process.cwd(), openedAs);
|
|
150
|
-
const journalMode = this.rawSqlite.pragma("journal_mode", {
|
|
151
|
-
simple: true,
|
|
152
|
-
});
|
|
153
|
-
const synchronous = this.rawSqlite.pragma("synchronous", {
|
|
154
|
-
simple: true,
|
|
155
|
-
});
|
|
156
|
-
const busyTimeout = this.rawSqlite.pragma("busy_timeout", {
|
|
157
|
-
simple: true,
|
|
158
|
-
});
|
|
159
|
-
const cacheSize = this.rawSqlite.pragma("cache_size", { simple: true });
|
|
160
|
-
const pageCount = this.rawSqlite.pragma("page_count", { simple: true });
|
|
161
|
-
const pageSize = this.rawSqlite.pragma("page_size", { simple: true });
|
|
162
|
-
const freelistCount = this.rawSqlite.pragma("freelist_count", {
|
|
163
|
-
simple: true,
|
|
164
|
-
});
|
|
165
|
-
const out = {
|
|
166
|
-
absPath,
|
|
167
|
-
busyTimeout,
|
|
168
|
-
cacheSize,
|
|
169
|
-
freelistCount,
|
|
170
|
-
journalMode,
|
|
171
|
-
openedAs,
|
|
172
|
-
pageCount,
|
|
173
|
-
pageSize,
|
|
174
|
-
synchronous,
|
|
175
|
-
};
|
|
176
|
-
if (openedAs !== ":memory:") {
|
|
177
|
-
const filePaths = {
|
|
178
|
-
main: absPath,
|
|
179
|
-
shm: `${absPath}-shm`,
|
|
180
|
-
wal: `${absPath}-wal`,
|
|
181
|
-
};
|
|
182
|
-
out["filePaths"] = filePaths;
|
|
183
|
-
const sizes = {};
|
|
184
|
-
for (const [label, fp] of Object.entries(filePaths)) {
|
|
185
|
-
try {
|
|
186
|
-
sizes[label] = statSync(fp).size;
|
|
187
|
-
}
|
|
188
|
-
catch {
|
|
189
|
-
sizes[label] = 0;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
out["fileBytes"] = sizes;
|
|
193
|
-
}
|
|
194
|
-
return out;
|
|
195
|
-
}
|
|
196
131
|
async close() {
|
|
197
|
-
this.userDeviceListCache.clear();
|
|
198
132
|
this.emojiListByServerCache.clear();
|
|
199
133
|
await this.db.destroy();
|
|
200
134
|
}
|
|
@@ -217,7 +151,6 @@ export class Database extends EventEmitter {
|
|
|
217
151
|
signKey: payload.signKey,
|
|
218
152
|
};
|
|
219
153
|
await this.db.insertInto("devices").values(device).execute();
|
|
220
|
-
this.userDeviceListCache.delete(owner);
|
|
221
154
|
const medPreKeys = {
|
|
222
155
|
deviceID: device.deviceID,
|
|
223
156
|
index: payload.preKeyIndex,
|
|
@@ -328,11 +261,6 @@ export class Database extends EventEmitter {
|
|
|
328
261
|
.execute();
|
|
329
262
|
}
|
|
330
263
|
async deleteDevice(deviceID) {
|
|
331
|
-
const ownerRow = await this.db
|
|
332
|
-
.selectFrom("devices")
|
|
333
|
-
.select("owner")
|
|
334
|
-
.where("deviceID", "=", deviceID)
|
|
335
|
-
.executeTakeFirst();
|
|
336
264
|
await this.db
|
|
337
265
|
.deleteFrom("preKeys")
|
|
338
266
|
.where("deviceID", "=", deviceID)
|
|
@@ -346,9 +274,6 @@ export class Database extends EventEmitter {
|
|
|
346
274
|
.set({ deleted: 1 })
|
|
347
275
|
.where("deviceID", "=", deviceID)
|
|
348
276
|
.execute();
|
|
349
|
-
if (ownerRow?.owner) {
|
|
350
|
-
this.userDeviceListCache.delete(ownerRow.owner);
|
|
351
|
-
}
|
|
352
277
|
}
|
|
353
278
|
async deleteEmoji(emojiID) {
|
|
354
279
|
const existing = await this.retrieveEmoji(emojiID);
|
|
@@ -396,6 +321,63 @@ export class Database extends EventEmitter {
|
|
|
396
321
|
.where("serverID", "=", serverID)
|
|
397
322
|
.execute();
|
|
398
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Dev-only snapshot of SQLite files + pragmas (same process as Spire).
|
|
326
|
+
* Not for production callers.
|
|
327
|
+
*/
|
|
328
|
+
getDevSqliteMonitor() {
|
|
329
|
+
const openedAs = this.rawSqlite.name;
|
|
330
|
+
const absPath = openedAs === ":memory:"
|
|
331
|
+
? ":memory:"
|
|
332
|
+
: path.isAbsolute(openedAs)
|
|
333
|
+
? openedAs
|
|
334
|
+
: path.resolve(process.cwd(), openedAs);
|
|
335
|
+
const journalMode = this.rawSqlite.pragma("journal_mode", {
|
|
336
|
+
simple: true,
|
|
337
|
+
});
|
|
338
|
+
const synchronous = this.rawSqlite.pragma("synchronous", {
|
|
339
|
+
simple: true,
|
|
340
|
+
});
|
|
341
|
+
const busyTimeout = this.rawSqlite.pragma("busy_timeout", {
|
|
342
|
+
simple: true,
|
|
343
|
+
});
|
|
344
|
+
const cacheSize = this.rawSqlite.pragma("cache_size", { simple: true });
|
|
345
|
+
const pageCount = this.rawSqlite.pragma("page_count", { simple: true });
|
|
346
|
+
const pageSize = this.rawSqlite.pragma("page_size", { simple: true });
|
|
347
|
+
const freelistCount = this.rawSqlite.pragma("freelist_count", {
|
|
348
|
+
simple: true,
|
|
349
|
+
});
|
|
350
|
+
const out = {
|
|
351
|
+
absPath,
|
|
352
|
+
busyTimeout,
|
|
353
|
+
cacheSize,
|
|
354
|
+
freelistCount,
|
|
355
|
+
journalMode,
|
|
356
|
+
openedAs,
|
|
357
|
+
pageCount,
|
|
358
|
+
pageSize,
|
|
359
|
+
synchronous,
|
|
360
|
+
};
|
|
361
|
+
if (openedAs !== ":memory:") {
|
|
362
|
+
const filePaths = {
|
|
363
|
+
main: absPath,
|
|
364
|
+
shm: `${absPath}-shm`,
|
|
365
|
+
wal: `${absPath}-wal`,
|
|
366
|
+
};
|
|
367
|
+
out["filePaths"] = filePaths;
|
|
368
|
+
const sizes = {};
|
|
369
|
+
for (const [label, fp] of Object.entries(filePaths)) {
|
|
370
|
+
try {
|
|
371
|
+
sizes[label] = statSync(fp).size;
|
|
372
|
+
}
|
|
373
|
+
catch {
|
|
374
|
+
sizes[label] = 0;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
out["fileBytes"] = sizes;
|
|
378
|
+
}
|
|
379
|
+
return out;
|
|
380
|
+
}
|
|
399
381
|
async getKeyBundle(deviceID) {
|
|
400
382
|
const device = await this.retrieveDevice(deviceID);
|
|
401
383
|
if (!device) {
|
|
@@ -406,10 +388,14 @@ export class Database extends EventEmitter {
|
|
|
406
388
|
if (!preKey) {
|
|
407
389
|
throw new Error("Failed to get prekey.");
|
|
408
390
|
}
|
|
391
|
+
const signKeyBytes = XUtils.decodeHex(device.signKey);
|
|
392
|
+
const signKey = getCryptoProfile() === "fips"
|
|
393
|
+
? await fipsEcdhRawPublicKeyFromEcdsaSpkiAsync(signKeyBytes)
|
|
394
|
+
: signKeyBytes;
|
|
409
395
|
const keyBundle = {
|
|
410
396
|
otk,
|
|
411
397
|
preKey,
|
|
412
|
-
signKey
|
|
398
|
+
signKey,
|
|
413
399
|
};
|
|
414
400
|
return keyBundle;
|
|
415
401
|
}
|
|
@@ -772,34 +758,20 @@ export class Database extends EventEmitter {
|
|
|
772
758
|
const row = rows[0];
|
|
773
759
|
return row ? toUserRecord(row) : null;
|
|
774
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* All devices for the given user ID(s), **no in-process cache** — a prior
|
|
763
|
+
* 10s TTL was removed: concurrent GET + POST could end with a stale
|
|
764
|
+
* snapshot overwriting the map after a new device was inserted (automation
|
|
765
|
+
* and `POST /user/:id/devices` must see a fresh list on the next read).
|
|
766
|
+
*/
|
|
775
767
|
async retrieveUserDeviceList(userIDs) {
|
|
776
|
-
const now = Date.now();
|
|
777
|
-
if (userIDs.length === 1) {
|
|
778
|
-
const owner = userIDs[0];
|
|
779
|
-
if (typeof owner === "string") {
|
|
780
|
-
const hit = this.userDeviceListCache.get(owner);
|
|
781
|
-
if (hit && hit.until > now) {
|
|
782
|
-
return hit.devices.map((d) => ({ ...d }));
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
768
|
const rows = await this.db
|
|
787
769
|
.selectFrom("devices")
|
|
788
770
|
.selectAll()
|
|
789
771
|
.where("owner", "in", userIDs)
|
|
790
772
|
.where("deleted", "=", 0)
|
|
791
773
|
.execute();
|
|
792
|
-
|
|
793
|
-
if (userIDs.length === 1) {
|
|
794
|
-
const owner = userIDs[0];
|
|
795
|
-
if (typeof owner === "string") {
|
|
796
|
-
this.userDeviceListCache.set(owner, {
|
|
797
|
-
devices: devices.map((d) => ({ ...d })),
|
|
798
|
-
until: now + Database.USER_DEVICE_LIST_CACHE_TTL_MS,
|
|
799
|
-
});
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
|
-
return devices;
|
|
774
|
+
return rows.map(toDevice);
|
|
803
775
|
}
|
|
804
776
|
async retrieveUsers() {
|
|
805
777
|
const rows = await this.db.selectFrom("users").selectAll().execute();
|