Add verification success endpoint and improve trust level system

Adds a POST endpoint `/api/verify-success` to `webServer.js` for handling verification callbacks from aethex.dev, including secret validation and role assignment. Updates `federationProtection.js` and `federation.js` to integrate trust level calculations and benefits. Introduces a new `trustLevels.js` utility file to manage trust level definitions, progression, and related logic.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 98ebd9a6-5755-4d7f-ae64-9ed93f1eae4f
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/3tJ1Z1J
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
sirpiglr 2025-12-13 10:11:52 +00:00
parent 73ff5bcce3
commit a1912fc48a
4 changed files with 401 additions and 12 deletions

View file

@ -1,5 +1,6 @@
const { SlashCommandBuilder, EmbedBuilder, PermissionFlagsBits, ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const { getServerMode, EMBED_COLORS } = require('../utils/modeHelper');
const { getTrustLevelInfo, getProgressToNextLevel, TRUST_LEVELS, calculateReputationChange } = require('../utils/trustLevels');
module.exports = {
data: new SlashCommandBuilder()
@ -60,6 +61,7 @@ module.exports = {
).setRequired(true))
.addStringOption(opt => opt.setName('description').setDescription('Brief description of your server').setRequired(true)))
.addSubcommand(sub => sub.setName('status').setDescription('Check your server\'s federation status'))
.addSubcommand(sub => sub.setName('stats').setDescription('View detailed trust level stats and progression'))
.addSubcommand(sub => sub.setName('treaty').setDescription('View the Federation Treaty'))
)
.addSubcommandGroup(group =>
@ -270,6 +272,25 @@ async function handleBans(interaction, supabase, client, subcommand) {
return interaction.reply({ content: 'Failed to add ban.', ephemeral: true });
}
const reputationGain = calculateReputationChange('ban_report', true);
const { data: currentServer } = await supabase
.from('federation_servers')
.select('reports_submitted, reputation_score')
.eq('guild_id', interaction.guildId)
.maybeSingle();
if (currentServer) {
await supabase
.from('federation_servers')
.update({
reports_submitted: (currentServer.reports_submitted || 0) + 1,
reputation_score: (currentServer.reputation_score || 0) + reputationGain,
last_activity: new Date().toISOString()
})
.eq('guild_id', interaction.guildId);
}
const severityColors = { low: 0xffff00, medium: 0xff9900, high: 0xff3300, critical: 0xff0000 };
const severityEmojis = { low: '⚠️', medium: '🔶', high: '🔴', critical: '☠️' };
@ -280,7 +301,8 @@ async function handleBans(interaction, supabase, client, subcommand) {
.addFields(
{ name: 'User', value: `${user.tag}\n\`${user.id}\``, inline: true },
{ name: 'Severity', value: severity.toUpperCase(), inline: true },
{ name: 'Reason', value: reason }
{ name: 'Reason', value: reason },
{ name: 'Reputation', value: `+${reputationGain} points`, inline: true }
)
.setFooter({ text: `Added by ${interaction.user.tag}` })
.setTimestamp();
@ -553,14 +575,20 @@ async function handleMembership(interaction, supabase, client, subcommand) {
.maybeSingle();
if (server) {
const trustLevel = server.trust_level || 'bronze';
const trustInfo = getTrustLevelInfo(trustLevel);
const embed = new EmbedBuilder()
.setColor(0x00ff00)
.setTitle('Federation Member')
.setColor(trustInfo.color)
.setTitle(`${trustInfo.emoji} Federation Member - ${trustInfo.name}`)
.addFields(
{ name: 'Status', value: server.status.toUpperCase(), inline: true },
{ name: 'Tier', value: server.tier?.toUpperCase() || 'FREE', inline: true },
{ name: 'Trust Level', value: `${trustInfo.emoji} ${trustInfo.name}`, inline: true },
{ name: 'Reputation', value: `${server.reputation_score || 0} points`, inline: true },
{ name: 'Joined', value: new Date(server.joined_at).toLocaleDateString(), inline: true }
)
.setFooter({ text: 'Use /federation membership stats for detailed progression' })
.setTimestamp();
return interaction.reply({ embeds: [embed] });
}
@ -598,6 +626,78 @@ async function handleMembership(interaction, supabase, client, subcommand) {
await interaction.reply({ embeds: [embed] });
}
if (subcommand === 'stats') {
const { data: server } = await supabase
.from('federation_servers')
.select('*')
.eq('guild_id', interaction.guildId)
.maybeSingle();
if (!server) {
return interaction.reply({
content: 'This server is not a federation member. Use `/federation membership apply` to join!',
ephemeral: true
});
}
const trustLevel = server.trust_level || 'bronze';
const trustInfo = getTrustLevelInfo(trustLevel);
const progression = getProgressToNextLevel(server);
const embed = new EmbedBuilder()
.setColor(trustInfo.color)
.setTitle(`${trustInfo.emoji} Trust Level: ${trustInfo.name}`)
.setDescription('Your server\'s federation standing and progression.')
.addFields(
{ name: 'Current Benefits', value: [
`**Auto-Action:** ${trustInfo.benefits.autoAction === 'ban' ? 'Auto-ban threats' : trustInfo.benefits.autoAction === 'kick' ? 'Auto-kick threats' : 'Alert only'}`,
`**Severity Threshold:** ${trustInfo.benefits.severityThreshold.charAt(0).toUpperCase() + trustInfo.benefits.severityThreshold.slice(1)}+`,
`**Featured Eligible:** ${trustInfo.benefits.featuredEligible ? 'Yes' : 'No'}`,
`**Partnership Limit:** ${trustInfo.benefits.partnershipLimit} servers`
].join('\n') }
);
if (progression.nextLevel) {
const progressBars = [];
const makeBar = (pct) => {
const filled = Math.floor(pct / 10);
return '█'.repeat(filled) + '░'.repeat(10 - filled);
};
progressBars.push(`**Members:** ${server.member_count || 0}/${progression.progress.members.required} ${progression.progress.members.met ? '✅' : ''}\n\`${makeBar(progression.progress.members.percentage)}\` ${progression.progress.members.percentage}%`);
progressBars.push(`**Days in Federation:** ${progression.progress.days.current}/${progression.progress.days.required} ${progression.progress.days.met ? '✅' : ''}\n\`${makeBar(progression.progress.days.percentage)}\` ${progression.progress.days.percentage}%`);
progressBars.push(`**Reputation:** ${server.reputation_score || 0}/${progression.progress.reputation.required} ${progression.progress.reputation.met ? '✅' : ''}\n\`${makeBar(progression.progress.reputation.percentage)}\` ${progression.progress.reputation.percentage}%`);
embed.addFields({
name: `Progress to ${progression.nextLevelInfo.emoji} ${progression.nextLevelInfo.name}`,
value: progressBars.join('\n\n')
});
if (progression.allMet) {
embed.addFields({
name: '🎉 Ready for Upgrade!',
value: 'Your server meets all requirements for the next trust level! Upgrades are processed weekly.'
});
}
} else {
embed.addFields({
name: '👑 Maximum Level',
value: 'Your server has reached the highest trust level!'
});
}
embed.addFields(
{ name: 'Statistics', value: [
`**Reports Submitted:** ${server.reports_submitted || 0}`,
`**False Positives:** ${server.false_positives || 0}`,
`**Last Activity:** ${server.last_activity ? new Date(server.last_activity).toLocaleDateString() : 'N/A'}`
].join('\n'), inline: true }
);
embed.setTimestamp();
return interaction.reply({ embeds: [embed] });
}
if (subcommand === 'treaty') {
const embed = new EmbedBuilder()
.setColor(0x7c3aed)

View file

@ -1,4 +1,5 @@
const { EmbedBuilder } = require('discord.js');
const { getTrustLevelInfo, shouldAutoAction } = require('../utils/trustLevels');
module.exports = {
name: 'guildMemberAdd',
@ -9,7 +10,7 @@ module.exports = {
try {
const { data: serverConfig } = await supabase
.from('federation_servers')
.select('tier, status')
.select('tier, status, trust_level, reputation_score')
.eq('guild_id', member.guild.id)
.eq('status', 'approved')
.maybeSingle();
@ -25,10 +26,13 @@ module.exports = {
if (!ban) return;
const trustLevel = serverConfig.trust_level || 'bronze';
const isPremium = serverConfig.tier === 'premium';
const isCritical = ban.severity === 'critical';
const trustInfo = getTrustLevelInfo(trustLevel);
if (!isPremium && !isCritical) {
const actionDecision = shouldAutoAction(trustLevel, ban.severity, isPremium);
if (actionDecision.action === 'alert' && ban.severity !== 'critical') {
return;
}
@ -42,6 +46,7 @@ module.exports = {
.addFields(
{ name: 'User', value: `${member.user.tag}\n\`${member.id}\``, inline: true },
{ name: 'Severity', value: ban.severity.toUpperCase(), inline: true },
{ name: 'Trust Level', value: `${trustInfo.emoji} ${trustInfo.name}`, inline: true },
{ name: 'Reason', value: ban.reason || 'No reason provided' }
)
.setFooter({ text: 'AeThex Federation Protection' })
@ -60,12 +65,12 @@ module.exports = {
}
}
if (isCritical) {
if (actionDecision.action === 'ban') {
try {
await member.ban({ reason: `[Federation] Global ban: ${ban.reason}` });
await member.ban({ reason: `[Federation] Global ban (${ban.severity}): ${ban.reason}` });
alertEmbed.setTitle(`${severityEmojis[ban.severity]} Federation Auto-Ban: Critical Threat Removed`);
alertEmbed.addFields({ name: 'Action Taken', value: 'User was automatically banned' });
alertEmbed.setTitle(`${severityEmojis[ban.severity]} Federation Auto-Ban: Threat Removed`);
alertEmbed.addFields({ name: 'Action Taken', value: `User was automatically banned (${trustInfo.name} Protection)` });
await supabase.from('federation_alerts').update({
delivered: true,
@ -77,10 +82,10 @@ module.exports = {
console.error('[Federation] Failed to auto-ban:', banError.message);
alertEmbed.addFields({ name: 'Action Required', value: 'Auto-ban failed. Please ban manually.' });
}
} else if (isPremium) {
} else if (actionDecision.action === 'kick') {
try {
await member.kick(`[Federation] Global ban (${ban.severity} severity): ${ban.reason}`);
alertEmbed.addFields({ name: 'Action Taken', value: 'User was automatically kicked (Premium Protection)' });
alertEmbed.addFields({ name: 'Action Taken', value: `User was automatically kicked (${trustInfo.name} Protection)` });
await supabase.from('federation_alerts').update({
delivered: true,
@ -93,6 +98,11 @@ module.exports = {
}
}
await supabase
.from('federation_servers')
.update({ last_activity: new Date().toISOString() })
.eq('guild_id', member.guild.id);
const owner = await member.guild.fetchOwner().catch(() => null);
if (owner && ban.severity === 'critical') {
try {

View file

@ -422,6 +422,90 @@ function createWebServer(discordClient, supabase, options = {}) {
}
});
// Verification success endpoint - called by aethex.dev when verification completes
app.post('/api/verify-success', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });
}
const webhookSecret = process.env.DISCORD_BOT_WEBHOOK_SECRET;
const providedSecret = req.headers['x-webhook-secret'] || req.body.secret;
if (webhookSecret && providedSecret !== webhookSecret) {
console.log('[Verify-Success] Invalid webhook secret provided');
return res.status(401).json({ error: 'Invalid webhook secret' });
}
const { discord_id, user_id, username } = req.body;
if (!discord_id) {
return res.status(400).json({ error: 'Missing discord_id' });
}
console.log(`[Verify-Success] Received verification for Discord ID: ${discord_id}`);
try {
// Update or create the discord link
if (user_id) {
await supabase.from('discord_links').upsert({
discord_id,
user_id,
username: username || null,
linked_at: new Date().toISOString()
}, { onConflict: 'discord_id' });
}
// Get server configs that have a verified role configured
const { data: configs } = await supabase
.from('server_config')
.select('guild_id, verified_role_id')
.not('verified_role_id', 'is', null);
let rolesAssigned = 0;
const assignedIn = [];
// Assign verified role in all guilds where the user is a member
for (const config of configs || []) {
if (!config.verified_role_id) continue;
try {
const guild = client.guilds.cache.get(config.guild_id);
if (!guild) continue;
const member = await guild.members.fetch(discord_id).catch(() => null);
if (!member) continue;
const role = guild.roles.cache.get(config.verified_role_id);
if (!role) continue;
if (!member.roles.cache.has(role.id)) {
await member.roles.add(role, 'Account verified via aethex.dev');
rolesAssigned++;
assignedIn.push(guild.name);
console.log(`[Verify-Success] Assigned verified role to ${member.user.tag} in ${guild.name}`);
}
} catch (err) {
console.error(`[Verify-Success] Failed to assign role in guild ${config.guild_id}:`, err.message);
}
}
// Clean up any pending verification codes
await supabase.from('discord_verifications').delete().eq('discord_id', discord_id);
res.json({
success: true,
rolesAssigned,
assignedIn,
message: rolesAssigned > 0
? `Verified role assigned in ${rolesAssigned} server(s)`
: 'Verification recorded (user not found in any configured servers)'
});
} catch (error) {
console.error('[Verify-Success] Error:', error);
res.status(500).json({ error: 'Failed to process verification' });
}
});
app.get('/api/stats/:userId/:guildId', async (req, res) => {
if (!supabase) {
return res.status(503).json({ error: 'Database not available' });

View file

@ -0,0 +1,195 @@
const TRUST_LEVELS = {
bronze: {
name: 'Bronze',
emoji: '🥉',
color: 0xcd7f32,
requirements: {
minMembers: 0,
minDays: 0,
minReputationScore: 0
},
benefits: {
autoAction: 'alert',
severityThreshold: 'critical',
directoryListing: true,
featuredEligible: false,
partnershipLimit: 3
}
},
silver: {
name: 'Silver',
emoji: '🥈',
color: 0xc0c0c0,
requirements: {
minMembers: 100,
minDays: 30,
minReputationScore: 50
},
benefits: {
autoAction: 'kick',
severityThreshold: 'medium',
directoryListing: true,
featuredEligible: false,
partnershipLimit: 5
}
},
gold: {
name: 'Gold',
emoji: '🥇',
color: 0xffd700,
requirements: {
minMembers: 500,
minDays: 90,
minReputationScore: 200
},
benefits: {
autoAction: 'ban',
severityThreshold: 'medium',
directoryListing: true,
featuredEligible: true,
partnershipLimit: 10
}
},
platinum: {
name: 'Platinum',
emoji: '💎',
color: 0xe5e4e2,
requirements: {
minMembers: 1000,
minDays: 180,
minReputationScore: 500
},
benefits: {
autoAction: 'ban',
severityThreshold: 'low',
directoryListing: true,
featuredEligible: true,
partnershipLimit: 20
}
}
};
const TRUST_ORDER = ['bronze', 'silver', 'gold', 'platinum'];
function calculateTrustLevel(serverData) {
const memberCount = serverData.member_count || 0;
const joinedAt = new Date(serverData.joined_federation_at || serverData.joined_at);
const daysInFederation = Math.floor((Date.now() - joinedAt.getTime()) / (1000 * 60 * 60 * 24));
const reputationScore = serverData.reputation_score || 0;
let qualifiedLevel = 'bronze';
for (const level of TRUST_ORDER) {
const reqs = TRUST_LEVELS[level].requirements;
if (memberCount >= reqs.minMembers &&
daysInFederation >= reqs.minDays &&
reputationScore >= reqs.minReputationScore) {
qualifiedLevel = level;
} else {
break;
}
}
return qualifiedLevel;
}
function getTrustLevelInfo(level) {
return TRUST_LEVELS[level] || TRUST_LEVELS.bronze;
}
function getNextTrustLevel(currentLevel) {
const currentIndex = TRUST_ORDER.indexOf(currentLevel);
if (currentIndex === -1 || currentIndex >= TRUST_ORDER.length - 1) {
return null;
}
return TRUST_ORDER[currentIndex + 1];
}
function getProgressToNextLevel(serverData) {
const currentLevel = serverData.trust_level || 'bronze';
const nextLevel = getNextTrustLevel(currentLevel);
if (!nextLevel) {
return { nextLevel: null, progress: {}, allMet: true };
}
const reqs = TRUST_LEVELS[nextLevel].requirements;
const memberCount = serverData.member_count || 0;
const joinedAt = new Date(serverData.joined_federation_at || serverData.joined_at);
const daysInFederation = Math.floor((Date.now() - joinedAt.getTime()) / (1000 * 60 * 60 * 24));
const reputationScore = serverData.reputation_score || 0;
const progress = {
members: {
current: memberCount,
required: reqs.minMembers,
met: memberCount >= reqs.minMembers,
percentage: Math.min(100, Math.floor((memberCount / reqs.minMembers) * 100)) || 0
},
days: {
current: daysInFederation,
required: reqs.minDays,
met: daysInFederation >= reqs.minDays,
percentage: Math.min(100, Math.floor((daysInFederation / reqs.minDays) * 100)) || 0
},
reputation: {
current: reputationScore,
required: reqs.minReputationScore,
met: reputationScore >= reqs.minReputationScore,
percentage: Math.min(100, Math.floor((reputationScore / reqs.minReputationScore) * 100)) || 0
}
};
const allMet = progress.members.met && progress.days.met && progress.reputation.met;
return { nextLevel, nextLevelInfo: TRUST_LEVELS[nextLevel], progress, allMet };
}
function shouldAutoAction(trustLevel, banSeverity, isPremium) {
const levelInfo = getTrustLevelInfo(trustLevel);
const severityOrder = ['low', 'medium', 'high', 'critical'];
const banSeverityIndex = severityOrder.indexOf(banSeverity);
const thresholdIndex = severityOrder.indexOf(levelInfo.benefits.severityThreshold);
if (banSeverityIndex < thresholdIndex) {
if (banSeverity === 'critical') {
return { action: 'ban', reason: 'critical_severity' };
}
return { action: 'alert', reason: 'below_threshold' };
}
if (isPremium) {
return { action: levelInfo.benefits.autoAction, reason: 'premium_protection' };
}
if (banSeverity === 'critical') {
return { action: 'ban', reason: 'critical_severity' };
}
return { action: 'alert', reason: 'free_tier' };
}
function calculateReputationChange(action, successful = true) {
const reputationValues = {
ban_report: successful ? 10 : -5,
report_verified: 15,
false_positive: -20,
event_hosted: 25,
partnership_formed: 10,
monthly_activity: 5
};
return reputationValues[action] || 0;
}
module.exports = {
TRUST_LEVELS,
TRUST_ORDER,
calculateTrustLevel,
getTrustLevelInfo,
getNextTrustLevel,
getProgressToNextLevel,
shouldAutoAction,
calculateReputationChange
};