@helyx/module-account-gate 1.0.15 → 1.1.3
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 +27 -0
- package/README.md +55 -90
- package/dist/challenge-interactions.d.ts +12 -0
- package/dist/challenge-interactions.js +238 -0
- package/dist/commands.js +3 -2
- package/dist/contracts.d.ts +1 -1
- package/dist/contracts.js +1 -1
- package/dist/expiry-workflows.d.ts +32 -0
- package/dist/expiry-workflows.js +281 -0
- package/dist/gateway-interactions.d.ts +5 -0
- package/dist/gateway-interactions.js +124 -0
- package/dist/health.d.ts +10 -0
- package/dist/health.js +55 -0
- package/dist/index.d.ts +8 -19
- package/dist/index.js +35 -3157
- package/dist/member-lifecycle.d.ts +21 -0
- package/dist/member-lifecycle.js +305 -0
- package/dist/member-resource.js +14 -74
- package/dist/member-restart.d.ts +4 -0
- package/dist/member-restart.js +76 -0
- package/dist/member-revocation.d.ts +21 -0
- package/dist/member-revocation.js +127 -0
- package/dist/member-start.d.ts +5 -0
- package/dist/member-start.js +199 -0
- package/dist/member-summary.d.ts +47 -0
- package/dist/member-summary.js +200 -0
- package/dist/panel-maintenance.d.ts +26 -0
- package/dist/panel-maintenance.js +143 -0
- package/dist/panel-model.d.ts +47 -0
- package/dist/panel-model.js +128 -0
- package/dist/panel-mutations.d.ts +24 -0
- package/dist/panel-mutations.js +89 -0
- package/dist/panel-presentation.d.ts +8 -0
- package/dist/panel-presentation.js +67 -0
- package/dist/panel-resource.d.ts +2 -13
- package/dist/panel-resource.js +122 -310
- package/dist/panel-runtime.d.ts +8 -0
- package/dist/panel-runtime.js +34 -0
- package/dist/records.d.ts +42 -2
- package/dist/records.js +31 -0
- package/dist/review-decisions.d.ts +3 -0
- package/dist/review-decisions.js +274 -0
- package/dist/review-delivery.d.ts +34 -0
- package/dist/review-delivery.js +252 -0
- package/dist/review-presentation.d.ts +3 -0
- package/dist/review-presentation.js +28 -0
- package/dist/review-store.d.ts +4 -0
- package/dist/review-store.js +94 -0
- package/dist/role-recovery.d.ts +23 -0
- package/dist/role-recovery.js +176 -0
- package/dist/runtime-dependencies.d.ts +32 -0
- package/dist/runtime-dependencies.js +27 -0
- package/dist/runtime-support.d.ts +19 -0
- package/dist/runtime-support.js +74 -0
- package/dist/setup-presentation.d.ts +15 -0
- package/dist/setup-presentation.js +378 -0
- package/dist/strict-mode.d.ts +17 -0
- package/dist/strict-mode.js +154 -0
- package/dist/task-contributions.d.ts +4 -0
- package/dist/task-contributions.js +113 -0
- package/dist/task-scheduling.d.ts +12 -0
- package/dist/task-scheduling.js +79 -0
- package/dist/terminal-finalization.d.ts +12 -0
- package/dist/terminal-finalization.js +238 -0
- package/dist/workflow-fences.d.ts +6 -0
- package/dist/workflow-fences.js +73 -0
- package/manifest.json +1 -1
- package/migrations/0002_panel_mutation_claims.sql +34 -0
- package/package.json +5 -4
package/dist/panel-resource.js
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import { DashboardActionValidationError, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
|
|
2
|
-
import {
|
|
2
|
+
import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
|
|
3
3
|
import { parseAccountGateConfiguration } from "./configuration.js";
|
|
4
4
|
import { managedResourceSummary } from "./managed-resource-summary.js";
|
|
5
|
-
import {
|
|
5
|
+
import { queuePanelMessageCleanup } from "./panel-maintenance.js";
|
|
6
|
+
import { DEFAULT_PANEL_MESSAGES, panelAccessType, panelDetail as detail, panelLimitError, panelMessage, panelMessageFromValue, panelNumber as numberValue, panelPasswordFromRow, panelString as stringValue, savedPanelMessage, } from "./panel-model.js";
|
|
7
|
+
import { claimPanelMutation, finalizePanelMutation, } from "./panel-mutations.js";
|
|
8
|
+
import { configuredPanelAccessType as configuredAccessType, inspectedPanelDetail as inspectedDetail, panelPasswordForCreate, panelPasswordForUpdate, } from "./panel-presentation.js";
|
|
6
9
|
import { accountGateRecords } from "./records.js";
|
|
7
|
-
|
|
10
|
+
export { getGatePanelRuntime, hasPasswordGatePanel } from "./panel-runtime.js";
|
|
8
11
|
const MAX_GATE_PANELS = 10;
|
|
9
|
-
const DEFAULT_PANEL_MESSAGES = {
|
|
10
|
-
challenges: "Complete a short human-interaction check to access the server. Press the button below when you are ready.",
|
|
11
|
-
password: "Enter the password for this gate in the Password Entry channel. Never enter your Discord password or authentication code.",
|
|
12
|
-
emoji: "React with the configured emoji on the Emoji Entry message to continue.",
|
|
13
|
-
};
|
|
14
12
|
export const gatePanelResource = {
|
|
15
13
|
id: "gate-panel",
|
|
16
14
|
availableWhileDisabled: true,
|
|
@@ -57,14 +55,50 @@ export const gatePanelResource = {
|
|
|
57
55
|
if (!row)
|
|
58
56
|
return { deleted: false };
|
|
59
57
|
assertRevision(row, input.expectedRevision);
|
|
60
|
-
|
|
61
|
-
const
|
|
58
|
+
const store = records(context);
|
|
59
|
+
const claimed = await claimPanelMutation(store, row, {
|
|
60
|
+
...auditContext(context),
|
|
61
|
+
action: "delete",
|
|
62
|
+
messageBody: savedPanelMessage(row),
|
|
63
|
+
accessType: panelAccessType(row),
|
|
64
|
+
password: panelPasswordFromRow(row),
|
|
65
|
+
});
|
|
66
|
+
if (!claimed)
|
|
67
|
+
throw stalePanel();
|
|
68
|
+
const channelId = stringValue(claimed.channel_id);
|
|
69
|
+
const messageId = stringValue(claimed.message_id);
|
|
70
|
+
const removed = await context
|
|
71
|
+
.deleteChannelMessage(channelId, messageId)
|
|
72
|
+
.then(() => true)
|
|
73
|
+
.catch(() => false);
|
|
74
|
+
if (!removed)
|
|
75
|
+
await queuePanelMessageCleanup({
|
|
76
|
+
services: context.services,
|
|
77
|
+
guildId: context.guildId,
|
|
78
|
+
panelId: input.resourceId,
|
|
79
|
+
channelId,
|
|
80
|
+
messageId,
|
|
81
|
+
});
|
|
82
|
+
const result = await store.deleteOne({
|
|
62
83
|
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
63
84
|
collection: "gate_panels",
|
|
64
85
|
where: { guild_id: context.guildId, panel_id: input.resourceId },
|
|
65
|
-
|
|
86
|
+
expected: { field: "revision", value: numberValue(claimed.revision) },
|
|
87
|
+
audit: {
|
|
88
|
+
guildId: context.guildId,
|
|
89
|
+
actorUserId: context.actor.userId,
|
|
90
|
+
action: "account_gate.panel_deleted",
|
|
91
|
+
source: "dashboard",
|
|
92
|
+
correlationId: context.correlationId,
|
|
93
|
+
idempotencyKey: `account-gate-panel-deleted:${input.resourceId}:${numberValue(claimed.revision)}`,
|
|
94
|
+
targetType: "account_gate_panel",
|
|
95
|
+
targetId: input.resourceId,
|
|
96
|
+
metadata: { channelId, messageId },
|
|
97
|
+
},
|
|
66
98
|
});
|
|
67
|
-
|
|
99
|
+
if (!result.deleted)
|
|
100
|
+
throw stalePanel();
|
|
101
|
+
return { deleted: true };
|
|
68
102
|
},
|
|
69
103
|
async executeAction(context, input) {
|
|
70
104
|
if (input.actionId !== "repair" && input.actionId !== "repost") {
|
|
@@ -94,20 +128,31 @@ async function writePanelMessage(context, current, messageBody, accessType, pass
|
|
|
94
128
|
!inspection.channelMatches) {
|
|
95
129
|
return replacePanel(context, current, action, messageBody, accessType, password);
|
|
96
130
|
}
|
|
131
|
+
const store = records(context);
|
|
132
|
+
const claimed = await claimPanelMutation(store, current, {
|
|
133
|
+
...auditContext(context),
|
|
134
|
+
action,
|
|
135
|
+
messageBody,
|
|
136
|
+
accessType,
|
|
137
|
+
password,
|
|
138
|
+
});
|
|
139
|
+
if (!claimed)
|
|
140
|
+
throw stalePanel();
|
|
97
141
|
try {
|
|
98
|
-
await context.editChannelMessage(stringValue(
|
|
142
|
+
await context.editChannelMessage(stringValue(claimed.channel_id), stringValue(claimed.message_id), panelMessage(messageBody, accessType, stringValue(claimed.panel_id)));
|
|
99
143
|
}
|
|
100
144
|
catch {
|
|
101
|
-
|
|
145
|
+
throw new DashboardActionValidationError("The Gate panel settings were saved, but the Discord message needs repair.");
|
|
102
146
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
messageId: stringValue(current.message_id),
|
|
106
|
-
messageBody,
|
|
107
|
-
accessType,
|
|
108
|
-
password,
|
|
147
|
+
const finalized = await finalizePanelMutation(store, claimed, {
|
|
148
|
+
...auditContext(context),
|
|
109
149
|
action,
|
|
150
|
+
channelId: stringValue(claimed.channel_id),
|
|
151
|
+
messageId: stringValue(claimed.message_id),
|
|
110
152
|
});
|
|
153
|
+
if (!finalized)
|
|
154
|
+
throw new DashboardActionValidationError("The Gate panel message was updated, but its final state needs repair.");
|
|
155
|
+
return detail(finalized, "posted");
|
|
111
156
|
}
|
|
112
157
|
async function replacePanel(context, current, action, messageBody, accessType, password) {
|
|
113
158
|
const configured = await context.services
|
|
@@ -117,85 +162,56 @@ async function replacePanel(context, current, action, messageBody, accessType, p
|
|
|
117
162
|
throw new DashboardActionValidationError("Save Access Control settings before posting the panel.");
|
|
118
163
|
}
|
|
119
164
|
const configuration = parseAccountGateConfiguration(configured.value);
|
|
120
|
-
const
|
|
165
|
+
const store = records(context);
|
|
166
|
+
const claimed = await claimPanelMutation(store, current, {
|
|
167
|
+
...auditContext(context),
|
|
168
|
+
action,
|
|
169
|
+
messageBody,
|
|
170
|
+
accessType,
|
|
171
|
+
password,
|
|
172
|
+
});
|
|
173
|
+
if (!claimed)
|
|
174
|
+
throw stalePanel();
|
|
175
|
+
const sent = await context.sendToChannel(configuration.gateChannelId, panelMessage(messageBody, accessType, stringValue(claimed.panel_id)));
|
|
121
176
|
try {
|
|
122
|
-
const
|
|
177
|
+
const finalized = await finalizePanelMutation(store, claimed, {
|
|
178
|
+
...auditContext(context),
|
|
179
|
+
action,
|
|
123
180
|
channelId: configuration.gateChannelId,
|
|
124
181
|
messageId: sent.messageId,
|
|
125
|
-
messageBody,
|
|
126
|
-
accessType,
|
|
127
|
-
password,
|
|
128
|
-
action,
|
|
129
182
|
});
|
|
183
|
+
if (!finalized)
|
|
184
|
+
throw stalePanel();
|
|
130
185
|
try {
|
|
131
186
|
await context.deleteChannelMessage(stringValue(current.channel_id), stringValue(current.message_id));
|
|
132
187
|
}
|
|
133
188
|
catch {
|
|
134
|
-
|
|
189
|
+
await queuePanelMessageCleanup({
|
|
190
|
+
services: context.services,
|
|
191
|
+
guildId: context.guildId,
|
|
192
|
+
panelId: stringValue(current.panel_id),
|
|
193
|
+
channelId: stringValue(current.channel_id),
|
|
194
|
+
messageId: stringValue(current.message_id),
|
|
195
|
+
});
|
|
135
196
|
}
|
|
136
|
-
return detail;
|
|
197
|
+
return detail(finalized, "posted");
|
|
137
198
|
}
|
|
138
199
|
catch (error) {
|
|
139
|
-
await context
|
|
200
|
+
const removed = await context
|
|
140
201
|
.deleteChannelMessage(configuration.gateChannelId, sent.messageId)
|
|
141
|
-
.
|
|
202
|
+
.then(() => true)
|
|
203
|
+
.catch(() => false);
|
|
204
|
+
if (!removed)
|
|
205
|
+
await queuePanelMessageCleanup({
|
|
206
|
+
services: context.services,
|
|
207
|
+
guildId: context.guildId,
|
|
208
|
+
panelId: stringValue(current.panel_id),
|
|
209
|
+
channelId: configuration.gateChannelId,
|
|
210
|
+
messageId: sent.messageId,
|
|
211
|
+
});
|
|
142
212
|
throw error;
|
|
143
213
|
}
|
|
144
214
|
}
|
|
145
|
-
async function persistPanelRevision(context, current, input) {
|
|
146
|
-
const revision = numberValue(current.revision) + 1;
|
|
147
|
-
const updatedAt = new Date();
|
|
148
|
-
const result = await records(context).updateOne({
|
|
149
|
-
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
150
|
-
collection: "gate_panels",
|
|
151
|
-
where: {
|
|
152
|
-
guild_id: context.guildId,
|
|
153
|
-
panel_id: stringValue(current.panel_id),
|
|
154
|
-
},
|
|
155
|
-
expected: { field: "revision", value: numberValue(current.revision) },
|
|
156
|
-
values: {
|
|
157
|
-
channel_id: input.channelId,
|
|
158
|
-
message_id: input.messageId,
|
|
159
|
-
message_body: input.messageBody,
|
|
160
|
-
access_type: input.accessType,
|
|
161
|
-
...(input.password
|
|
162
|
-
? {
|
|
163
|
-
password_hash: input.password.encodedHash,
|
|
164
|
-
password_ignore_case: input.password.ignoreCase,
|
|
165
|
-
password_trim_whitespace: input.password.trimWhitespace,
|
|
166
|
-
}
|
|
167
|
-
: {}),
|
|
168
|
-
revision,
|
|
169
|
-
updated_at: updatedAt,
|
|
170
|
-
},
|
|
171
|
-
audit: {
|
|
172
|
-
guildId: context.guildId,
|
|
173
|
-
actorUserId: context.actor.userId,
|
|
174
|
-
action: `account_gate.panel_${input.action}`,
|
|
175
|
-
source: "dashboard",
|
|
176
|
-
correlationId: context.correlationId,
|
|
177
|
-
idempotencyKey: context.correlationId,
|
|
178
|
-
targetType: "account_gate_panel",
|
|
179
|
-
targetId: stringValue(current.panel_id),
|
|
180
|
-
metadata: { revision },
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
if (!result.updated) {
|
|
184
|
-
throw new DashboardActionValidationError("The Gate panel changed in another session. Reload and try again.");
|
|
185
|
-
}
|
|
186
|
-
return detail({
|
|
187
|
-
...current,
|
|
188
|
-
channel_id: input.channelId,
|
|
189
|
-
message_id: input.messageId,
|
|
190
|
-
message_body: input.messageBody,
|
|
191
|
-
access_type: input.accessType,
|
|
192
|
-
password_hash: input.password?.encodedHash ?? null,
|
|
193
|
-
password_ignore_case: input.password?.ignoreCase ?? true,
|
|
194
|
-
password_trim_whitespace: input.password?.trimWhitespace ?? true,
|
|
195
|
-
revision,
|
|
196
|
-
updated_at: updatedAt,
|
|
197
|
-
}, "posted");
|
|
198
|
-
}
|
|
199
215
|
async function currentPanel(context, resourceId) {
|
|
200
216
|
const current = await currentPanelOrNull(context, resourceId);
|
|
201
217
|
if (!current) {
|
|
@@ -212,9 +228,8 @@ async function currentPanelOrNull(context, resourceId) {
|
|
|
212
228
|
});
|
|
213
229
|
}
|
|
214
230
|
function assertRevision(current, expectedRevision) {
|
|
215
|
-
if (numberValue(current.revision) !== expectedRevision)
|
|
216
|
-
throw
|
|
217
|
-
}
|
|
231
|
+
if (numberValue(current.revision) !== expectedRevision)
|
|
232
|
+
throw stalePanel();
|
|
218
233
|
}
|
|
219
234
|
async function post(context, value = {}) {
|
|
220
235
|
const configured = await context.services
|
|
@@ -255,6 +270,7 @@ async function post(context, value = {}) {
|
|
|
255
270
|
password_trim_whitespace: password.trimWhitespace,
|
|
256
271
|
}
|
|
257
272
|
: {}),
|
|
273
|
+
status: "posted",
|
|
258
274
|
revision: 1,
|
|
259
275
|
created_at: now,
|
|
260
276
|
updated_at: now,
|
|
@@ -267,19 +283,11 @@ async function post(context, value = {}) {
|
|
|
267
283
|
values: row,
|
|
268
284
|
});
|
|
269
285
|
if (!created.created) {
|
|
270
|
-
await context
|
|
271
|
-
.deleteChannelMessage(configuration.gateChannelId, sent.messageId)
|
|
272
|
-
.catch(() => undefined);
|
|
273
|
-
const concurrent = await currentPanelOrNull(context, context.correlationId);
|
|
274
|
-
if (concurrent)
|
|
275
|
-
return inspectedDetail(context, concurrent);
|
|
276
286
|
throw new DashboardActionValidationError("That gate-panel request was already handled. Reload the resources list.");
|
|
277
287
|
}
|
|
278
288
|
}
|
|
279
289
|
catch (error) {
|
|
280
|
-
await context
|
|
281
|
-
.deleteChannelMessage(configuration.gateChannelId, sent.messageId)
|
|
282
|
-
.catch(() => undefined);
|
|
290
|
+
await cleanupSentMessage(context, panelId, configuration.gateChannelId, sent.messageId);
|
|
283
291
|
const concurrent = await currentPanelOrNull(context, context.correlationId);
|
|
284
292
|
if (concurrent)
|
|
285
293
|
return inspectedDetail(context, concurrent);
|
|
@@ -300,228 +308,32 @@ async function post(context, value = {}) {
|
|
|
300
308
|
message_body: messageBody,
|
|
301
309
|
}, "posted");
|
|
302
310
|
}
|
|
303
|
-
function
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
},
|
|
313
|
-
],
|
|
314
|
-
},
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
allowedUserMentionIds: [],
|
|
319
|
-
componentsV2: {
|
|
320
|
-
...(accessType === "challenges" ? { accentColor: VIOLET } : {}),
|
|
321
|
-
text: [
|
|
322
|
-
...(accessType === "challenges"
|
|
323
|
-
? [{ content: "## Complete Access Control" }]
|
|
324
|
-
: []),
|
|
325
|
-
{
|
|
326
|
-
content: messageBody,
|
|
327
|
-
markdown: false,
|
|
328
|
-
},
|
|
329
|
-
],
|
|
330
|
-
},
|
|
331
|
-
buttons: [
|
|
332
|
-
{
|
|
333
|
-
type: "button",
|
|
334
|
-
customId: `${ACCOUNT_GATE_COMPONENTS.startPrefix}${panelId}`,
|
|
335
|
-
label: "Start check",
|
|
336
|
-
style: "primary",
|
|
337
|
-
},
|
|
338
|
-
],
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
|
-
async function configuredAccessType(context) {
|
|
342
|
-
const configured = await context.services
|
|
343
|
-
.get(HELYX_SERVICE_NAMES.configuration)
|
|
344
|
-
.get(context.guildId, ACCOUNT_GATE_IDENTITY.moduleId);
|
|
345
|
-
if (!configured)
|
|
346
|
-
return "challenges";
|
|
347
|
-
return parseAccountGateConfiguration(configured.value).accessType;
|
|
348
|
-
}
|
|
349
|
-
async function inspectedDetail(context, row) {
|
|
350
|
-
const inspected = await context.services
|
|
351
|
-
.get(HELYX_SERVICE_NAMES.discordResources)
|
|
352
|
-
.inspectMessage({
|
|
311
|
+
async function cleanupSentMessage(context, panelId, channelId, messageId) {
|
|
312
|
+
const removed = await context
|
|
313
|
+
.deleteChannelMessage(channelId, messageId)
|
|
314
|
+
.then(() => true)
|
|
315
|
+
.catch(() => false);
|
|
316
|
+
if (removed)
|
|
317
|
+
return;
|
|
318
|
+
await queuePanelMessageCleanup({
|
|
319
|
+
services: context.services,
|
|
353
320
|
guildId: context.guildId,
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return detail(row, inspected.exists && inspected.guildMatches && inspected.channelMatches
|
|
358
|
-
? "posted"
|
|
359
|
-
: "missing");
|
|
360
|
-
}
|
|
361
|
-
function detail(row, status) {
|
|
362
|
-
const updatedAt = row.updated_at instanceof Date ? row.updated_at : new Date(0);
|
|
363
|
-
const accessType = panelAccessType(row);
|
|
364
|
-
const panelTitles = {
|
|
365
|
-
challenges: "Challenge gate panel",
|
|
366
|
-
password: "Password Entry instructions",
|
|
367
|
-
emoji: "Emoji Entry instructions",
|
|
368
|
-
};
|
|
369
|
-
return {
|
|
370
|
-
id: stringValue(row.panel_id),
|
|
371
|
-
revision: numberValue(row.revision),
|
|
372
|
-
status,
|
|
373
|
-
title: panelTitles[accessType],
|
|
374
|
-
description: status === "posted"
|
|
375
|
-
? "Published and currently available."
|
|
376
|
-
: "The saved panel message is missing. Repost it to repair the panel.",
|
|
377
|
-
attributes: {
|
|
378
|
-
channelId: stringValue(row.channel_id),
|
|
379
|
-
messageId: stringValue(row.message_id),
|
|
380
|
-
accessType,
|
|
381
|
-
passwordConfigured: accessType === "password" && panelPasswordFromRow(row) !== null,
|
|
382
|
-
},
|
|
383
|
-
value: {
|
|
384
|
-
channelId: stringValue(row.channel_id),
|
|
385
|
-
messageId: stringValue(row.message_id),
|
|
386
|
-
accessType,
|
|
387
|
-
messageBody: savedPanelMessage(row),
|
|
388
|
-
replacementPhrase: "",
|
|
389
|
-
},
|
|
390
|
-
updatedAt: updatedAt.toISOString(),
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
function panelAccessType(row) {
|
|
394
|
-
const value = row.access_type;
|
|
395
|
-
if (value === "password" || value === "emoji")
|
|
396
|
-
return value;
|
|
397
|
-
return "challenges";
|
|
398
|
-
}
|
|
399
|
-
function panelMessageFromValue(value, fallback) {
|
|
400
|
-
const candidate = value.messageBody;
|
|
401
|
-
if (candidate === undefined)
|
|
402
|
-
return fallback;
|
|
403
|
-
if (typeof candidate !== "string") {
|
|
404
|
-
throw new DashboardActionValidationError("Enter a valid Access Control panel message.");
|
|
405
|
-
}
|
|
406
|
-
const normalized = candidate.trim();
|
|
407
|
-
if (normalized.length < 1 || normalized.length > 1000) {
|
|
408
|
-
throw new DashboardActionValidationError("The Access Control panel message must be between 1 and 1000 characters.");
|
|
409
|
-
}
|
|
410
|
-
return normalized;
|
|
411
|
-
}
|
|
412
|
-
function savedPanelMessage(row) {
|
|
413
|
-
const message = row.message_body;
|
|
414
|
-
return typeof message === "string" && message.length > 0
|
|
415
|
-
? message
|
|
416
|
-
: DEFAULT_PANEL_MESSAGES[panelAccessType(row)];
|
|
417
|
-
}
|
|
418
|
-
export async function getGatePanelRuntime(store, guildId, panelId) {
|
|
419
|
-
const row = await store.findOne({
|
|
420
|
-
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
421
|
-
collection: "gate_panels",
|
|
422
|
-
select: accountGateRecords.gate_panels.readableFields,
|
|
423
|
-
where: { guild_id: guildId, panel_id: panelId },
|
|
424
|
-
});
|
|
425
|
-
if (!row)
|
|
426
|
-
return null;
|
|
427
|
-
return {
|
|
428
|
-
accessType: panelAccessType(row),
|
|
429
|
-
password: panelPasswordFromRow(row),
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
export async function hasPasswordGatePanel(store, guildId) {
|
|
433
|
-
const page = await store.findMany({
|
|
434
|
-
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
435
|
-
collection: "gate_panels",
|
|
436
|
-
select: accountGateRecords.gate_panels.readableFields,
|
|
437
|
-
where: { guild_id: guildId, access_type: "password" },
|
|
438
|
-
orderBy: [
|
|
439
|
-
{ field: "guild_id", direction: "asc" },
|
|
440
|
-
{ field: "created_at", direction: "asc" },
|
|
441
|
-
{ field: "panel_id", direction: "asc" },
|
|
442
|
-
],
|
|
443
|
-
limit: MAX_GATE_PANELS,
|
|
321
|
+
panelId,
|
|
322
|
+
channelId,
|
|
323
|
+
messageId,
|
|
444
324
|
});
|
|
445
|
-
return page.records.some((row) => panelPasswordFromRow(row) !== null);
|
|
446
|
-
}
|
|
447
|
-
async function panelPasswordForCreate(configuration, accessType, value) {
|
|
448
|
-
if (accessType !== "password")
|
|
449
|
-
return null;
|
|
450
|
-
const phrase = replacementPhrase(value, true);
|
|
451
|
-
return {
|
|
452
|
-
encodedHash: await hashPasswordEntryPhrase(phrase, {
|
|
453
|
-
ignoreCase: configuration.passwordIgnoreCase,
|
|
454
|
-
trimWhitespace: configuration.passwordTrimWhitespace,
|
|
455
|
-
}),
|
|
456
|
-
ignoreCase: configuration.passwordIgnoreCase,
|
|
457
|
-
trimWhitespace: configuration.passwordTrimWhitespace,
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
async function panelPasswordForUpdate(context, current, value) {
|
|
461
|
-
if (panelAccessType(current) !== "password")
|
|
462
|
-
return null;
|
|
463
|
-
const existing = panelPasswordFromRow(current);
|
|
464
|
-
const phrase = replacementPhrase(value, false);
|
|
465
|
-
if (!phrase) {
|
|
466
|
-
if (existing)
|
|
467
|
-
return existing;
|
|
468
|
-
throw new DashboardActionValidationError("Enter a password containing 4 to 200 characters before publishing this panel.");
|
|
469
|
-
}
|
|
470
|
-
const configured = await context.services
|
|
471
|
-
.get(HELYX_SERVICE_NAMES.configuration)
|
|
472
|
-
.get(context.guildId, ACCOUNT_GATE_IDENTITY.moduleId);
|
|
473
|
-
if (!configured) {
|
|
474
|
-
throw new DashboardActionValidationError("Save Access Control settings before changing this password.");
|
|
475
|
-
}
|
|
476
|
-
const configuration = parseAccountGateConfiguration(configured.value);
|
|
477
|
-
return {
|
|
478
|
-
encodedHash: await hashPasswordEntryPhrase(phrase, {
|
|
479
|
-
ignoreCase: configuration.passwordIgnoreCase,
|
|
480
|
-
trimWhitespace: configuration.passwordTrimWhitespace,
|
|
481
|
-
}),
|
|
482
|
-
ignoreCase: configuration.passwordIgnoreCase,
|
|
483
|
-
trimWhitespace: configuration.passwordTrimWhitespace,
|
|
484
|
-
};
|
|
485
325
|
}
|
|
486
|
-
function
|
|
487
|
-
if (typeof row.password_hash !== "string" || row.password_hash.length < 1) {
|
|
488
|
-
return null;
|
|
489
|
-
}
|
|
326
|
+
function auditContext(context) {
|
|
490
327
|
return {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
: true,
|
|
495
|
-
trimWhitespace: typeof row.password_trim_whitespace === "boolean"
|
|
496
|
-
? row.password_trim_whitespace
|
|
497
|
-
: true,
|
|
328
|
+
guildId: context.guildId,
|
|
329
|
+
actorUserId: context.actor.userId,
|
|
330
|
+
correlationId: context.correlationId,
|
|
498
331
|
};
|
|
499
332
|
}
|
|
500
|
-
function
|
|
501
|
-
|
|
502
|
-
if ((candidate === undefined || candidate === "") && !required)
|
|
503
|
-
return "";
|
|
504
|
-
if (typeof candidate !== "string" ||
|
|
505
|
-
candidate.length < 4 ||
|
|
506
|
-
candidate.length > 200) {
|
|
507
|
-
throw new DashboardActionValidationError("Enter a password containing 4 to 200 characters.");
|
|
508
|
-
}
|
|
509
|
-
return candidate;
|
|
510
|
-
}
|
|
511
|
-
function panelLimitError() {
|
|
512
|
-
return new DashboardActionValidationError("You have reached your limit of 10 gate panels. Delete an unused gate before publishing another.");
|
|
333
|
+
function stalePanel() {
|
|
334
|
+
return new DashboardActionValidationError("The Gate panel changed in another session. Reload and try again.");
|
|
513
335
|
}
|
|
514
336
|
function records(context) {
|
|
515
337
|
return context.services.get(HELYX_SERVICE_NAMES.records);
|
|
516
338
|
}
|
|
517
|
-
function stringValue(value) {
|
|
518
|
-
if (typeof value !== "string")
|
|
519
|
-
throw new Error("Access Control panel record is invalid");
|
|
520
|
-
return value;
|
|
521
|
-
}
|
|
522
|
-
function numberValue(value) {
|
|
523
|
-
if (typeof value !== "number" || !Number.isSafeInteger(value))
|
|
524
|
-
throw new Error("Access Control panel record is invalid");
|
|
525
|
-
return value;
|
|
526
|
-
}
|
|
527
339
|
//# sourceMappingURL=panel-resource.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { ModuleRecordService } from "@helyx/sdk";
|
|
2
|
+
import type { GatePanelAccessType, PanelPassword } from "./panel-model.js";
|
|
3
|
+
export declare function getGatePanelRuntime(store: ModuleRecordService, guildId: string, panelId: string): Promise<{
|
|
4
|
+
accessType: GatePanelAccessType;
|
|
5
|
+
password: PanelPassword | null;
|
|
6
|
+
} | null>;
|
|
7
|
+
export declare function hasPasswordGatePanel(store: ModuleRecordService, guildId: string): Promise<boolean>;
|
|
8
|
+
//# sourceMappingURL=panel-runtime.d.ts.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
|
|
2
|
+
import { panelAccessType, panelPasswordFromRow } from "./panel-model.js";
|
|
3
|
+
import { accountGateRecords } from "./records.js";
|
|
4
|
+
const MAX_GATE_PANELS = 10;
|
|
5
|
+
export async function getGatePanelRuntime(store, guildId, panelId) {
|
|
6
|
+
const row = await store.findOne({
|
|
7
|
+
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
8
|
+
collection: "gate_panels",
|
|
9
|
+
select: accountGateRecords.gate_panels.readableFields,
|
|
10
|
+
where: { guild_id: guildId, panel_id: panelId },
|
|
11
|
+
});
|
|
12
|
+
if (!row)
|
|
13
|
+
return null;
|
|
14
|
+
return {
|
|
15
|
+
accessType: panelAccessType(row),
|
|
16
|
+
password: panelPasswordFromRow(row),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export async function hasPasswordGatePanel(store, guildId) {
|
|
20
|
+
const page = await store.findMany({
|
|
21
|
+
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
22
|
+
collection: "gate_panels",
|
|
23
|
+
select: accountGateRecords.gate_panels.readableFields,
|
|
24
|
+
where: { guild_id: guildId, access_type: "password" },
|
|
25
|
+
orderBy: [
|
|
26
|
+
{ field: "guild_id", direction: "asc" },
|
|
27
|
+
{ field: "created_at", direction: "asc" },
|
|
28
|
+
{ field: "panel_id", direction: "asc" },
|
|
29
|
+
],
|
|
30
|
+
limit: MAX_GATE_PANELS,
|
|
31
|
+
});
|
|
32
|
+
return page.records.some((row) => panelPasswordFromRow(row) !== null);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=panel-runtime.js.map
|
package/dist/records.d.ts
CHANGED
|
@@ -32,6 +32,9 @@ export declare const accountGateRecords: {
|
|
|
32
32
|
readonly type: "boolean";
|
|
33
33
|
readonly optional: true;
|
|
34
34
|
};
|
|
35
|
+
readonly status: {
|
|
36
|
+
readonly type: "string";
|
|
37
|
+
};
|
|
35
38
|
readonly revision: {
|
|
36
39
|
readonly type: "number";
|
|
37
40
|
};
|
|
@@ -43,10 +46,47 @@ export declare const accountGateRecords: {
|
|
|
43
46
|
};
|
|
44
47
|
};
|
|
45
48
|
readonly uniqueKeys: readonly [readonly ["panel_id"], readonly ["guild_id", "panel_id"]];
|
|
46
|
-
readonly readableFields: readonly ["panel_id", "guild_id", "channel_id", "message_id", "message_body", "access_type", "password_hash", "password_ignore_case", "password_trim_whitespace", "revision", "created_at", "updated_at"];
|
|
47
|
-
readonly writableFields: readonly ["channel_id", "message_id", "message_body", "access_type", "password_hash", "password_ignore_case", "password_trim_whitespace", "revision", "updated_at"];
|
|
49
|
+
readonly readableFields: readonly ["panel_id", "guild_id", "channel_id", "message_id", "message_body", "access_type", "password_hash", "password_ignore_case", "password_trim_whitespace", "status", "revision", "created_at", "updated_at"];
|
|
50
|
+
readonly writableFields: readonly ["channel_id", "message_id", "message_body", "access_type", "password_hash", "password_ignore_case", "password_trim_whitespace", "status", "revision", "updated_at"];
|
|
48
51
|
readonly orderKeys: readonly [readonly ["guild_id", "created_at", "panel_id"]];
|
|
49
52
|
};
|
|
53
|
+
readonly panel_message_cleanup: {
|
|
54
|
+
readonly table: "account_gate_panel_message_cleanup";
|
|
55
|
+
readonly fields: {
|
|
56
|
+
readonly cleanup_id: {
|
|
57
|
+
readonly type: "string";
|
|
58
|
+
};
|
|
59
|
+
readonly guild_id: {
|
|
60
|
+
readonly type: "string";
|
|
61
|
+
};
|
|
62
|
+
readonly panel_id: {
|
|
63
|
+
readonly type: "string";
|
|
64
|
+
};
|
|
65
|
+
readonly channel_id: {
|
|
66
|
+
readonly type: "string";
|
|
67
|
+
};
|
|
68
|
+
readonly message_id: {
|
|
69
|
+
readonly type: "string";
|
|
70
|
+
};
|
|
71
|
+
readonly attempts: {
|
|
72
|
+
readonly type: "number";
|
|
73
|
+
};
|
|
74
|
+
readonly next_attempt_at: {
|
|
75
|
+
readonly type: "date";
|
|
76
|
+
};
|
|
77
|
+
readonly completed_at: {
|
|
78
|
+
readonly type: "date";
|
|
79
|
+
readonly optional: true;
|
|
80
|
+
};
|
|
81
|
+
readonly created_at: {
|
|
82
|
+
readonly type: "date";
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
readonly uniqueKeys: readonly [readonly ["cleanup_id"], readonly ["guild_id", "channel_id", "message_id"]];
|
|
86
|
+
readonly readableFields: readonly ["cleanup_id", "guild_id", "panel_id", "channel_id", "message_id", "attempts", "next_attempt_at", "completed_at", "created_at"];
|
|
87
|
+
readonly writableFields: readonly ["attempts", "next_attempt_at", "completed_at"];
|
|
88
|
+
readonly orderKeys: readonly [readonly ["next_attempt_at", "cleanup_id"]];
|
|
89
|
+
};
|
|
50
90
|
readonly questions: {
|
|
51
91
|
readonly table: "account_gate_questions";
|
|
52
92
|
readonly fields: {
|