@helyx/bot 0.4.9 → 0.4.13
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/CHANGELOG.md +66 -0
- package/dist/cloud-control-dispatcher.js +2 -1
- package/dist/control-server.d.ts +43 -3
- package/dist/control-server.js +228 -18
- package/dist/events.d.ts +92 -1
- package/dist/events.js +1483 -5
- package/dist/guilds.d.ts +20 -1
- package/dist/guilds.js +149 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +89 -23
- package/dist/interactions.js +101 -22
- package/dist/management-module.d.ts +1 -1
- package/dist/management-module.js +42 -10
- package/dist/message-classification.d.ts +5 -0
- package/dist/message-classification.js +75 -0
- package/dist/modules.js +9 -0
- package/dist/numeric-write-behind-proxy.d.ts +62 -0
- package/dist/numeric-write-behind-proxy.js +313 -0
- package/dist/numeric-write-behind-redis.d.ts +123 -0
- package/dist/numeric-write-behind-redis.js +1281 -0
- package/dist/numeric-write-behind.d.ts +5 -0
- package/dist/numeric-write-behind.js +37 -0
- package/dist/shard-contracts.d.ts +810 -0
- package/dist/shard-contracts.js +292 -3
- package/dist/shard-router.d.ts +3 -0
- package/dist/shard-router.js +42 -0
- package/dist/shard-supervisor.d.ts +4 -1
- package/dist/shard-supervisor.js +262 -7
- package/dist/shard-worker.d.ts +7 -0
- package/dist/shard-worker.js +64 -10
- package/dist/verification-service.d.ts +50 -0
- package/dist/verification-service.js +166 -0
- package/package.json +10 -9
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { actionReceiptSchema, challengeCreateRequestSchema, challengeCreateResponseSchema, dashboardAttemptPageSchema, dashboardAttemptQuerySchema, dashboardSafeAttemptDetailSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
2
|
+
import { createPublicKey, verify } from "node:crypto";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
const healthSchema = z
|
|
5
|
+
.object({
|
|
6
|
+
status: z.enum(["healthy", "degraded", "unavailable"]),
|
|
7
|
+
provider: z.enum(["available", "degraded", "unavailable"]),
|
|
8
|
+
allowanceRemaining: z.number().int().min(0).optional(),
|
|
9
|
+
})
|
|
10
|
+
.strict();
|
|
11
|
+
export class HttpVerificationService {
|
|
12
|
+
#baseUrl;
|
|
13
|
+
#internalSecret;
|
|
14
|
+
#publicSigningKeys;
|
|
15
|
+
#fetch;
|
|
16
|
+
#installationId;
|
|
17
|
+
#handler;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.#baseUrl = options.baseUrl.replace(/\/$/u, "");
|
|
20
|
+
this.#internalSecret = options.internalSecret;
|
|
21
|
+
this.#publicSigningKeys = { ...options.publicSigningKeys };
|
|
22
|
+
this.#installationId = options.installationId;
|
|
23
|
+
this.#fetch = options.fetchImplementation ?? fetch;
|
|
24
|
+
}
|
|
25
|
+
getInstallationContext() {
|
|
26
|
+
return Promise.resolve({
|
|
27
|
+
installation: {
|
|
28
|
+
installationId: this.#installationId,
|
|
29
|
+
sourceClassification: "helyx_hosted",
|
|
30
|
+
capabilities: [
|
|
31
|
+
"challenge.create.v1",
|
|
32
|
+
"assessment.receive.v1",
|
|
33
|
+
"action.receipt.write.v1",
|
|
34
|
+
"member.current.read.v1",
|
|
35
|
+
"ban.current.read.v1",
|
|
36
|
+
"service.health.read.v1",
|
|
37
|
+
],
|
|
38
|
+
authenticatedAt: new Date().toISOString(),
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
async createChallenge(input) {
|
|
43
|
+
return challengeCreateResponseSchema.parse(await this.#request("/internal/v1/challenges", {
|
|
44
|
+
method: "POST",
|
|
45
|
+
body: challengeCreateRequestSchema.parse(input),
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
async cancelChallenges(input) {
|
|
49
|
+
return z
|
|
50
|
+
.object({ cancelled: z.number().int().min(0) })
|
|
51
|
+
.strict()
|
|
52
|
+
.parse(await this.#request("/internal/v1/challenges/cancel", {
|
|
53
|
+
method: "POST",
|
|
54
|
+
body: input,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
async listAttempts(input) {
|
|
58
|
+
const { installationId, guildId, ...query } = input;
|
|
59
|
+
return dashboardAttemptPageSchema.parse(await this.#request("/internal/v1/attempts/query", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: {
|
|
62
|
+
installationId,
|
|
63
|
+
guildId,
|
|
64
|
+
query: dashboardAttemptQuerySchema.parse(query),
|
|
65
|
+
},
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
async getAttempt(input) {
|
|
69
|
+
const result = await this.#request(`/internal/v1/attempts/${encodeURIComponent(input.attemptId)}`, { method: "POST", body: input }, true);
|
|
70
|
+
return result === null
|
|
71
|
+
? null
|
|
72
|
+
: dashboardSafeAttemptDetailSchema.parse(result);
|
|
73
|
+
}
|
|
74
|
+
async decideAttempt(input) {
|
|
75
|
+
return dashboardSafeAttemptDetailSchema.parse(await this.#request(`/internal/v1/attempts/${encodeURIComponent(input.attemptId)}/decision`, { method: "POST", body: input }));
|
|
76
|
+
}
|
|
77
|
+
registerResultHandler(handler) {
|
|
78
|
+
if (this.#handler !== undefined)
|
|
79
|
+
throw new Error("Verification result handler is already registered");
|
|
80
|
+
this.#handler = handler;
|
|
81
|
+
return () => {
|
|
82
|
+
if (this.#handler === handler)
|
|
83
|
+
this.#handler = undefined;
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
async acceptResult(envelopeInput) {
|
|
87
|
+
const envelope = signedAssessmentEnvelopeSchema.parse(envelopeInput);
|
|
88
|
+
if (envelope.payload.deploymentSourceClassification !== "helyx_hosted" ||
|
|
89
|
+
envelope.payload.evidenceSources.discord !== "helyx_hosted" ||
|
|
90
|
+
envelope.payload.installationId !== this.#installationId)
|
|
91
|
+
throw new Error("Verification assessment is not bound to hosted Helyx");
|
|
92
|
+
const publicKey = this.#publicSigningKeys[envelope.keyId];
|
|
93
|
+
if (publicKey === undefined || !verifyEnvelope(envelope, publicKey))
|
|
94
|
+
throw new Error("Verification assessment signature is invalid");
|
|
95
|
+
const expiresAt = Date.parse(envelope.payload.expiresAt);
|
|
96
|
+
const issuedAt = Date.parse(envelope.payload.issuedAt);
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (issuedAt > now + 30_000 || expiresAt <= now)
|
|
99
|
+
throw new Error("Verification assessment is outside its valid lifetime");
|
|
100
|
+
if (!this.#handler)
|
|
101
|
+
throw new Error("Verification result handler is unavailable");
|
|
102
|
+
return actionReceiptSchema.parse(await this.#handler({
|
|
103
|
+
envelope,
|
|
104
|
+
correlationId: envelope.payload.attemptId,
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
async health() {
|
|
108
|
+
const health = healthSchema.parse(await this.#request("/internal/v1/health", { method: "GET" }));
|
|
109
|
+
return {
|
|
110
|
+
status: health.status,
|
|
111
|
+
provider: health.provider,
|
|
112
|
+
...(health.allowanceRemaining === undefined
|
|
113
|
+
? {}
|
|
114
|
+
: { allowanceRemaining: health.allowanceRemaining }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async #request(path, input, allowNotFound = false) {
|
|
118
|
+
let response;
|
|
119
|
+
try {
|
|
120
|
+
response = await this.#fetch(`${this.#baseUrl}${path}`, {
|
|
121
|
+
method: input.method,
|
|
122
|
+
headers: {
|
|
123
|
+
authorization: `Bearer ${this.#internalSecret}`,
|
|
124
|
+
accept: "application/json",
|
|
125
|
+
...(input.body === undefined
|
|
126
|
+
? {}
|
|
127
|
+
: { "content-type": "application/json" }),
|
|
128
|
+
},
|
|
129
|
+
...(input.body === undefined
|
|
130
|
+
? {}
|
|
131
|
+
: { body: JSON.stringify(input.body) }),
|
|
132
|
+
signal: AbortSignal.timeout(10_000),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new Error("Verification service is unavailable");
|
|
137
|
+
}
|
|
138
|
+
if (allowNotFound && response.status === 404)
|
|
139
|
+
return null;
|
|
140
|
+
if (!response.ok)
|
|
141
|
+
throw new Error(response.status >= 500
|
|
142
|
+
? "Verification service is unavailable"
|
|
143
|
+
: "Verification request was rejected");
|
|
144
|
+
try {
|
|
145
|
+
return await response.json();
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
throw new Error("Verification service returned an invalid response");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function verifyEnvelope(envelope, publicKeySpkiBase64) {
|
|
153
|
+
let key;
|
|
154
|
+
try {
|
|
155
|
+
key = createPublicKey({
|
|
156
|
+
key: Buffer.from(publicKeySpkiBase64, "base64"),
|
|
157
|
+
format: "der",
|
|
158
|
+
type: "spki",
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return false;
|
|
163
|
+
}
|
|
164
|
+
return verify(null, Buffer.from(JSON.stringify(envelope.payload), "utf8"), key, Buffer.from(envelope.signature, "base64url"));
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=verification-service.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/bot",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
4
4
|
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -48,14 +48,15 @@
|
|
|
48
48
|
"start": "node --env-file-if-exists=../../.env dist/index.js"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@helyx/config": "0.
|
|
52
|
-
"@helyx/control-protocol": "0.
|
|
53
|
-
"@helyx/core": "0.1.
|
|
54
|
-
"@helyx/database": "0.
|
|
55
|
-
"@helyx/framework": "0.1.
|
|
56
|
-
"@helyx/module-manifest": "0.
|
|
57
|
-
"@helyx/platform": "0.
|
|
58
|
-
"@helyx/sdk": "0.
|
|
51
|
+
"@helyx/config": "0.5.0",
|
|
52
|
+
"@helyx/control-protocol": "0.3.0",
|
|
53
|
+
"@helyx/core": "0.1.12",
|
|
54
|
+
"@helyx/database": "0.6.0",
|
|
55
|
+
"@helyx/framework": "0.1.12",
|
|
56
|
+
"@helyx/module-manifest": "0.6.0",
|
|
57
|
+
"@helyx/platform": "0.4.0",
|
|
58
|
+
"@helyx/sdk": "0.5.0",
|
|
59
|
+
"@helyx/verification-contracts": "0.1.0",
|
|
59
60
|
"discord.js": "14.27.0",
|
|
60
61
|
"fastify": "5.10.0",
|
|
61
62
|
"pino": "10.3.1",
|