@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.
Files changed (69) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +55 -90
  3. package/dist/challenge-interactions.d.ts +12 -0
  4. package/dist/challenge-interactions.js +238 -0
  5. package/dist/commands.js +3 -2
  6. package/dist/contracts.d.ts +1 -1
  7. package/dist/contracts.js +1 -1
  8. package/dist/expiry-workflows.d.ts +32 -0
  9. package/dist/expiry-workflows.js +281 -0
  10. package/dist/gateway-interactions.d.ts +5 -0
  11. package/dist/gateway-interactions.js +124 -0
  12. package/dist/health.d.ts +10 -0
  13. package/dist/health.js +55 -0
  14. package/dist/index.d.ts +8 -19
  15. package/dist/index.js +35 -3157
  16. package/dist/member-lifecycle.d.ts +21 -0
  17. package/dist/member-lifecycle.js +305 -0
  18. package/dist/member-resource.js +14 -74
  19. package/dist/member-restart.d.ts +4 -0
  20. package/dist/member-restart.js +76 -0
  21. package/dist/member-revocation.d.ts +21 -0
  22. package/dist/member-revocation.js +127 -0
  23. package/dist/member-start.d.ts +5 -0
  24. package/dist/member-start.js +199 -0
  25. package/dist/member-summary.d.ts +47 -0
  26. package/dist/member-summary.js +200 -0
  27. package/dist/panel-maintenance.d.ts +26 -0
  28. package/dist/panel-maintenance.js +143 -0
  29. package/dist/panel-model.d.ts +47 -0
  30. package/dist/panel-model.js +128 -0
  31. package/dist/panel-mutations.d.ts +24 -0
  32. package/dist/panel-mutations.js +89 -0
  33. package/dist/panel-presentation.d.ts +8 -0
  34. package/dist/panel-presentation.js +67 -0
  35. package/dist/panel-resource.d.ts +2 -13
  36. package/dist/panel-resource.js +122 -310
  37. package/dist/panel-runtime.d.ts +8 -0
  38. package/dist/panel-runtime.js +34 -0
  39. package/dist/records.d.ts +42 -2
  40. package/dist/records.js +31 -0
  41. package/dist/review-decisions.d.ts +3 -0
  42. package/dist/review-decisions.js +274 -0
  43. package/dist/review-delivery.d.ts +34 -0
  44. package/dist/review-delivery.js +252 -0
  45. package/dist/review-presentation.d.ts +3 -0
  46. package/dist/review-presentation.js +28 -0
  47. package/dist/review-store.d.ts +4 -0
  48. package/dist/review-store.js +94 -0
  49. package/dist/role-recovery.d.ts +23 -0
  50. package/dist/role-recovery.js +176 -0
  51. package/dist/runtime-dependencies.d.ts +32 -0
  52. package/dist/runtime-dependencies.js +27 -0
  53. package/dist/runtime-support.d.ts +19 -0
  54. package/dist/runtime-support.js +74 -0
  55. package/dist/setup-presentation.d.ts +15 -0
  56. package/dist/setup-presentation.js +378 -0
  57. package/dist/strict-mode.d.ts +17 -0
  58. package/dist/strict-mode.js +154 -0
  59. package/dist/task-contributions.d.ts +4 -0
  60. package/dist/task-contributions.js +113 -0
  61. package/dist/task-scheduling.d.ts +12 -0
  62. package/dist/task-scheduling.js +79 -0
  63. package/dist/terminal-finalization.d.ts +12 -0
  64. package/dist/terminal-finalization.js +238 -0
  65. package/dist/workflow-fences.d.ts +6 -0
  66. package/dist/workflow-fences.js +73 -0
  67. package/manifest.json +1 -1
  68. package/migrations/0002_panel_mutation_claims.sql +34 -0
  69. package/package.json +5 -4
@@ -0,0 +1,128 @@
1
+ import { DashboardActionValidationError } from "@helyx/sdk";
2
+ import { ACCOUNT_GATE_COMPONENTS } from "./contracts.js";
3
+ const VIOLET = 0x7c3aed;
4
+ export const DEFAULT_PANEL_MESSAGES = {
5
+ challenges: "Complete a short human-interaction check to access the server. Press the button below when you are ready.",
6
+ password: "Enter the password for this gate in the Password Entry channel. Never enter your Discord password or authentication code.",
7
+ emoji: "React with the configured emoji on the Emoji Entry message to continue.",
8
+ };
9
+ export function panelMessage(messageBody, accessType, panelId) {
10
+ if (accessType === "emoji") {
11
+ return {
12
+ allowedUserMentionIds: [],
13
+ componentsV2: { text: [{ content: messageBody, markdown: false }] },
14
+ };
15
+ }
16
+ return {
17
+ allowedUserMentionIds: [],
18
+ componentsV2: {
19
+ ...(accessType === "challenges" ? { accentColor: VIOLET } : {}),
20
+ text: [
21
+ ...(accessType === "challenges"
22
+ ? [{ content: "## Complete Access Control" }]
23
+ : []),
24
+ { content: messageBody, markdown: false },
25
+ ],
26
+ },
27
+ buttons: [
28
+ {
29
+ type: "button",
30
+ customId: `${ACCOUNT_GATE_COMPONENTS.startPrefix}${panelId}`,
31
+ label: "Start check",
32
+ style: "primary",
33
+ },
34
+ ],
35
+ };
36
+ }
37
+ export function panelDetail(row, status) {
38
+ const updatedAt = row.updated_at instanceof Date ? row.updated_at : new Date(0);
39
+ const accessType = panelAccessType(row);
40
+ const panelTitles = {
41
+ challenges: "Challenge gate panel",
42
+ password: "Password Entry instructions",
43
+ emoji: "Emoji Entry instructions",
44
+ };
45
+ return {
46
+ id: panelString(row.panel_id),
47
+ revision: panelNumber(row.revision),
48
+ status,
49
+ title: panelTitles[accessType],
50
+ description: status === "posted"
51
+ ? "Published and currently available."
52
+ : "The saved panel message is missing. Repost it to repair the panel.",
53
+ attributes: {
54
+ channelId: panelString(row.channel_id),
55
+ messageId: panelString(row.message_id),
56
+ accessType,
57
+ passwordConfigured: accessType === "password" && panelPasswordFromRow(row) !== null,
58
+ },
59
+ value: {
60
+ channelId: panelString(row.channel_id),
61
+ messageId: panelString(row.message_id),
62
+ accessType,
63
+ messageBody: savedPanelMessage(row),
64
+ replacementPhrase: "",
65
+ },
66
+ updatedAt: updatedAt.toISOString(),
67
+ };
68
+ }
69
+ export function panelAccessType(row) {
70
+ const value = row.access_type;
71
+ if (value === "password" || value === "emoji")
72
+ return value;
73
+ return "challenges";
74
+ }
75
+ export function panelMessageFromValue(value, fallback) {
76
+ const candidate = value.messageBody;
77
+ if (candidate === undefined)
78
+ return fallback;
79
+ if (typeof candidate !== "string")
80
+ throw new DashboardActionValidationError("Enter a valid Access Control panel message.");
81
+ const normalized = candidate.trim();
82
+ if (normalized.length < 1 || normalized.length > 1000)
83
+ throw new DashboardActionValidationError("The Access Control panel message must be between 1 and 1000 characters.");
84
+ return normalized;
85
+ }
86
+ export function savedPanelMessage(row) {
87
+ const message = row.message_body;
88
+ return typeof message === "string" && message.length > 0
89
+ ? message
90
+ : DEFAULT_PANEL_MESSAGES[panelAccessType(row)];
91
+ }
92
+ export function panelPasswordFromRow(row) {
93
+ if (typeof row.password_hash !== "string" || row.password_hash.length < 1)
94
+ return null;
95
+ return {
96
+ encodedHash: row.password_hash,
97
+ ignoreCase: typeof row.password_ignore_case === "boolean"
98
+ ? row.password_ignore_case
99
+ : true,
100
+ trimWhitespace: typeof row.password_trim_whitespace === "boolean"
101
+ ? row.password_trim_whitespace
102
+ : true,
103
+ };
104
+ }
105
+ export function replacementPhrase(value, required) {
106
+ const candidate = value.replacementPhrase;
107
+ if ((candidate === undefined || candidate === "") && !required)
108
+ return "";
109
+ if (typeof candidate !== "string" ||
110
+ candidate.length < 4 ||
111
+ candidate.length > 200)
112
+ throw new DashboardActionValidationError("Enter a password containing 4 to 200 characters.");
113
+ return candidate;
114
+ }
115
+ export function panelLimitError() {
116
+ return new DashboardActionValidationError("You have reached your limit of 10 gate panels. Delete an unused gate before publishing another.");
117
+ }
118
+ export function panelString(value) {
119
+ if (typeof value !== "string")
120
+ throw new Error("Access Control panel record is invalid");
121
+ return value;
122
+ }
123
+ export function panelNumber(value) {
124
+ if (typeof value !== "number" || !Number.isSafeInteger(value))
125
+ throw new Error("Access Control panel record is invalid");
126
+ return value;
127
+ }
128
+ //# sourceMappingURL=panel-model.js.map
@@ -0,0 +1,24 @@
1
+ import type { ModuleRecordService } from "@helyx/sdk";
2
+ interface PanelPasswordMutation {
3
+ encodedHash: string;
4
+ ignoreCase: boolean;
5
+ trimWhitespace: boolean;
6
+ }
7
+ interface PanelAuditContext {
8
+ actorUserId: string;
9
+ correlationId: string;
10
+ guildId: string;
11
+ }
12
+ export declare function claimPanelMutation(records: ModuleRecordService, current: Readonly<Record<string, unknown>>, input: PanelAuditContext & {
13
+ action: "updated" | "repaired" | "reposted" | "delete";
14
+ messageBody: string;
15
+ accessType: "challenges" | "password" | "emoji";
16
+ password: PanelPasswordMutation | null;
17
+ }): Promise<Readonly<Record<string, unknown>> | null>;
18
+ export declare function finalizePanelMutation(records: ModuleRecordService, claimed: Readonly<Record<string, unknown>>, input: PanelAuditContext & {
19
+ action: "updated" | "repaired" | "reposted";
20
+ channelId: string;
21
+ messageId: string;
22
+ }): Promise<Readonly<Record<string, unknown>> | null>;
23
+ export {};
24
+ //# sourceMappingURL=panel-mutations.d.ts.map
@@ -0,0 +1,89 @@
1
+ import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
2
+ export async function claimPanelMutation(records, current, input) {
3
+ const revision = recordNumber(current.revision) + 1;
4
+ const updatedAt = new Date();
5
+ const values = {
6
+ message_body: input.messageBody,
7
+ access_type: input.accessType,
8
+ ...(input.password
9
+ ? {
10
+ password_hash: input.password.encodedHash,
11
+ password_ignore_case: input.password.ignoreCase,
12
+ password_trim_whitespace: input.password.trimWhitespace,
13
+ }
14
+ : {}),
15
+ status: "missing",
16
+ revision,
17
+ updated_at: updatedAt,
18
+ };
19
+ const result = await records.updateOne({
20
+ moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
21
+ collection: "gate_panels",
22
+ where: {
23
+ guild_id: input.guildId,
24
+ panel_id: recordString(current.panel_id),
25
+ },
26
+ expected: { field: "revision", value: recordNumber(current.revision) },
27
+ values,
28
+ audit: {
29
+ guildId: input.guildId,
30
+ actorUserId: input.actorUserId,
31
+ action: `account_gate.panel_${input.action}_claimed`,
32
+ source: "dashboard",
33
+ correlationId: input.correlationId,
34
+ idempotencyKey: `account-gate-panel-${input.action}-claimed:${recordString(current.panel_id)}:${revision}`,
35
+ targetType: "account_gate_panel",
36
+ targetId: recordString(current.panel_id),
37
+ metadata: { revision },
38
+ },
39
+ });
40
+ return result.updated ? { ...current, ...values } : null;
41
+ }
42
+ export async function finalizePanelMutation(records, claimed, input) {
43
+ const revision = recordNumber(claimed.revision) + 1;
44
+ const updatedAt = new Date();
45
+ const values = {
46
+ channel_id: input.channelId,
47
+ message_id: input.messageId,
48
+ status: "posted",
49
+ revision,
50
+ updated_at: updatedAt,
51
+ };
52
+ const result = await records.updateOne({
53
+ moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
54
+ collection: "gate_panels",
55
+ where: {
56
+ guild_id: input.guildId,
57
+ panel_id: recordString(claimed.panel_id),
58
+ },
59
+ expected: { field: "revision", value: recordNumber(claimed.revision) },
60
+ values,
61
+ audit: {
62
+ guildId: input.guildId,
63
+ actorUserId: input.actorUserId,
64
+ action: `account_gate.panel_${input.action}`,
65
+ source: "dashboard",
66
+ correlationId: input.correlationId,
67
+ idempotencyKey: `account-gate-panel-${input.action}:${recordString(claimed.panel_id)}:${revision}`,
68
+ targetType: "account_gate_panel",
69
+ targetId: recordString(claimed.panel_id),
70
+ metadata: {
71
+ revision,
72
+ channelId: input.channelId,
73
+ messageId: input.messageId,
74
+ },
75
+ },
76
+ });
77
+ return result.updated ? { ...claimed, ...values } : null;
78
+ }
79
+ function recordString(value) {
80
+ if (typeof value !== "string")
81
+ throw new Error("Access Control panel record is invalid");
82
+ return value;
83
+ }
84
+ function recordNumber(value) {
85
+ if (typeof value !== "number" || !Number.isSafeInteger(value))
86
+ throw new Error("Access Control panel record is invalid");
87
+ return value;
88
+ }
89
+ //# sourceMappingURL=panel-mutations.js.map
@@ -0,0 +1,8 @@
1
+ import type { ManagedResourceContext, ManagedResourceDetail } from "@helyx/sdk";
2
+ import { parseAccountGateConfiguration } from "./configuration.js";
3
+ import type { GatePanelAccessType, PanelPassword } from "./panel-model.js";
4
+ export declare function configuredPanelAccessType(context: ManagedResourceContext): Promise<GatePanelAccessType>;
5
+ export declare function inspectedPanelDetail(context: ManagedResourceContext, row: Readonly<Record<string, unknown>>): Promise<ManagedResourceDetail>;
6
+ export declare function panelPasswordForCreate(configuration: ReturnType<typeof parseAccountGateConfiguration>, accessType: GatePanelAccessType, value: Readonly<Record<string, unknown>>): Promise<PanelPassword | null>;
7
+ export declare function panelPasswordForUpdate(context: ManagedResourceContext, current: Readonly<Record<string, unknown>>, value: Readonly<Record<string, unknown>>): Promise<PanelPassword | null>;
8
+ //# sourceMappingURL=panel-presentation.d.ts.map
@@ -0,0 +1,67 @@
1
+ import { DashboardActionValidationError, HELYX_SERVICE_NAMES, } from "@helyx/sdk";
2
+ import { parseAccountGateConfiguration } from "./configuration.js";
3
+ import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
4
+ import { panelAccessType, panelDetail, panelPasswordFromRow, panelString, replacementPhrase, } from "./panel-model.js";
5
+ import { hashPasswordEntryPhrase } from "./password-entry.js";
6
+ export async function configuredPanelAccessType(context) {
7
+ const configured = await context.services
8
+ .get(HELYX_SERVICE_NAMES.configuration)
9
+ .get(context.guildId, ACCOUNT_GATE_IDENTITY.moduleId);
10
+ if (!configured)
11
+ return "challenges";
12
+ return parseAccountGateConfiguration(configured.value).accessType;
13
+ }
14
+ export async function inspectedPanelDetail(context, row) {
15
+ const inspected = await context.services
16
+ .get(HELYX_SERVICE_NAMES.discordResources)
17
+ .inspectMessage({
18
+ guildId: context.guildId,
19
+ channelId: panelString(row.channel_id),
20
+ messageId: panelString(row.message_id),
21
+ });
22
+ return panelDetail(row, row.status === "posted" &&
23
+ inspected.exists &&
24
+ inspected.guildMatches &&
25
+ inspected.channelMatches
26
+ ? "posted"
27
+ : "missing");
28
+ }
29
+ export async function panelPasswordForCreate(configuration, accessType, value) {
30
+ if (accessType !== "password")
31
+ return null;
32
+ const phrase = replacementPhrase(value, true);
33
+ return {
34
+ encodedHash: await hashPasswordEntryPhrase(phrase, {
35
+ ignoreCase: configuration.passwordIgnoreCase,
36
+ trimWhitespace: configuration.passwordTrimWhitespace,
37
+ }),
38
+ ignoreCase: configuration.passwordIgnoreCase,
39
+ trimWhitespace: configuration.passwordTrimWhitespace,
40
+ };
41
+ }
42
+ export async function panelPasswordForUpdate(context, current, value) {
43
+ if (panelAccessType(current) !== "password")
44
+ return null;
45
+ const existing = panelPasswordFromRow(current);
46
+ const phrase = replacementPhrase(value, false);
47
+ if (!phrase) {
48
+ if (existing)
49
+ return existing;
50
+ throw new DashboardActionValidationError("Enter a password containing 4 to 200 characters before publishing this panel.");
51
+ }
52
+ const configured = await context.services
53
+ .get(HELYX_SERVICE_NAMES.configuration)
54
+ .get(context.guildId, ACCOUNT_GATE_IDENTITY.moduleId);
55
+ if (!configured)
56
+ throw new DashboardActionValidationError("Save Access Control settings before changing this password.");
57
+ const configuration = parseAccountGateConfiguration(configured.value);
58
+ return {
59
+ encodedHash: await hashPasswordEntryPhrase(phrase, {
60
+ ignoreCase: configuration.passwordIgnoreCase,
61
+ trimWhitespace: configuration.passwordTrimWhitespace,
62
+ }),
63
+ ignoreCase: configuration.passwordIgnoreCase,
64
+ trimWhitespace: configuration.passwordTrimWhitespace,
65
+ };
66
+ }
67
+ //# sourceMappingURL=panel-presentation.js.map
@@ -1,15 +1,4 @@
1
- import type { ModuleManagedResource, ModuleRecordService } from "@helyx/sdk";
2
- type GatePanelAccessType = "challenges" | "password" | "emoji";
3
- interface PanelPassword {
4
- encodedHash: string;
5
- ignoreCase: boolean;
6
- trimWhitespace: boolean;
7
- }
1
+ import type { ModuleManagedResource } from "@helyx/sdk";
2
+ export { getGatePanelRuntime, hasPasswordGatePanel } from "./panel-runtime.js";
8
3
  export declare const gatePanelResource: ModuleManagedResource;
9
- export declare function getGatePanelRuntime(store: ModuleRecordService, guildId: string, panelId: string): Promise<{
10
- accessType: GatePanelAccessType;
11
- password: PanelPassword | null;
12
- } | null>;
13
- export declare function hasPasswordGatePanel(store: ModuleRecordService, guildId: string): Promise<boolean>;
14
- export {};
15
4
  //# sourceMappingURL=panel-resource.d.ts.map