feat(#709): add configured Discord interaction binding #730
@@ -639,9 +639,36 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
|
||||
* Creates it if absent — safe to call concurrently since a duplicate insert
|
||||
* would fail on the PK constraint and be caught here.
|
||||
*/
|
||||
@SubscribeMessage('discord:approve')
|
||||
async handleDiscordApproval(
|
||||
@ConnectedSocket() client: Socket,
|
||||
@MessageBody() envelope: DiscordIngressEnvelope,
|
||||
): Promise<void> {
|
||||
if (!client.data.discordService) return;
|
||||
const ingress = this.resolveDiscordIngress(client, envelope, 'approve');
|
||||
const command = ingress?.content.match(/^\/approve\s+([a-z][\w-]*)$/i)?.[1];
|
||||
const serviceUserId = process.env['DISCORD_SERVICE_USER_ID'];
|
||||
if (!ingress || !command || !serviceUserId) return;
|
||||
const approval = await this.commandExecutor.createApproval(
|
||||
{ command, conversationId: ingress.conversationId },
|
||||
{
|
||||
userId: serviceUserId,
|
||||
tenantId: process.env['DISCORD_SERVICE_TENANT_ID'] ?? serviceUserId,
|
||||
},
|
||||
);
|
||||
client.emit('discord:approval', {
|
||||
correlationId: ingress.correlationId,
|
||||
command,
|
||||
success: approval !== null,
|
||||
approvalId: approval?.approvalId,
|
||||
expiresAt: approval?.expiresAt,
|
||||
});
|
||||
}
|
||||
|
||||
private resolveDiscordIngress(
|
||||
client: Socket,
|
||||
envelope: DiscordIngressEnvelope,
|
||||
operation: 'send' | 'approve' = 'send',
|
||||
): DiscordIngressPayload | null {
|
||||
const payload = verifyDiscordIngressEnvelope(
|
||||
envelope,
|
||||
@@ -662,7 +689,7 @@ export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewa
|
||||
payload.guildId,
|
||||
payload.channelId,
|
||||
payload.userId,
|
||||
'send',
|
||||
operation,
|
||||
);
|
||||
if (!binding) {
|
||||
this.logger.warn(`Rejected unpaired Discord ingress from ${client.id}`);
|
||||
|
||||
@@ -327,15 +327,18 @@ export class DiscordPlugin {
|
||||
},
|
||||
this.config.serviceToken,
|
||||
);
|
||||
this.socket.emit('message', envelope);
|
||||
this.socket.emit(content.startsWith('/approve ') ? 'discord:approve' : 'message', envelope);
|
||||
}
|
||||
|
||||
private isAllowedMessage(message: DiscordMessage): boolean {
|
||||
const guildId = message.guildId;
|
||||
const parentChannelId = 'parentId' in message.channel ? message.channel.parentId : null;
|
||||
// Threads inherit their authorization boundary from their configured parent.
|
||||
const authorizationChannelId = parentChannelId ?? message.channelId;
|
||||
return (
|
||||
guildId !== null &&
|
||||
includesId(this.config.allowedGuildIds, guildId) &&
|
||||
includesId(this.config.allowedChannelIds, message.channelId) &&
|
||||
includesId(this.config.allowedChannelIds, authorizationChannelId) &&
|
||||
includesId(this.config.allowedUserIds, message.author.id)
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user