@helyx/module-account-gate 1.0.1
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/LICENSE +725 -0
- package/README.md +90 -0
- package/dist/challenges.d.ts +16 -0
- package/dist/challenges.js +233 -0
- package/dist/configuration.d.ts +41 -0
- package/dist/configuration.js +224 -0
- package/dist/contracts.d.ts +82 -0
- package/dist/contracts.js +56 -0
- package/dist/enforcement.d.ts +19 -0
- package/dist/enforcement.js +110 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +3324 -0
- package/dist/managed-resource-summary.d.ts +3 -0
- package/dist/managed-resource-summary.js +6 -0
- package/dist/member-resource.d.ts +3 -0
- package/dist/member-resource.js +348 -0
- package/dist/panel-resource.d.ts +15 -0
- package/dist/panel-resource.js +527 -0
- package/dist/password-entry.d.ts +8 -0
- package/dist/password-entry.js +59 -0
- package/dist/password-resource.d.ts +8 -0
- package/dist/password-resource.js +208 -0
- package/dist/privacy.d.ts +4 -0
- package/dist/privacy.js +20 -0
- package/dist/public-channel-resource.d.ts +4 -0
- package/dist/public-channel-resource.js +135 -0
- package/dist/question-resource.d.ts +3 -0
- package/dist/question-resource.js +246 -0
- package/dist/records.d.ts +508 -0
- package/dist/records.js +456 -0
- package/dist/workflow.d.ts +43 -0
- package/dist/workflow.js +167 -0
- package/manifest.json +750 -0
- package/migrations/0001_account_gate_foundation.sql +303 -0
- package/migrations/0002_account_gate_panel_message.sql +27 -0
- package/migrations/0003_account_gate_revoked_member_status.sql +17 -0
- package/migrations/0004_account_gate_multiple_panels.sql +23 -0
- package/migrations/0005_account_gate_panel_passwords.sql +45 -0
- package/package.json +54 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
export declare const ACCOUNT_GATE_IDENTITY: {
|
|
2
|
+
readonly packageName: "@helyx/module-account-gate";
|
|
3
|
+
readonly moduleId: "helyx.account-gate";
|
|
4
|
+
readonly name: "Access Control";
|
|
5
|
+
readonly version: "1.0.0";
|
|
6
|
+
readonly releaseStatus: "testing";
|
|
7
|
+
readonly category: "safety";
|
|
8
|
+
readonly icon: "shield-check";
|
|
9
|
+
};
|
|
10
|
+
export declare const ACCOUNT_GATE_SESSION_PURPOSE = "helyx.account-gate.check";
|
|
11
|
+
export declare const ACCOUNT_GATE_COMPONENTS: {
|
|
12
|
+
readonly start: "helyx.account-gate.start";
|
|
13
|
+
readonly startPrefix: "helyx.account-gate.start:";
|
|
14
|
+
readonly rerollPrefix: "helyx.account-gate.reroll:";
|
|
15
|
+
readonly recallOpenPrefix: "helyx.account-gate.recall.open:";
|
|
16
|
+
readonly recallSubmitPrefix: "helyx.account-gate.recall.submit:";
|
|
17
|
+
readonly answerPrefix: "helyx.account-gate.answer:";
|
|
18
|
+
readonly sequencePrefix: "helyx.account-gate.sequence:";
|
|
19
|
+
readonly reroll: "helyx.account-gate.reroll";
|
|
20
|
+
readonly recallOpen: "helyx.account-gate.recall.open";
|
|
21
|
+
readonly recallSubmit: "helyx.account-gate.recall.submit";
|
|
22
|
+
readonly answers: readonly ["helyx.account-gate.answer.0", "helyx.account-gate.answer.1", "helyx.account-gate.answer.2", "helyx.account-gate.answer.3"];
|
|
23
|
+
readonly sequence: readonly ["helyx.account-gate.sequence.red", "helyx.account-gate.sequence.blue", "helyx.account-gate.sequence.green"];
|
|
24
|
+
readonly review: {
|
|
25
|
+
readonly approve: "helyx.account-gate.review.approve";
|
|
26
|
+
readonly retry: "helyx.account-gate.review.retry";
|
|
27
|
+
readonly kick: "helyx.account-gate.review.kick";
|
|
28
|
+
readonly dismiss: "helyx.account-gate.review.dismiss";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export declare const GENERATED_CHALLENGE_TYPES: readonly ["quick_sum", "button_sequence", "number_order", "symbol_match", "modal_recall", "server_question"];
|
|
32
|
+
export declare const STATIC_CHALLENGE_TYPES: readonly ["password_entry", "emoji_entry"];
|
|
33
|
+
export declare const CHALLENGE_TYPES: readonly ["quick_sum", "button_sequence", "number_order", "symbol_match", "modal_recall", "server_question", "password_entry", "emoji_entry"];
|
|
34
|
+
export type GeneratedChallengeType = (typeof GENERATED_CHALLENGE_TYPES)[number];
|
|
35
|
+
export type StaticChallengeType = (typeof STATIC_CHALLENGE_TYPES)[number];
|
|
36
|
+
export type ChallengeType = (typeof CHALLENGE_TYPES)[number];
|
|
37
|
+
export type Challenge = {
|
|
38
|
+
type: "quick_sum" | "symbol_match" | "server_question";
|
|
39
|
+
prompt: string;
|
|
40
|
+
options: readonly string[];
|
|
41
|
+
expectedOption: number;
|
|
42
|
+
} | {
|
|
43
|
+
type: "button_sequence";
|
|
44
|
+
prompt: string;
|
|
45
|
+
expectedSequence: readonly ("red" | "blue" | "green")[];
|
|
46
|
+
progress: number;
|
|
47
|
+
} | {
|
|
48
|
+
type: "number_order";
|
|
49
|
+
prompt: string;
|
|
50
|
+
options: readonly string[];
|
|
51
|
+
expectedSequence: readonly number[];
|
|
52
|
+
progress: number;
|
|
53
|
+
} | {
|
|
54
|
+
type: "modal_recall";
|
|
55
|
+
prompt: string;
|
|
56
|
+
expectedText: string;
|
|
57
|
+
} | {
|
|
58
|
+
type: "password_entry" | "emoji_entry";
|
|
59
|
+
prompt: string;
|
|
60
|
+
};
|
|
61
|
+
export interface AccountGateSessionState extends Record<string, unknown> {
|
|
62
|
+
schemaVersion: 1;
|
|
63
|
+
status: "active" | "passed" | "failed" | "cancelled";
|
|
64
|
+
chain: readonly Challenge[];
|
|
65
|
+
step: number;
|
|
66
|
+
currentStep: number;
|
|
67
|
+
accessibilityRerollUsed: boolean;
|
|
68
|
+
joinGeneration: number;
|
|
69
|
+
panelId?: string;
|
|
70
|
+
startedAt: string;
|
|
71
|
+
}
|
|
72
|
+
export interface ServerQuestion {
|
|
73
|
+
id: string;
|
|
74
|
+
question: string;
|
|
75
|
+
answers: readonly string[];
|
|
76
|
+
correctOption: number;
|
|
77
|
+
}
|
|
78
|
+
export interface RandomSource {
|
|
79
|
+
integer(maximumExclusive: number, context: string): number;
|
|
80
|
+
}
|
|
81
|
+
export type FailureAction = "restricted" | "review" | "kick";
|
|
82
|
+
//# sourceMappingURL=contracts.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export const ACCOUNT_GATE_IDENTITY = {
|
|
2
|
+
packageName: "@helyx/module-account-gate",
|
|
3
|
+
moduleId: "helyx.account-gate",
|
|
4
|
+
name: "Access Control",
|
|
5
|
+
version: "1.0.0",
|
|
6
|
+
releaseStatus: "testing",
|
|
7
|
+
category: "safety",
|
|
8
|
+
icon: "shield-check",
|
|
9
|
+
};
|
|
10
|
+
export const ACCOUNT_GATE_SESSION_PURPOSE = "helyx.account-gate.check";
|
|
11
|
+
export const ACCOUNT_GATE_COMPONENTS = {
|
|
12
|
+
start: "helyx.account-gate.start",
|
|
13
|
+
startPrefix: "helyx.account-gate.start:",
|
|
14
|
+
rerollPrefix: "helyx.account-gate.reroll:",
|
|
15
|
+
recallOpenPrefix: "helyx.account-gate.recall.open:",
|
|
16
|
+
recallSubmitPrefix: "helyx.account-gate.recall.submit:",
|
|
17
|
+
answerPrefix: "helyx.account-gate.answer:",
|
|
18
|
+
sequencePrefix: "helyx.account-gate.sequence:",
|
|
19
|
+
reroll: "helyx.account-gate.reroll",
|
|
20
|
+
recallOpen: "helyx.account-gate.recall.open",
|
|
21
|
+
recallSubmit: "helyx.account-gate.recall.submit",
|
|
22
|
+
answers: [
|
|
23
|
+
"helyx.account-gate.answer.0",
|
|
24
|
+
"helyx.account-gate.answer.1",
|
|
25
|
+
"helyx.account-gate.answer.2",
|
|
26
|
+
"helyx.account-gate.answer.3",
|
|
27
|
+
],
|
|
28
|
+
sequence: [
|
|
29
|
+
"helyx.account-gate.sequence.red",
|
|
30
|
+
"helyx.account-gate.sequence.blue",
|
|
31
|
+
"helyx.account-gate.sequence.green",
|
|
32
|
+
],
|
|
33
|
+
review: {
|
|
34
|
+
approve: "helyx.account-gate.review.approve",
|
|
35
|
+
retry: "helyx.account-gate.review.retry",
|
|
36
|
+
kick: "helyx.account-gate.review.kick",
|
|
37
|
+
dismiss: "helyx.account-gate.review.dismiss",
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export const GENERATED_CHALLENGE_TYPES = [
|
|
41
|
+
"quick_sum",
|
|
42
|
+
"button_sequence",
|
|
43
|
+
"number_order",
|
|
44
|
+
"symbol_match",
|
|
45
|
+
"modal_recall",
|
|
46
|
+
"server_question",
|
|
47
|
+
];
|
|
48
|
+
export const STATIC_CHALLENGE_TYPES = [
|
|
49
|
+
"password_entry",
|
|
50
|
+
"emoji_entry",
|
|
51
|
+
];
|
|
52
|
+
export const CHALLENGE_TYPES = [
|
|
53
|
+
...GENERATED_CHALLENGE_TYPES,
|
|
54
|
+
...STATIC_CHALLENGE_TYPES,
|
|
55
|
+
];
|
|
56
|
+
//# sourceMappingURL=contracts.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { DiscordResourceInspectionService, MemberRoleService } from "@helyx/sdk";
|
|
2
|
+
export interface RoleTransitionResult {
|
|
3
|
+
outcome: "completed" | "partial" | "blocked" | "stale";
|
|
4
|
+
accessRoleAdded: boolean;
|
|
5
|
+
removedRoleIds: readonly string[];
|
|
6
|
+
pendingRoleIds: readonly string[];
|
|
7
|
+
safeCode?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function applyRoleTransition(input: {
|
|
10
|
+
guildId: string;
|
|
11
|
+
memberUserId: string;
|
|
12
|
+
verifiedRoleId: string;
|
|
13
|
+
temporaryRoleIds: readonly string[];
|
|
14
|
+
resources: DiscordResourceInspectionService;
|
|
15
|
+
roles: MemberRoleService;
|
|
16
|
+
revalidate: () => Promise<boolean>;
|
|
17
|
+
}): Promise<RoleTransitionResult>;
|
|
18
|
+
export declare function neutralKickDirectMessage(serverName: string): string;
|
|
19
|
+
//# sourceMappingURL=enforcement.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
|
|
2
|
+
export async function applyRoleTransition(input) {
|
|
3
|
+
const accessRole = await input.resources.inspectRole({
|
|
4
|
+
guildId: input.guildId,
|
|
5
|
+
roleId: input.verifiedRoleId,
|
|
6
|
+
});
|
|
7
|
+
if (!accessRole.exists || !accessRole.assignable) {
|
|
8
|
+
return {
|
|
9
|
+
outcome: "blocked",
|
|
10
|
+
accessRoleAdded: false,
|
|
11
|
+
removedRoleIds: [],
|
|
12
|
+
pendingRoleIds: input.temporaryRoleIds,
|
|
13
|
+
safeCode: `access_role_${accessRole.hierarchy}`,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
let currentRoles = await input.roles.getCurrentRoleIds({
|
|
17
|
+
guildId: input.guildId,
|
|
18
|
+
memberUserId: input.memberUserId,
|
|
19
|
+
});
|
|
20
|
+
if (!(await input.revalidate()))
|
|
21
|
+
return staleResult(input.temporaryRoleIds, false);
|
|
22
|
+
const accessRoleAdded = !currentRoles.includes(input.verifiedRoleId);
|
|
23
|
+
if (accessRoleAdded) {
|
|
24
|
+
await input.roles.addApprovedRole({
|
|
25
|
+
guildId: input.guildId,
|
|
26
|
+
memberUserId: input.memberUserId,
|
|
27
|
+
roleId: input.verifiedRoleId,
|
|
28
|
+
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
29
|
+
reason: "Access Control completed",
|
|
30
|
+
});
|
|
31
|
+
if (!(await input.revalidate())) {
|
|
32
|
+
await compensateAccessRole(input);
|
|
33
|
+
return staleResult(input.temporaryRoleIds, false);
|
|
34
|
+
}
|
|
35
|
+
currentRoles = await input.roles.getCurrentRoleIds({
|
|
36
|
+
guildId: input.guildId,
|
|
37
|
+
memberUserId: input.memberUserId,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const removed = [];
|
|
41
|
+
const pending = [];
|
|
42
|
+
for (const roleId of input.temporaryRoleIds) {
|
|
43
|
+
if (!currentRoles.includes(roleId)) {
|
|
44
|
+
removed.push(roleId);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const role = await input.resources.inspectRole({
|
|
48
|
+
guildId: input.guildId,
|
|
49
|
+
roleId,
|
|
50
|
+
});
|
|
51
|
+
if (!role.exists) {
|
|
52
|
+
removed.push(roleId);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (!role.assignable) {
|
|
56
|
+
pending.push(roleId);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (!(await input.revalidate())) {
|
|
60
|
+
if (accessRoleAdded)
|
|
61
|
+
await compensateAccessRole(input);
|
|
62
|
+
return staleResult(input.temporaryRoleIds.filter((candidate) => !removed.includes(candidate)), false);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
await input.roles.removeApprovedRole({
|
|
66
|
+
guildId: input.guildId,
|
|
67
|
+
memberUserId: input.memberUserId,
|
|
68
|
+
roleId,
|
|
69
|
+
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
70
|
+
reason: "Access Control completed",
|
|
71
|
+
});
|
|
72
|
+
removed.push(roleId);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
pending.push(roleId);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
outcome: pending.length > 0 ? "partial" : "completed",
|
|
80
|
+
accessRoleAdded,
|
|
81
|
+
removedRoleIds: removed,
|
|
82
|
+
pendingRoleIds: pending,
|
|
83
|
+
...(pending.length > 0
|
|
84
|
+
? { safeCode: "temporary_role_removal_failed" }
|
|
85
|
+
: {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
async function compensateAccessRole(input) {
|
|
89
|
+
await input.roles.removeApprovedRole({
|
|
90
|
+
guildId: input.guildId,
|
|
91
|
+
memberUserId: input.memberUserId,
|
|
92
|
+
roleId: input.verifiedRoleId,
|
|
93
|
+
moduleId: ACCOUNT_GATE_IDENTITY.moduleId,
|
|
94
|
+
reason: "Access Control stale-generation compensation",
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function staleResult(pendingRoleIds, accessRoleAdded) {
|
|
98
|
+
return {
|
|
99
|
+
outcome: "stale",
|
|
100
|
+
accessRoleAdded,
|
|
101
|
+
removedRoleIds: [],
|
|
102
|
+
pendingRoleIds,
|
|
103
|
+
safeCode: "join_generation_stale",
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export function neutralKickDirectMessage(serverName) {
|
|
107
|
+
const safeServerName = serverName.trim().slice(0, 100);
|
|
108
|
+
return `You were removed from ${safeServerName || "a Discord server"} because its Access Control check was not completed. You may contact the server team if you believe this was a mistake.`;
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=enforcement.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { MessageResponse, ModuleRecordService, ModuleRecordValue, ServiceAccess } from "@helyx/sdk";
|
|
2
|
+
import { generateChallengeChain, validateServerQuestion } from "./challenges.js";
|
|
3
|
+
import { ACCOUNT_GATE_IDENTITY } from "./contracts.js";
|
|
4
|
+
import type { AccountGateSessionState, Challenge } from "./contracts.js";
|
|
5
|
+
import { parseAccountGateConfiguration, validateRuntimeCapabilities } from "./configuration.js";
|
|
6
|
+
import type { AccountGateConfiguration } from "./configuration.js";
|
|
7
|
+
import { accountGateRecords } from "./records.js";
|
|
8
|
+
import { applyChallengeAction, createAccountGateSessionState } from "./workflow.js";
|
|
9
|
+
export declare const accountGateModule: import("@helyx/sdk").ModuleDefinition;
|
|
10
|
+
export default accountGateModule;
|
|
11
|
+
export { ACCOUNT_GATE_IDENTITY, accountGateRecords, applyChallengeAction, createAccountGateSessionState, generateChallengeChain, parseAccountGateConfiguration, validateRuntimeCapabilities, validateServerQuestion, };
|
|
12
|
+
export declare function isAccountTooYoung(accountCreatedAt: Date, joinedAt: Date, minimumAccountAgeDays: number): boolean;
|
|
13
|
+
export declare function hasPreviousSuccess(value: unknown): boolean;
|
|
14
|
+
export declare function resumeRevokedMember(services: ServiceAccess, guildId: string, memberUserId: string, summary: Readonly<Record<string, ModuleRecordValue>>, configuration: AccountGateConfiguration, correlationId: string, now?: Date): Promise<Readonly<Record<string, ModuleRecordValue>> | null>;
|
|
15
|
+
export declare function recordAccountGateAttempt(records: ModuleRecordService, guildId: string, memberUserId: string, sessionId: string, state: AccountGateSessionState, challengeType: Challenge["type"], outcome: "passed" | "failed", now?: Date): Promise<void>;
|
|
16
|
+
export declare function ensureTerminalFinalization(services: ServiceAccess, guildId: string, memberUserId: string, sessionId: string, state: AccountGateSessionState, outcome: "passed" | "failed"): Promise<boolean>;
|
|
17
|
+
export declare function validateAccountGateSetup(services: ServiceAccess, guildId: string, configuration: AccountGateConfiguration, throwOnError?: boolean, allowMissingPanel?: boolean): Promise<readonly string[]>;
|
|
18
|
+
interface ComponentBinding {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
version: bigint;
|
|
21
|
+
step: number;
|
|
22
|
+
value?: string;
|
|
23
|
+
}
|
|
24
|
+
export declare function boundComponentId(prefix: string, binding: ComponentBinding): string;
|
|
25
|
+
export declare function parseComponentBinding(customId: string | undefined, prefix: string): ComponentBinding | null;
|
|
26
|
+
export declare function staticResumeMessage(guildId: string, gateChannelId: string): MessageResponse;
|
|
27
|
+
//# sourceMappingURL=index.d.ts.map
|