@helyx/bot 0.4.23 → 0.4.24
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 +33 -0
- package/dist/verification-service.js +52 -5
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.24
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Reuse active Verification links when available, report their expiry, and allow members to replace them while atomically invalidating the old request.---
|
|
8
|
+
"@helyx/bot": patch
|
|
9
|
+
"@helyx/database": patch
|
|
10
|
+
"@helyx/sdk": patch
|
|
11
|
+
"@helyx/verification-contracts": patch
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Reuse active Verification links when available, report their expiry, and allow members to replace them while atomically invalidating the old request.
|
|
15
|
+
|
|
16
|
+
- Accept compatible internal Helyx releases within explicit minor-safe pre-1.0 or major-safe stable ranges, preventing dependency-only patch cascades.---
|
|
17
|
+
"@helyx/bot": patch
|
|
18
|
+
"@helyx/cli": patch
|
|
19
|
+
"@helyx/core": patch
|
|
20
|
+
"@helyx/framework": patch
|
|
21
|
+
"@helyx/platform": patch
|
|
22
|
+
"@helyx/sdk": patch
|
|
23
|
+
"@helyx/module-account-gate": patch
|
|
24
|
+
"@helyx/module-feedback": patch
|
|
25
|
+
"@helyx/module-giveaways": patch
|
|
26
|
+
"@helyx/module-levels": patch
|
|
27
|
+
"@helyx/module-logging": patch
|
|
28
|
+
"@helyx/module-polls": patch
|
|
29
|
+
"@helyx/module-reactionroles": patch
|
|
30
|
+
"@helyx/module-suggestions": patch
|
|
31
|
+
"@helyx/module-welcome": patch
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
Accept compatible internal Helyx releases within explicit minor-safe pre-1.0 or major-safe stable ranges, preventing dependency-only patch cascades.
|
|
35
|
+
|
|
3
36
|
## 0.4.23
|
|
4
37
|
|
|
5
38
|
### Patch Changes
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { actionReceiptSchema, challengeCreateRequestSchema, challengeCreateResponseSchema, dashboardAttemptPageSchema, dashboardAttemptQuerySchema, dashboardSafeAttemptDetailSchema, signedAssessmentEnvelopeSchema, } from "@helyx/verification-contracts";
|
|
2
|
+
import { VerificationChallengeActiveError } from "@helyx/sdk";
|
|
2
3
|
import { createPublicKey, verify } from "node:crypto";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
const healthSchema = z
|
|
@@ -8,12 +9,19 @@ const healthSchema = z
|
|
|
8
9
|
allowanceRemaining: z.number().int().min(0).optional(),
|
|
9
10
|
})
|
|
10
11
|
.strict();
|
|
12
|
+
const activeChallengeSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
message: z.literal("A verification link is already active."),
|
|
15
|
+
expiresAt: z.iso.datetime(),
|
|
16
|
+
})
|
|
17
|
+
.strict();
|
|
11
18
|
export class HttpVerificationService {
|
|
12
19
|
#baseUrl;
|
|
13
20
|
#internalSecret;
|
|
14
21
|
#publicSigningKeys;
|
|
15
22
|
#fetch;
|
|
16
23
|
#installationId;
|
|
24
|
+
#challengeCache = new Map();
|
|
17
25
|
#handler;
|
|
18
26
|
constructor(options) {
|
|
19
27
|
this.#baseUrl = options.baseUrl.replace(/\/$/u, "");
|
|
@@ -40,19 +48,36 @@ export class HttpVerificationService {
|
|
|
40
48
|
});
|
|
41
49
|
}
|
|
42
50
|
async createChallenge(input) {
|
|
43
|
-
|
|
51
|
+
const cacheKey = challengeCacheKey(input.installation.installationId, input.guildId, input.userId);
|
|
52
|
+
this.#pruneChallengeCache();
|
|
53
|
+
const cached = this.#challengeCache.get(cacheKey);
|
|
54
|
+
if (input.replaceActive !== true &&
|
|
55
|
+
cached &&
|
|
56
|
+
Date.parse(cached.expiresAt) > Date.now()) {
|
|
57
|
+
return challengeCreateResponseSchema.parse({ ...cached, reused: true });
|
|
58
|
+
}
|
|
59
|
+
const created = challengeCreateResponseSchema.parse(await this.#request("/internal/v1/challenges", {
|
|
44
60
|
method: "POST",
|
|
45
61
|
body: challengeCreateRequestSchema.parse(input),
|
|
46
62
|
}));
|
|
63
|
+
this.#challengeCache.set(cacheKey, created);
|
|
64
|
+
return created;
|
|
47
65
|
}
|
|
48
66
|
async cancelChallenges(input) {
|
|
49
|
-
|
|
67
|
+
const cancelled = z
|
|
50
68
|
.object({ cancelled: z.number().int().min(0) })
|
|
51
69
|
.strict()
|
|
52
70
|
.parse(await this.#request("/internal/v1/challenges/cancel", {
|
|
53
71
|
method: "POST",
|
|
54
72
|
body: input,
|
|
55
73
|
}));
|
|
74
|
+
const prefix = `${input.installationId}:${input.guildId}:`;
|
|
75
|
+
for (const key of this.#challengeCache.keys()) {
|
|
76
|
+
if (key.startsWith(prefix) &&
|
|
77
|
+
(input.userId === undefined || key === `${prefix}${input.userId}`))
|
|
78
|
+
this.#challengeCache.delete(key);
|
|
79
|
+
}
|
|
80
|
+
return cancelled;
|
|
56
81
|
}
|
|
57
82
|
async listAttempts(input) {
|
|
58
83
|
const { installationId, guildId, ...query } = input;
|
|
@@ -99,10 +124,12 @@ export class HttpVerificationService {
|
|
|
99
124
|
throw new Error("Verification assessment is outside its valid lifetime");
|
|
100
125
|
if (!this.#handler)
|
|
101
126
|
throw new Error("Verification result handler is unavailable");
|
|
102
|
-
|
|
127
|
+
const receipt = actionReceiptSchema.parse(await this.#handler({
|
|
103
128
|
envelope,
|
|
104
129
|
correlationId: envelope.payload.attemptId,
|
|
105
130
|
}));
|
|
131
|
+
this.#challengeCache.delete(challengeCacheKey(envelope.payload.installationId, envelope.payload.guildId, envelope.payload.userId));
|
|
132
|
+
return receipt;
|
|
106
133
|
}
|
|
107
134
|
async health() {
|
|
108
135
|
const health = healthSchema.parse(await this.#request("/internal/v1/health", { method: "GET" }));
|
|
@@ -138,8 +165,12 @@ export class HttpVerificationService {
|
|
|
138
165
|
if (allowNotFound && response.status === 404)
|
|
139
166
|
return null;
|
|
140
167
|
if (!response.ok)
|
|
141
|
-
if (response.status === 409)
|
|
142
|
-
|
|
168
|
+
if (response.status === 409) {
|
|
169
|
+
const active = activeChallengeSchema.safeParse(await response.json());
|
|
170
|
+
if (active.success)
|
|
171
|
+
throw new VerificationChallengeActiveError(active.data.expiresAt);
|
|
172
|
+
throw new Error("Verification request was rejected");
|
|
173
|
+
}
|
|
143
174
|
else if (response.status === 429)
|
|
144
175
|
throw new Error("Verification requests are temporarily limited. Try again shortly.");
|
|
145
176
|
else
|
|
@@ -153,6 +184,22 @@ export class HttpVerificationService {
|
|
|
153
184
|
throw new Error("Verification service returned an invalid response");
|
|
154
185
|
}
|
|
155
186
|
}
|
|
187
|
+
#pruneChallengeCache() {
|
|
188
|
+
const now = Date.now();
|
|
189
|
+
for (const [key, challenge] of this.#challengeCache) {
|
|
190
|
+
if (Date.parse(challenge.expiresAt) <= now)
|
|
191
|
+
this.#challengeCache.delete(key);
|
|
192
|
+
}
|
|
193
|
+
while (this.#challengeCache.size >= 10_000) {
|
|
194
|
+
const oldestKey = this.#challengeCache.keys().next().value;
|
|
195
|
+
if (oldestKey === undefined)
|
|
196
|
+
break;
|
|
197
|
+
this.#challengeCache.delete(oldestKey);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
function challengeCacheKey(installationId, guildId, userId) {
|
|
202
|
+
return `${installationId}:${guildId}:${userId}`;
|
|
156
203
|
}
|
|
157
204
|
function verifyEnvelope(envelope, publicKeySpkiBase64) {
|
|
158
205
|
let key;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/bot",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.24",
|
|
4
4
|
"description": "Production Discord runtime for the modular Helyx platform.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -48,15 +48,15 @@
|
|
|
48
48
|
"start": "node --env-file-if-exists=../../.env dist/index.js"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@helyx/config": "0.5.0",
|
|
52
|
-
"@helyx/control-protocol": "0.3.
|
|
53
|
-
"@helyx/core": "0.1.
|
|
54
|
-
"@helyx/database": "0.7.
|
|
55
|
-
"@helyx/framework": "0.1.
|
|
56
|
-
"@helyx/module-manifest": "0.6.0",
|
|
57
|
-
"@helyx/platform": "0.4.
|
|
58
|
-
"@helyx/sdk": "0.6.
|
|
59
|
-
"@helyx/verification-contracts": "0.3.
|
|
51
|
+
"@helyx/config": ">=0.5.0 <0.6.0",
|
|
52
|
+
"@helyx/control-protocol": ">=0.3.0 <0.4.0",
|
|
53
|
+
"@helyx/core": ">=0.1.0 <0.2.0",
|
|
54
|
+
"@helyx/database": ">=0.7.0 <0.8.0",
|
|
55
|
+
"@helyx/framework": ">=0.1.0 <0.2.0",
|
|
56
|
+
"@helyx/module-manifest": ">=0.6.0 <0.7.0",
|
|
57
|
+
"@helyx/platform": ">=0.4.0 <0.5.0",
|
|
58
|
+
"@helyx/sdk": ">=0.6.0 <0.7.0",
|
|
59
|
+
"@helyx/verification-contracts": ">=0.3.0 <0.4.0",
|
|
60
60
|
"discord.js": "14.27.0",
|
|
61
61
|
"fastify": "5.10.0",
|
|
62
62
|
"pino": "10.3.1",
|