@helyx/bot 0.4.32 → 0.4.33
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 +10 -0
- package/dist/control-server.d.ts +23 -1
- package/dist/control-server.js +37 -0
- package/dist/oauth-module.js +7 -3
- package/dist/oauth-service.js +1 -1
- package/dist/shard-contracts.d.ts +42 -0
- package/dist/shard-contracts.js +26 -0
- package/dist/shard-router.d.ts +1 -0
- package/dist/shard-router.js +3 -0
- package/dist/shard-supervisor.js +23 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.33
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add versioned OAuth consumer cleanup contracts, durable and resumable
|
|
8
|
+
revocation state, least-privilege provider scopes, and Creator Connections
|
|
9
|
+
cleanup dispatch. Promote the hosted-only Creator Connections module to live
|
|
10
|
+
availability with clear OAuth activation validation. Ignore shard responses
|
|
11
|
+
that finish after shutdown has already detached their worker.
|
|
12
|
+
|
|
3
13
|
## 0.4.32
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/dist/control-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type DatabasePool, type GuildShardOwnership } from "@helyx/database";
|
|
2
|
-
import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { type DashboardActionResult, type GuildAccessPolicy, type ManagedResourceCreationIntent, type ManagedResourceDetail, type ManagedResourcePage, type ManagedResourceRelationPage, type ManagedResourceValue, type MessageResponse, type ModuleDefinition, type OAuthConsumerCleanupResult, type PermissionActor, type PrivacyCapturePage, type PrivacyErasePage, type PrivacyProviderExecutor, type PublicAchievementProjectionPage, type ServiceAccess } from "@helyx/sdk";
|
|
3
3
|
import { type ActionReceipt, type SignedAssessmentEnvelope } from "@helyx/verification-contracts";
|
|
4
4
|
import { type Client } from "discord.js";
|
|
5
5
|
import { type FastifyInstance } from "fastify";
|
|
@@ -143,6 +143,17 @@ export interface DashboardGuildAccessState extends GuildAccessPolicy {
|
|
|
143
143
|
updatedAt: string | null;
|
|
144
144
|
}
|
|
145
145
|
export interface BotControlHandler {
|
|
146
|
+
cleanupOAuthConsumer(input: {
|
|
147
|
+
consumerBindingId: string;
|
|
148
|
+
moduleId: string;
|
|
149
|
+
provider: "youtube" | "twitch";
|
|
150
|
+
guildId: string;
|
|
151
|
+
resourceType: string;
|
|
152
|
+
resourceId: string;
|
|
153
|
+
subjectUserId: string;
|
|
154
|
+
contractVersion: number;
|
|
155
|
+
correlationId: string;
|
|
156
|
+
}): Promise<OAuthConsumerCleanupResult>;
|
|
146
157
|
sendSupportNotification(input: SupportNotificationInput): Promise<void>;
|
|
147
158
|
listGuildPresence(input: {
|
|
148
159
|
guildIds: readonly string[];
|
|
@@ -339,6 +350,17 @@ export declare class BotControlService implements BotControlHandler {
|
|
|
339
350
|
guildId: string;
|
|
340
351
|
channelId: string;
|
|
341
352
|
} | undefined, presenceOwnership?: GuildShardOwnership | undefined, presenceShardCount?: number | undefined);
|
|
353
|
+
cleanupOAuthConsumer(input: {
|
|
354
|
+
consumerBindingId: string;
|
|
355
|
+
moduleId: string;
|
|
356
|
+
provider: "youtube" | "twitch";
|
|
357
|
+
guildId: string;
|
|
358
|
+
resourceType: string;
|
|
359
|
+
resourceId: string;
|
|
360
|
+
subjectUserId: string;
|
|
361
|
+
contractVersion: number;
|
|
362
|
+
correlationId: string;
|
|
363
|
+
}): Promise<OAuthConsumerCleanupResult>;
|
|
342
364
|
listGuildPresence(input: {
|
|
343
365
|
guildIds: readonly string[];
|
|
344
366
|
correlationId: string;
|
package/dist/control-server.js
CHANGED
|
@@ -286,6 +286,26 @@ export class BotControlService {
|
|
|
286
286
|
})
|
|
287
287
|
: []);
|
|
288
288
|
}
|
|
289
|
+
async cleanupOAuthConsumer(input) {
|
|
290
|
+
const module = this.#modules.get(input.moduleId);
|
|
291
|
+
if (!module)
|
|
292
|
+
throw new BotControlNotFoundError("OAuth consumer module is unavailable");
|
|
293
|
+
const consumer = module.oauthConsumers?.find((candidate) => candidate.provider === input.provider &&
|
|
294
|
+
candidate.resourceType === input.resourceType &&
|
|
295
|
+
candidate.version === input.contractVersion);
|
|
296
|
+
if (!consumer)
|
|
297
|
+
throw new BotControlNotFoundError("OAuth consumer cleanup contract is unavailable");
|
|
298
|
+
return consumer.cleanup({
|
|
299
|
+
consumerBindingId: input.consumerBindingId,
|
|
300
|
+
provider: input.provider,
|
|
301
|
+
guildId: input.guildId,
|
|
302
|
+
resourceType: input.resourceType,
|
|
303
|
+
resourceId: input.resourceId,
|
|
304
|
+
subjectUserId: input.subjectUserId,
|
|
305
|
+
correlationId: input.correlationId,
|
|
306
|
+
services: this.#services,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
289
309
|
async listGuildPresence(input) {
|
|
290
310
|
const guildIds = [...new Set(input.guildIds)];
|
|
291
311
|
if (guildIds.length > 200)
|
|
@@ -1213,6 +1233,23 @@ export async function createBotControlServer(input) {
|
|
|
1213
1233
|
server.get("/health", (_request, reply) => readiness(reply));
|
|
1214
1234
|
server.get("/v1/health", (_request, reply) => readiness(reply));
|
|
1215
1235
|
server.post("/v1/verification/results", async (request) => input.service.acceptVerificationResult(signedAssessmentEnvelopeSchema.parse(request.body)));
|
|
1236
|
+
server.post("/v1/oauth/consumers/cleanup", async (request) => {
|
|
1237
|
+
const body = z
|
|
1238
|
+
.object({
|
|
1239
|
+
consumerBindingId: z.uuid(),
|
|
1240
|
+
moduleId,
|
|
1241
|
+
provider: z.enum(["youtube", "twitch"]),
|
|
1242
|
+
guildId: snowflake,
|
|
1243
|
+
resourceType: z.string().regex(/^[a-z][a-z0-9_-]*$/u),
|
|
1244
|
+
resourceId,
|
|
1245
|
+
subjectUserId: snowflake,
|
|
1246
|
+
contractVersion: z.number().int().min(1).max(32_767),
|
|
1247
|
+
correlationId: z.string().trim().min(1).max(200),
|
|
1248
|
+
})
|
|
1249
|
+
.strict()
|
|
1250
|
+
.parse(request.body);
|
|
1251
|
+
return input.service.cleanupOAuthConsumer(body);
|
|
1252
|
+
});
|
|
1216
1253
|
server.get("/v1/shards/health", async (request, reply) => {
|
|
1217
1254
|
reply.header("Cache-Control", "no-store");
|
|
1218
1255
|
if (!input.shardHealth)
|
package/dist/oauth-module.js
CHANGED
|
@@ -155,9 +155,13 @@ export function createOAuthModule() {
|
|
|
155
155
|
componentsV2: {
|
|
156
156
|
text: [
|
|
157
157
|
{
|
|
158
|
-
content: result.
|
|
159
|
-
? `${providerName(result.provider)} access
|
|
160
|
-
:
|
|
158
|
+
content: result.outcome === "processing"
|
|
159
|
+
? `${providerName(result.provider)} access removal is already in progress. Helyx will finish it automatically.`
|
|
160
|
+
: result.outcome === "cleanup_pending"
|
|
161
|
+
? `${providerName(result.provider)} credentials were deleted. Helyx is still removing related roles and connection records.`
|
|
162
|
+
: result.providerRevocation === "completed"
|
|
163
|
+
? `${providerName(result.provider)} access has been removed from Helyx. Local credentials were deleted.`
|
|
164
|
+
: `${providerName(result.provider)} access has been removed from Helyx and local credentials were deleted. Helyx could not confirm provider-side revocation, so you may also remove Helyx from your ${providerName(result.provider)} connected-app settings.`,
|
|
161
165
|
},
|
|
162
166
|
],
|
|
163
167
|
},
|
package/dist/oauth-service.js
CHANGED
|
@@ -30,7 +30,7 @@ export class HttpOAuthService {
|
|
|
30
30
|
"content-type": "application/json",
|
|
31
31
|
},
|
|
32
32
|
body: JSON.stringify(body),
|
|
33
|
-
signal: AbortSignal.timeout(
|
|
33
|
+
signal: AbortSignal.timeout(15_000),
|
|
34
34
|
});
|
|
35
35
|
if (!response.ok)
|
|
36
36
|
throw new Error(`oauth_service_http_${response.status}`);
|
|
@@ -359,6 +359,20 @@ export declare const shardControlOperationSchema: z.ZodDiscriminatedUnion<[z.Zod
|
|
|
359
359
|
expectedRevision: z.ZodNumber;
|
|
360
360
|
value: z.ZodRecord<z.ZodString, z.ZodCustom<ManagedResourceValue, ManagedResourceValue>>;
|
|
361
361
|
actorAuthenticatedAt: z.ZodOptional<z.ZodISODateTime>;
|
|
362
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
363
|
+
kind: z.ZodLiteral<"oauth.consumer.cleanup">;
|
|
364
|
+
consumerBindingId: z.ZodUUID;
|
|
365
|
+
moduleId: z.ZodString;
|
|
366
|
+
provider: z.ZodEnum<{
|
|
367
|
+
youtube: "youtube";
|
|
368
|
+
twitch: "twitch";
|
|
369
|
+
}>;
|
|
370
|
+
guildId: z.ZodString;
|
|
371
|
+
resourceType: z.ZodString;
|
|
372
|
+
resourceId: z.ZodString;
|
|
373
|
+
subjectUserId: z.ZodString;
|
|
374
|
+
contractVersion: z.ZodNumber;
|
|
375
|
+
correlationId: z.ZodString;
|
|
362
376
|
}, z.core.$strict>, z.ZodObject<{
|
|
363
377
|
kind: z.ZodLiteral<"support.notification.send">;
|
|
364
378
|
guildId: z.ZodString;
|
|
@@ -698,6 +712,20 @@ export declare const shardControlRequestSchema: z.ZodObject<{
|
|
|
698
712
|
expectedRevision: z.ZodNumber;
|
|
699
713
|
value: z.ZodRecord<z.ZodString, z.ZodCustom<ManagedResourceValue, ManagedResourceValue>>;
|
|
700
714
|
actorAuthenticatedAt: z.ZodOptional<z.ZodISODateTime>;
|
|
715
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
716
|
+
kind: z.ZodLiteral<"oauth.consumer.cleanup">;
|
|
717
|
+
consumerBindingId: z.ZodUUID;
|
|
718
|
+
moduleId: z.ZodString;
|
|
719
|
+
provider: z.ZodEnum<{
|
|
720
|
+
youtube: "youtube";
|
|
721
|
+
twitch: "twitch";
|
|
722
|
+
}>;
|
|
723
|
+
guildId: z.ZodString;
|
|
724
|
+
resourceType: z.ZodString;
|
|
725
|
+
resourceId: z.ZodString;
|
|
726
|
+
subjectUserId: z.ZodString;
|
|
727
|
+
contractVersion: z.ZodNumber;
|
|
728
|
+
correlationId: z.ZodString;
|
|
701
729
|
}, z.core.$strict>, z.ZodObject<{
|
|
702
730
|
kind: z.ZodLiteral<"support.notification.send">;
|
|
703
731
|
guildId: z.ZodString;
|
|
@@ -1542,6 +1570,20 @@ export declare const supervisorMessageSchema: z.ZodDiscriminatedUnion<[z.ZodObje
|
|
|
1542
1570
|
expectedRevision: z.ZodNumber;
|
|
1543
1571
|
value: z.ZodRecord<z.ZodString, z.ZodCustom<ManagedResourceValue, ManagedResourceValue>>;
|
|
1544
1572
|
actorAuthenticatedAt: z.ZodOptional<z.ZodISODateTime>;
|
|
1573
|
+
}, z.core.$strict>, z.ZodObject<{
|
|
1574
|
+
kind: z.ZodLiteral<"oauth.consumer.cleanup">;
|
|
1575
|
+
consumerBindingId: z.ZodUUID;
|
|
1576
|
+
moduleId: z.ZodString;
|
|
1577
|
+
provider: z.ZodEnum<{
|
|
1578
|
+
youtube: "youtube";
|
|
1579
|
+
twitch: "twitch";
|
|
1580
|
+
}>;
|
|
1581
|
+
guildId: z.ZodString;
|
|
1582
|
+
resourceType: z.ZodString;
|
|
1583
|
+
resourceId: z.ZodString;
|
|
1584
|
+
subjectUserId: z.ZodString;
|
|
1585
|
+
contractVersion: z.ZodNumber;
|
|
1586
|
+
correlationId: z.ZodString;
|
|
1545
1587
|
}, z.core.$strict>, z.ZodObject<{
|
|
1546
1588
|
kind: z.ZodLiteral<"support.notification.send">;
|
|
1547
1589
|
guildId: z.ZodString;
|
package/dist/shard-contracts.js
CHANGED
|
@@ -425,6 +425,20 @@ export const shardControlOperationSchema = z.discriminatedUnion("kind", [
|
|
|
425
425
|
...mutationContext.shape,
|
|
426
426
|
})
|
|
427
427
|
.strict(),
|
|
428
|
+
z
|
|
429
|
+
.object({
|
|
430
|
+
kind: z.literal("oauth.consumer.cleanup"),
|
|
431
|
+
consumerBindingId: z.uuid(),
|
|
432
|
+
moduleId,
|
|
433
|
+
provider: z.enum(["youtube", "twitch"]),
|
|
434
|
+
guildId: snowflake,
|
|
435
|
+
resourceType,
|
|
436
|
+
resourceId,
|
|
437
|
+
subjectUserId: snowflake,
|
|
438
|
+
contractVersion: z.number().int().min(1).max(32_767),
|
|
439
|
+
correlationId: z.string().trim().min(1).max(200),
|
|
440
|
+
})
|
|
441
|
+
.strict(),
|
|
428
442
|
z
|
|
429
443
|
.object({
|
|
430
444
|
kind: z.literal("support.notification.send"),
|
|
@@ -1111,6 +1125,18 @@ export async function executeShardControlOperation(handler, operation) {
|
|
|
1111
1125
|
? { actorAuthenticatedAt: operation.actorAuthenticatedAt }
|
|
1112
1126
|
: {}),
|
|
1113
1127
|
});
|
|
1128
|
+
case "oauth.consumer.cleanup":
|
|
1129
|
+
return handler.cleanupOAuthConsumer({
|
|
1130
|
+
consumerBindingId: operation.consumerBindingId,
|
|
1131
|
+
moduleId: operation.moduleId,
|
|
1132
|
+
provider: operation.provider,
|
|
1133
|
+
guildId: operation.guildId,
|
|
1134
|
+
resourceType: operation.resourceType,
|
|
1135
|
+
resourceId: operation.resourceId,
|
|
1136
|
+
subjectUserId: operation.subjectUserId,
|
|
1137
|
+
contractVersion: operation.contractVersion,
|
|
1138
|
+
correlationId: operation.correlationId,
|
|
1139
|
+
});
|
|
1114
1140
|
case "support.notification.send": {
|
|
1115
1141
|
return handler.sendSupportNotification({
|
|
1116
1142
|
notificationId: operation.notificationId,
|
package/dist/shard-router.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare class ShardControlRouter implements BotControlHandler {
|
|
|
15
15
|
coordinator?: ShardCoordinator;
|
|
16
16
|
notificationGuildId?: string;
|
|
17
17
|
});
|
|
18
|
+
cleanupOAuthConsumer(input: Parameters<BotControlHandler["cleanupOAuthConsumer"]>[0]): ReturnType<BotControlHandler["cleanupOAuthConsumer"]>;
|
|
18
19
|
getGuildState(guildId: string): ReturnType<BotControlHandler["getGuildState"]>;
|
|
19
20
|
canAccessDashboard(input: Parameters<BotControlHandler["canAccessDashboard"]>[0]): ReturnType<BotControlHandler["canAccessDashboard"]>;
|
|
20
21
|
getGuildAccessPolicy(input: Parameters<BotControlHandler["getGuildAccessPolicy"]>[0]): ReturnType<BotControlHandler["getGuildAccessPolicy"]>;
|
package/dist/shard-router.js
CHANGED
|
@@ -12,6 +12,9 @@ export class ShardControlRouter {
|
|
|
12
12
|
constructor(input) {
|
|
13
13
|
this.input = input;
|
|
14
14
|
}
|
|
15
|
+
cleanupOAuthConsumer(input) {
|
|
16
|
+
return this.#route({ kind: "oauth.consumer.cleanup", ...input });
|
|
17
|
+
}
|
|
15
18
|
getGuildState(guildId) {
|
|
16
19
|
return this.#route({ kind: "guild.state.get", guildId });
|
|
17
20
|
}
|
package/dist/shard-supervisor.js
CHANGED
|
@@ -651,7 +651,22 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
651
651
|
}
|
|
652
652
|
}
|
|
653
653
|
assertShardMessageSize(response);
|
|
654
|
-
await
|
|
654
|
+
await sendWorkerResponse(shardId, response);
|
|
655
|
+
}
|
|
656
|
+
async function sendWorkerResponse(shardId, response) {
|
|
657
|
+
if (shuttingDown)
|
|
658
|
+
return;
|
|
659
|
+
const worker = workers.get(shardId);
|
|
660
|
+
if (!worker?.process)
|
|
661
|
+
return;
|
|
662
|
+
try {
|
|
663
|
+
await worker.send(response);
|
|
664
|
+
}
|
|
665
|
+
catch (error) {
|
|
666
|
+
if (shuttingDown || !worker.process)
|
|
667
|
+
return;
|
|
668
|
+
throw error;
|
|
669
|
+
}
|
|
655
670
|
}
|
|
656
671
|
async function handleWorkerMessage(shardId, raw) {
|
|
657
672
|
if (serializedBytes(raw) > SHARD_CONTROL_MAX_BYTES)
|
|
@@ -709,7 +724,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
709
724
|
const response = leaderLease
|
|
710
725
|
? await executeLeaderCloud(message.data)
|
|
711
726
|
: await routeCloudToLeader(message.data);
|
|
712
|
-
await
|
|
727
|
+
await sendWorkerResponse(shardId, response);
|
|
713
728
|
return;
|
|
714
729
|
}
|
|
715
730
|
if (message.data.type === "helyx.operations.health.request") {
|
|
@@ -720,7 +735,7 @@ export async function startShardSupervisor(environment, processEnvironment, work
|
|
|
720
735
|
health: boundedOperationalHealth(await buildHealth()),
|
|
721
736
|
};
|
|
722
737
|
assertShardMessageSize(response);
|
|
723
|
-
await
|
|
738
|
+
await sendWorkerResponse(shardId, response);
|
|
724
739
|
}
|
|
725
740
|
}
|
|
726
741
|
function dispatchLocal(shardId, request) {
|
|
@@ -1392,9 +1407,14 @@ export function buildShardWorkerEnvironment(source, identity, restartCount) {
|
|
|
1392
1407
|
"HELYX_SUPPORT_STAFF_CHANNEL_ID",
|
|
1393
1408
|
"HELYX_MODULE_PACKAGES",
|
|
1394
1409
|
"HELYX_MESSAGE_CONTENT_ENABLED",
|
|
1410
|
+
"HELYX_GUILD_MEMBERS_ENABLED",
|
|
1411
|
+
"HELYX_GUILD_RECONCILIATION_INTERVAL_SECONDS",
|
|
1395
1412
|
"HELYX_MODULE_CATALOGUE_PATH",
|
|
1396
1413
|
"HELYX_CLOUD_CONNECT_ENABLED",
|
|
1414
|
+
"HELYX_CLOUD_API_URL",
|
|
1397
1415
|
"HELYX_OWNER_USER_IDS",
|
|
1416
|
+
"HELYX_OAUTH_ENABLED",
|
|
1417
|
+
"HELYX_OAUTH_INTERNAL_SECRET",
|
|
1398
1418
|
"HELYX_VERIFICATION_ENABLED",
|
|
1399
1419
|
"HELYX_VERIFICATION_INSTALLATION_ID",
|
|
1400
1420
|
"HELYX_VERIFICATION_URL",
|