@helyx/bot 0.4.22 → 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 +45 -0
- package/dist/shard-contracts.d.ts +6 -6
- package/dist/shard-worker.d.ts +1 -1
- package/dist/verification-service.js +52 -5
- package/package.json +10 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
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
|
+
|
|
36
|
+
## 0.4.23
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- Rebuild and publish Verification package artifacts that match the current source.
|
|
41
|
+
- Updated dependencies
|
|
42
|
+
- @helyx/verification-contracts@0.3.1
|
|
43
|
+
- @helyx/sdk@0.6.3
|
|
44
|
+
- @helyx/core@0.1.16
|
|
45
|
+
- @helyx/framework@0.1.16
|
|
46
|
+
- @helyx/platform@0.4.7
|
|
47
|
+
|
|
3
48
|
## 0.4.22
|
|
4
49
|
|
|
5
50
|
### Patch Changes
|
|
@@ -426,16 +426,16 @@ export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
426
426
|
}>>;
|
|
427
427
|
actionBoundary: z.ZodObject<{
|
|
428
428
|
recommendedAction: z.ZodEnum<{
|
|
429
|
+
none: "none";
|
|
429
430
|
grant_role: "grant_role";
|
|
430
431
|
remove_role: "remove_role";
|
|
431
|
-
none: "none";
|
|
432
432
|
kick: "kick";
|
|
433
433
|
ban: "ban";
|
|
434
434
|
}>;
|
|
435
435
|
allowedActions: z.ZodArray<z.ZodEnum<{
|
|
436
|
+
none: "none";
|
|
436
437
|
grant_role: "grant_role";
|
|
437
438
|
remove_role: "remove_role";
|
|
438
|
-
none: "none";
|
|
439
439
|
kick: "kick";
|
|
440
440
|
ban: "ban";
|
|
441
441
|
}>>;
|
|
@@ -735,16 +735,16 @@ export declare const shardControlRequestSchema: z.ZodObject<{
|
|
|
735
735
|
}>>;
|
|
736
736
|
actionBoundary: z.ZodObject<{
|
|
737
737
|
recommendedAction: z.ZodEnum<{
|
|
738
|
+
none: "none";
|
|
738
739
|
grant_role: "grant_role";
|
|
739
740
|
remove_role: "remove_role";
|
|
740
|
-
none: "none";
|
|
741
741
|
kick: "kick";
|
|
742
742
|
ban: "ban";
|
|
743
743
|
}>;
|
|
744
744
|
allowedActions: z.ZodArray<z.ZodEnum<{
|
|
745
|
+
none: "none";
|
|
745
746
|
grant_role: "grant_role";
|
|
746
747
|
remove_role: "remove_role";
|
|
747
|
-
none: "none";
|
|
748
748
|
kick: "kick";
|
|
749
749
|
ban: "ban";
|
|
750
750
|
}>>;
|
|
@@ -1549,16 +1549,16 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
1549
1549
|
}>>;
|
|
1550
1550
|
actionBoundary: z.ZodObject<{
|
|
1551
1551
|
recommendedAction: z.ZodEnum<{
|
|
1552
|
+
none: "none";
|
|
1552
1553
|
grant_role: "grant_role";
|
|
1553
1554
|
remove_role: "remove_role";
|
|
1554
|
-
none: "none";
|
|
1555
1555
|
kick: "kick";
|
|
1556
1556
|
ban: "ban";
|
|
1557
1557
|
}>;
|
|
1558
1558
|
allowedActions: z.ZodArray<z.ZodEnum<{
|
|
1559
|
+
none: "none";
|
|
1559
1560
|
grant_role: "grant_role";
|
|
1560
1561
|
remove_role: "remove_role";
|
|
1561
|
-
none: "none";
|
|
1562
1562
|
kick: "kick";
|
|
1563
1563
|
ban: "ban";
|
|
1564
1564
|
}>>;
|
package/dist/shard-worker.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export declare function createLeaseHeartbeatWatchdog(onExpired: () => void, time
|
|
|
9
9
|
};
|
|
10
10
|
export declare function startShardWorker(processEnvironment?: NodeJS.ProcessEnv, identity?: {
|
|
11
11
|
version: 1;
|
|
12
|
-
mode: "
|
|
12
|
+
mode: "auto" | "fixed";
|
|
13
13
|
scope: "local" | "coordinated";
|
|
14
14
|
generation: string;
|
|
15
15
|
supervisorId: string;
|
|
@@ -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.0",
|
|
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",
|