@helyx/bot 0.4.31 → 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 +22 -0
- package/dist/control-permissions.d.ts +38 -0
- package/dist/control-permissions.js +139 -0
- package/dist/control-server.d.ts +24 -16
- package/dist/control-server.js +149 -166
- package/dist/discord-guild-health.d.ts +174 -0
- package/dist/discord-guild-health.js +370 -0
- package/dist/events.d.ts +15 -0
- package/dist/events.js +338 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -7
- package/dist/interactions.js +36 -6
- package/dist/modules.d.ts +2 -0
- package/dist/modules.js +10 -1
- package/dist/oauth-module.d.ts +2 -0
- package/dist/oauth-module.js +196 -0
- package/dist/oauth-module.test.d.ts +2 -0
- package/dist/oauth-module.test.js +172 -0
- package/dist/oauth-service.d.ts +16 -0
- package/dist/oauth-service.js +40 -0
- 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 +29 -3
- package/dist/shard-worker.d.ts +5 -0
- package/dist/shard-worker.js +16 -1
- package/package.json +1 -1
package/dist/modules.d.ts
CHANGED
|
@@ -12,11 +12,13 @@ export interface ConfiguredModulePackage extends LoadedDefinition {
|
|
|
12
12
|
}
|
|
13
13
|
export declare const secureRandomSelectionWorkerKeyName = "HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY";
|
|
14
14
|
interface PlatformServiceEnvironment extends Pick<BotEnvironment, "HELYX_DATA_ENCRYPTION_KEY"> {
|
|
15
|
+
HELYX_DASHBOARD_PUBLIC_URL?: string;
|
|
15
16
|
HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY?: string;
|
|
16
17
|
}
|
|
17
18
|
export declare function deriveSecureRandomSelectionKey(encodedDataEncryptionKey: string): Uint8Array;
|
|
18
19
|
export declare function platformServiceOptions(environment: PlatformServiceEnvironment, modules: readonly ModuleDefinition[]): {
|
|
19
20
|
secureRandomSelectionKey?: Uint8Array;
|
|
21
|
+
dashboardBaseUrl?: string;
|
|
20
22
|
};
|
|
21
23
|
export declare function loadConfiguredModules(specifiers: readonly string[], importer?: ModuleImporter): Promise<ModuleDefinition[]>;
|
|
22
24
|
export declare function loadConfiguredModulePackages(specifiers: readonly string[], importer?: ModuleImporter, resolvePackageJson?: PackageJsonResolver, readPackageJson?: PackageJsonReader): Promise<ConfiguredModulePackage[]>;
|
package/dist/modules.js
CHANGED
|
@@ -21,9 +21,13 @@ export function deriveSecureRandomSelectionKey(encodedDataEncryptionKey) {
|
|
|
21
21
|
.digest();
|
|
22
22
|
}
|
|
23
23
|
export function platformServiceOptions(environment, modules) {
|
|
24
|
+
const urls = environment.HELYX_DASHBOARD_PUBLIC_URL
|
|
25
|
+
? { dashboardBaseUrl: environment.HELYX_DASHBOARD_PUBLIC_URL }
|
|
26
|
+
: {};
|
|
24
27
|
const internalSelectionKey = environment.HELYX_INTERNAL_SECURE_RANDOM_SELECTION_KEY;
|
|
25
28
|
if (internalSelectionKey) {
|
|
26
29
|
return {
|
|
30
|
+
...urls,
|
|
27
31
|
secureRandomSelectionKey: decodeSecureRandomSelectionKey(internalSelectionKey, secureRandomSelectionWorkerKeyName),
|
|
28
32
|
};
|
|
29
33
|
}
|
|
@@ -32,9 +36,10 @@ export function platformServiceOptions(environment, modules) {
|
|
|
32
36
|
if (modules.some(({ manifest }) => manifest.id === giveawaysModuleId)) {
|
|
33
37
|
throw new Error("HELYX_DATA_ENCRYPTION_KEY is required when Giveaways is loaded");
|
|
34
38
|
}
|
|
35
|
-
return
|
|
39
|
+
return urls;
|
|
36
40
|
}
|
|
37
41
|
return {
|
|
42
|
+
...urls,
|
|
38
43
|
secureRandomSelectionKey: deriveSecureRandomSelectionKey(encodedKey),
|
|
39
44
|
};
|
|
40
45
|
}
|
|
@@ -103,7 +108,11 @@ export function validateContributionParity(definition) {
|
|
|
103
108
|
...(definition.events?.guildVoiceSession ?? []),
|
|
104
109
|
...(definition.events?.messageReactionAdd ?? []),
|
|
105
110
|
...(definition.events?.messageReactionRemove ?? []),
|
|
111
|
+
...(definition.events?.messageReactionRemoveAll ?? []),
|
|
112
|
+
...(definition.events?.messageReactionRemoveEmoji ?? []),
|
|
113
|
+
...(definition.events?.guildMessageUpdate ?? []),
|
|
106
114
|
...(definition.events?.messageDelete ?? []),
|
|
115
|
+
...(definition.events?.guildMessageDeleteBulk ?? []),
|
|
107
116
|
...(definition.events?.guildMemberAdd ?? []),
|
|
108
117
|
...(definition.events?.guildMemberRemove ?? []),
|
|
109
118
|
...(definition.events?.guildRoleDelete ?? []),
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { defineModule, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
|
+
const MODULE_ID = "helyx.oauth";
|
|
3
|
+
const MODAL_ID = "helyx.oauth.revoke";
|
|
4
|
+
const SERVICE_FIELD_ID = "helyx.oauth.revoke.service";
|
|
5
|
+
const CONFIRM_FIELD_ID = "helyx.oauth.revoke.confirm";
|
|
6
|
+
const COMMENT_FIELD_ID = "helyx.oauth.revoke.comment";
|
|
7
|
+
const everyonePolicy = {
|
|
8
|
+
everyone: true,
|
|
9
|
+
requiredPermissions: [],
|
|
10
|
+
allowedRoleIds: [],
|
|
11
|
+
deniedRoleIds: [],
|
|
12
|
+
allowedUserIds: [],
|
|
13
|
+
deniedUserIds: [],
|
|
14
|
+
};
|
|
15
|
+
export function createOAuthModule() {
|
|
16
|
+
return defineModule({
|
|
17
|
+
manifest: {
|
|
18
|
+
manifestVersion: "0.1",
|
|
19
|
+
releaseStatus: "live",
|
|
20
|
+
id: MODULE_ID,
|
|
21
|
+
name: "Helyx OAuth",
|
|
22
|
+
version: "0.1.0",
|
|
23
|
+
description: "Provides self-service OAuth connection revocation.",
|
|
24
|
+
entry: "./dist/oauth-module.js",
|
|
25
|
+
framework: "^0.1.0",
|
|
26
|
+
dependencies: [],
|
|
27
|
+
permissions: [],
|
|
28
|
+
contributions: { commands: ["oauth"], events: [] },
|
|
29
|
+
},
|
|
30
|
+
defaultPermissions: {
|
|
31
|
+
module: everyonePolicy,
|
|
32
|
+
commands: { "oauth.revoke": everyonePolicy },
|
|
33
|
+
},
|
|
34
|
+
interactions: {
|
|
35
|
+
commands: [
|
|
36
|
+
{
|
|
37
|
+
name: "oauth",
|
|
38
|
+
description: "Manage OAuth access granted to Helyx.",
|
|
39
|
+
registration: "global",
|
|
40
|
+
contexts: ["guild"],
|
|
41
|
+
integrationTypes: ["guild_install"],
|
|
42
|
+
subcommands: [
|
|
43
|
+
{
|
|
44
|
+
name: "revoke",
|
|
45
|
+
description: "Revoke a service and delete the OAuth details Helyx stores for you.",
|
|
46
|
+
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
47
|
+
execute: async (context) => {
|
|
48
|
+
const oauth = context.services.get(HELYX_SERVICE_NAMES.oauth);
|
|
49
|
+
let connections;
|
|
50
|
+
try {
|
|
51
|
+
connections = await oauth.listConnections({
|
|
52
|
+
discordUserId: context.userId,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
await context.reply(unavailable());
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (connections.length === 0) {
|
|
60
|
+
await context.reply({
|
|
61
|
+
ephemeral: true,
|
|
62
|
+
componentsV2: {
|
|
63
|
+
text: [
|
|
64
|
+
{
|
|
65
|
+
content: "You do not have any OAuth services connected to Helyx.",
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await context.showModal({
|
|
73
|
+
customId: MODAL_ID,
|
|
74
|
+
title: "Revoke OAuth access",
|
|
75
|
+
items: [
|
|
76
|
+
{
|
|
77
|
+
type: "text-display",
|
|
78
|
+
content: "Choose one connection. This disconnects the service from Helyx everywhere. Any Helyx module, server or panel using it will lose access.",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "string-select",
|
|
82
|
+
customId: SERVICE_FIELD_ID,
|
|
83
|
+
label: "Connected service",
|
|
84
|
+
placeholder: "Choose a connection",
|
|
85
|
+
required: true,
|
|
86
|
+
minValues: 1,
|
|
87
|
+
maxValues: 1,
|
|
88
|
+
options: connections.slice(0, 25).map((connection) => ({
|
|
89
|
+
label: `${providerName(connection.provider)} — ${connection.displayName}`.slice(0, 100),
|
|
90
|
+
value: connection.selectionRef,
|
|
91
|
+
description: "Remove this connection",
|
|
92
|
+
})),
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
type: "string-select",
|
|
96
|
+
customId: CONFIRM_FIELD_ID,
|
|
97
|
+
label: "Confirm removal",
|
|
98
|
+
placeholder: "Select Remove access",
|
|
99
|
+
required: true,
|
|
100
|
+
minValues: 1,
|
|
101
|
+
maxValues: 1,
|
|
102
|
+
options: [
|
|
103
|
+
{
|
|
104
|
+
label: "Remove access",
|
|
105
|
+
value: "remove_access",
|
|
106
|
+
description: "Revoke access and delete my stored details",
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
customId: COMMENT_FIELD_ID,
|
|
112
|
+
label: "Reason or comment (optional)",
|
|
113
|
+
description: "Stored only in Helyx's restricted security audit.",
|
|
114
|
+
style: "paragraph",
|
|
115
|
+
required: false,
|
|
116
|
+
maxLength: 250,
|
|
117
|
+
},
|
|
118
|
+
],
|
|
119
|
+
fields: [],
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
modals: [
|
|
127
|
+
{
|
|
128
|
+
customId: MODAL_ID,
|
|
129
|
+
execute: async (context) => {
|
|
130
|
+
const selectionRef = context.getStringSelectValues(SERVICE_FIELD_ID)[0];
|
|
131
|
+
const confirmation = context.getStringSelectValues(CONFIRM_FIELD_ID)[0];
|
|
132
|
+
if (!selectionRef || confirmation !== "remove_access") {
|
|
133
|
+
await context.reply({
|
|
134
|
+
ephemeral: true,
|
|
135
|
+
componentsV2: {
|
|
136
|
+
text: [{ content: "Access was not removed." }],
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const comment = context.getField(COMMENT_FIELD_ID).trim();
|
|
142
|
+
await context.deferReply?.(true);
|
|
143
|
+
try {
|
|
144
|
+
const result = await context.services
|
|
145
|
+
.get(HELYX_SERVICE_NAMES.oauth)
|
|
146
|
+
.revoke({
|
|
147
|
+
discordUserId: context.userId,
|
|
148
|
+
selectionRef,
|
|
149
|
+
confirmation: "remove_access",
|
|
150
|
+
...(comment ? { comment } : {}),
|
|
151
|
+
correlationId: context.interactionId,
|
|
152
|
+
});
|
|
153
|
+
await context.reply({
|
|
154
|
+
ephemeral: true,
|
|
155
|
+
componentsV2: {
|
|
156
|
+
text: [
|
|
157
|
+
{
|
|
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.`,
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
},
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
await context.reply(unavailable());
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
},
|
|
177
|
+
start: () => Promise.resolve(),
|
|
178
|
+
stop: () => Promise.resolve(),
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
function providerName(provider) {
|
|
182
|
+
return provider === "youtube" ? "YouTube" : "Twitch";
|
|
183
|
+
}
|
|
184
|
+
function unavailable() {
|
|
185
|
+
return {
|
|
186
|
+
ephemeral: true,
|
|
187
|
+
componentsV2: {
|
|
188
|
+
text: [
|
|
189
|
+
{
|
|
190
|
+
content: "OAuth access could not be updated right now. Nothing new was retained; please try again.",
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//# sourceMappingURL=oauth-module.js.map
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { createOAuthModule } from "./oauth-module.js";
|
|
4
|
+
describe("global OAuth command", () => {
|
|
5
|
+
it("is global, guild-scoped and available without management permissions", () => {
|
|
6
|
+
const module = createOAuthModule();
|
|
7
|
+
const command = module.interactions?.commands?.[0];
|
|
8
|
+
const revoke = command?.subcommands?.[0];
|
|
9
|
+
expect(command).toMatchObject({
|
|
10
|
+
name: "oauth",
|
|
11
|
+
registration: "global",
|
|
12
|
+
contexts: ["guild"],
|
|
13
|
+
integrationTypes: ["guild_install"],
|
|
14
|
+
});
|
|
15
|
+
expect(revoke).toMatchObject({
|
|
16
|
+
name: "revoke",
|
|
17
|
+
rateLimit: { scope: "user", limit: 5, windowSeconds: 60 },
|
|
18
|
+
});
|
|
19
|
+
expect(module.manifest.permissions).toEqual([]);
|
|
20
|
+
expect(module.defaultPermissions?.commands["oauth.revoke"]).toMatchObject({
|
|
21
|
+
everyone: true,
|
|
22
|
+
requiredPermissions: [],
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
it("does not open a modal when the acting user has no connections", async () => {
|
|
26
|
+
const oauth = oauthService({ connections: [] });
|
|
27
|
+
const showModal = vi.fn();
|
|
28
|
+
const replies = [];
|
|
29
|
+
const command = createOAuthModule().interactions?.commands?.[0]?.subcommands?.[0];
|
|
30
|
+
await command?.execute(commandContext(oauth, {
|
|
31
|
+
showModal,
|
|
32
|
+
reply: (response) => {
|
|
33
|
+
replies.push(response);
|
|
34
|
+
return Promise.resolve();
|
|
35
|
+
},
|
|
36
|
+
}));
|
|
37
|
+
expect(showModal).not.toHaveBeenCalled();
|
|
38
|
+
expect(replies).toEqual([expect.objectContaining({ ephemeral: true })]);
|
|
39
|
+
expect(replies[0]?.componentsV2?.text?.[0]?.content).toContain("do not have any OAuth services");
|
|
40
|
+
});
|
|
41
|
+
it("uses opaque selections and requires explicit removal confirmation", async () => {
|
|
42
|
+
const oauth = oauthService({
|
|
43
|
+
connections: [
|
|
44
|
+
{
|
|
45
|
+
selectionRef: "opaque-selection-reference-123",
|
|
46
|
+
provider: "youtube",
|
|
47
|
+
displayName: "Example creator",
|
|
48
|
+
connectedAt: new Date().toISOString(),
|
|
49
|
+
},
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
const showModal = vi.fn();
|
|
53
|
+
const command = createOAuthModule().interactions?.commands?.[0]?.subcommands?.[0];
|
|
54
|
+
await command?.execute(commandContext(oauth, { showModal }));
|
|
55
|
+
const modal = showModal.mock.calls[0]?.[0];
|
|
56
|
+
expect(modal?.title).toBe("Revoke OAuth access");
|
|
57
|
+
const service = modal?.items?.find((item) => "customId" in item && item.customId.endsWith("service"));
|
|
58
|
+
const confirmation = modal?.items?.find((item) => "customId" in item && item.customId.endsWith("confirm"));
|
|
59
|
+
expect(service).toMatchObject({
|
|
60
|
+
type: "string-select",
|
|
61
|
+
required: true,
|
|
62
|
+
options: [
|
|
63
|
+
expect.objectContaining({ value: "opaque-selection-reference-123" }),
|
|
64
|
+
],
|
|
65
|
+
});
|
|
66
|
+
expect(confirmation).toMatchObject({
|
|
67
|
+
type: "string-select",
|
|
68
|
+
required: true,
|
|
69
|
+
options: [expect.objectContaining({ value: "remove_access" })],
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
it("revokes only for the acting user and returns an ephemeral advisory", async () => {
|
|
73
|
+
const revoke = vi.fn().mockResolvedValue({
|
|
74
|
+
provider: "twitch",
|
|
75
|
+
outcome: "completed",
|
|
76
|
+
providerRevocation: "failed",
|
|
77
|
+
});
|
|
78
|
+
const oauth = oauthService({ connections: [], revoke });
|
|
79
|
+
const replies = [];
|
|
80
|
+
const modal = createOAuthModule().interactions?.modals?.[0];
|
|
81
|
+
await modal?.execute(modalContext(oauth, {
|
|
82
|
+
reply: (response) => {
|
|
83
|
+
replies.push(response);
|
|
84
|
+
return Promise.resolve();
|
|
85
|
+
},
|
|
86
|
+
}));
|
|
87
|
+
expect(revoke).toHaveBeenCalledWith({
|
|
88
|
+
discordUserId: "12345678901234567",
|
|
89
|
+
selectionRef: "opaque-selection-reference-123",
|
|
90
|
+
confirmation: "remove_access",
|
|
91
|
+
comment: "No longer needed",
|
|
92
|
+
correlationId: "interaction-1",
|
|
93
|
+
});
|
|
94
|
+
expect(replies[0]).toMatchObject({ ephemeral: true });
|
|
95
|
+
expect(replies[0]?.componentsV2?.text?.[0]?.content).toContain("connected-app settings");
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
function oauthService(input) {
|
|
99
|
+
return {
|
|
100
|
+
connectionStatus: () => Promise.resolve([]),
|
|
101
|
+
listConnections: () => Promise.resolve(input.connections),
|
|
102
|
+
createAuthorization: () => Promise.resolve({
|
|
103
|
+
authorizationUrl: "https://example.com",
|
|
104
|
+
expiresAt: "",
|
|
105
|
+
}),
|
|
106
|
+
verifyEligibility: () => Promise.resolve({
|
|
107
|
+
provider: "youtube",
|
|
108
|
+
status: "unknown",
|
|
109
|
+
connected: false,
|
|
110
|
+
checkedAt: new Date().toISOString(),
|
|
111
|
+
}),
|
|
112
|
+
revoke: input.revoke ??
|
|
113
|
+
(() => Promise.resolve({
|
|
114
|
+
provider: "youtube",
|
|
115
|
+
outcome: "completed",
|
|
116
|
+
providerRevocation: "completed",
|
|
117
|
+
})),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
function services(oauth) {
|
|
121
|
+
return {
|
|
122
|
+
has: (name) => name === HELYX_SERVICE_NAMES.oauth,
|
|
123
|
+
get: (name) => {
|
|
124
|
+
if (name !== HELYX_SERVICE_NAMES.oauth)
|
|
125
|
+
throw new Error("unknown service");
|
|
126
|
+
return oauth;
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
function commandContext(oauth, overrides = {}) {
|
|
131
|
+
return {
|
|
132
|
+
...interactionBase(oauth),
|
|
133
|
+
showModal: () => Promise.resolve(),
|
|
134
|
+
getStringOption: () => null,
|
|
135
|
+
getBooleanOption: () => null,
|
|
136
|
+
getUserOption: () => null,
|
|
137
|
+
getChannelOption: () => null,
|
|
138
|
+
getRoleOption: () => null,
|
|
139
|
+
...overrides,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function modalContext(oauth, overrides = {}) {
|
|
143
|
+
return {
|
|
144
|
+
...interactionBase(oauth),
|
|
145
|
+
getField: (customId) => customId.endsWith("comment") ? "No longer needed" : "",
|
|
146
|
+
getStringSelectValues: (customId) => customId.endsWith("service")
|
|
147
|
+
? ["opaque-selection-reference-123"]
|
|
148
|
+
: ["remove_access"],
|
|
149
|
+
getSelectedUserIds: () => [],
|
|
150
|
+
getSelectedRoleIds: () => [],
|
|
151
|
+
getSelectedMentionables: () => ({ userIds: [], roleIds: [] }),
|
|
152
|
+
getSelectedChannelIds: () => [],
|
|
153
|
+
...overrides,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function interactionBase(oauth) {
|
|
157
|
+
return {
|
|
158
|
+
interactionId: "interaction-1",
|
|
159
|
+
userId: "12345678901234567",
|
|
160
|
+
userDisplayName: "Member",
|
|
161
|
+
userRoleIds: [],
|
|
162
|
+
isAdministrator: false,
|
|
163
|
+
serverId: "12345678901234568",
|
|
164
|
+
services: services(oauth),
|
|
165
|
+
reply: () => Promise.resolve(),
|
|
166
|
+
sendToChannel: () => Promise.resolve({ messageId: "message-1" }),
|
|
167
|
+
deleteChannelMessage: () => Promise.resolve(),
|
|
168
|
+
editChannelMessage: () => Promise.resolve(),
|
|
169
|
+
addChannelMessageReaction: () => Promise.resolve(),
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=oauth-module.test.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { OAuthConnectionSummary, OAuthEligibilityResult, OAuthService } from "@helyx/sdk";
|
|
2
|
+
export declare class HttpOAuthService implements OAuthService {
|
|
3
|
+
#private;
|
|
4
|
+
private readonly internalSecret;
|
|
5
|
+
constructor(baseUrl: string, internalSecret: string);
|
|
6
|
+
listConnections(input: {
|
|
7
|
+
discordUserId: string;
|
|
8
|
+
}): Promise<readonly OAuthConnectionSummary[]>;
|
|
9
|
+
connectionStatus(input: {
|
|
10
|
+
discordUserId: string;
|
|
11
|
+
}): Promise<readonly ("youtube" | "twitch")[]>;
|
|
12
|
+
createAuthorization(input: Parameters<OAuthService["createAuthorization"]>[0]): ReturnType<OAuthService["createAuthorization"]>;
|
|
13
|
+
verifyEligibility(input: Parameters<OAuthService["verifyEligibility"]>[0]): Promise<OAuthEligibilityResult>;
|
|
14
|
+
revoke(input: Parameters<OAuthService["revoke"]>[0]): ReturnType<OAuthService["revoke"]>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=oauth-service.d.ts.map
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export class HttpOAuthService {
|
|
2
|
+
internalSecret;
|
|
3
|
+
#baseUrl;
|
|
4
|
+
constructor(baseUrl, internalSecret) {
|
|
5
|
+
this.internalSecret = internalSecret;
|
|
6
|
+
this.#baseUrl = new URL(baseUrl);
|
|
7
|
+
}
|
|
8
|
+
async listConnections(input) {
|
|
9
|
+
const result = await this.#request("/v1/internal/oauth/connections", input);
|
|
10
|
+
return result.connections;
|
|
11
|
+
}
|
|
12
|
+
async connectionStatus(input) {
|
|
13
|
+
const result = await this.#request("/v1/internal/oauth/status", input);
|
|
14
|
+
return result.providers;
|
|
15
|
+
}
|
|
16
|
+
createAuthorization(input) {
|
|
17
|
+
return this.#request("/v1/internal/oauth/authorizations", input);
|
|
18
|
+
}
|
|
19
|
+
verifyEligibility(input) {
|
|
20
|
+
return this.#request("/v1/internal/oauth/eligibility", input);
|
|
21
|
+
}
|
|
22
|
+
revoke(input) {
|
|
23
|
+
return this.#request("/v1/internal/oauth/revocations", input);
|
|
24
|
+
}
|
|
25
|
+
async #request(path, body) {
|
|
26
|
+
const response = await fetch(new URL(path, this.#baseUrl), {
|
|
27
|
+
method: "POST",
|
|
28
|
+
headers: {
|
|
29
|
+
authorization: `Bearer ${this.internalSecret}`,
|
|
30
|
+
"content-type": "application/json",
|
|
31
|
+
},
|
|
32
|
+
body: JSON.stringify(body),
|
|
33
|
+
signal: AbortSignal.timeout(15_000),
|
|
34
|
+
});
|
|
35
|
+
if (!response.ok)
|
|
36
|
+
throw new Error(`oauth_service_http_${response.status}`);
|
|
37
|
+
return (await response.json());
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=oauth-service.js.map
|
|
@@ -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
|
}
|