diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 8863d64e..04320402 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -2,44 +2,36 @@ using BotSharp.Abstraction.Files; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Models; using BotSharp.Plugin.Twilio.Services; -using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using System.IdentityModel.Tokens.Jwt; namespace BotSharp.Plugin.Twilio.Controllers; -[AllowAnonymous] -[Route("twilio/voice")] public class TwilioVoiceController : TwilioController { private readonly TwilioSetting _settings; private readonly IServiceProvider _services; + private readonly IHttpContextAccessor _context; - public TwilioVoiceController(TwilioSetting settings, IServiceProvider services) + public TwilioVoiceController(TwilioSetting settings, IServiceProvider services, IHttpContextAccessor context) { _settings = settings; _services = services; + _context = context; } - [Authorize] - [HttpGet("/twilio/token")] - public Token GetAccessToken() - { - var twilio = _services.GetRequiredService(); - var accessToken = twilio.GetAccessToken(); - var jwt = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); - return new Token - { - AccessToken = accessToken, - ExpireTime = jwt.Payload.Exp.Value, - TokenType = "Bearer", - Scope = "api" - }; - } - - [HttpPost("welcome")] + /// + /// https://github.com/twilio-labs/twilio-aspnet?tab=readme-ov-file#validate-twilio-http-requests + /// + /// + /// + /// + /// + [ValidateRequest] + [HttpPost("twilio/voice/welcome")] public TwiMLResult InitiateConversation(VoiceRequest request, [FromQuery] string states) { + _context.HttpContext.Request.Headers["auth-schema"] = "twilio"; if (request?.CallSid == null) throw new ArgumentNullException(nameof(VoiceRequest.CallSid)); string conversationId = $"TwilioVoice_{request.CallSid}"; var twilio = _services.GetRequiredService(); @@ -48,9 +40,11 @@ public class TwilioVoiceController : TwilioController return TwiML(response); } - [HttpPost("{conversationId}/receive/{seqNum}")] + [ValidateRequest] + [HttpPost("twilio/voice/{conversationId}/receive/{seqNum}")] public async Task ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request) { + _context.HttpContext.Request.Headers["auth-schema"] = "twilio"; var twilio = _services.GetRequiredService(); var messageQueue = _services.GetRequiredService(); var sessionManager = _services.GetRequiredService(); @@ -88,14 +82,18 @@ public class TwilioVoiceController : TwilioController } } await messageQueue.EnqueueAsync(callerMessage); - response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1); + + int audioIndex = Random.Shared.Next(1, 5); + response = twilio.ReturnInstructions($"twilio/hold-on-{audioIndex}.mp3", $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1); } return TwiML(response); } - [HttpPost("{conversationId}/reply/{seqNum}")] + [ValidateRequest] + [HttpPost("twilio/voice/{conversationId}/reply/{seqNum}")] public async Task ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request) { + _context.HttpContext.Request.Headers["auth-schema"] = "twilio"; var nextSeqNum = seqNum + 1; var sessionManager = _services.GetRequiredService(); var twilio = _services.GetRequiredService(); @@ -151,9 +149,11 @@ public class TwilioVoiceController : TwilioController return TwiML(response); } - [HttpGet("speeches/{conversationId}/{fileName}")] + [ValidateRequest] + [HttpGet("twilio/voice/speeches/{conversationId}/{fileName}")] public async Task RetrieveSpeechFile([FromRoute] string conversationId, [FromRoute] string fileName) { + _context.HttpContext.Request.Headers["auth-schema"] = "twilio"; var fileService = _services.GetRequiredService(); var data = await fileService.RetrieveSpeechFileAsync(conversationId, fileName); var result = new FileContentResult(data.ToArray(), "audio/mpeg"); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index a4e9d4ab..718741a1 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -81,6 +81,11 @@ public class TwilioService if (!string.IsNullOrEmpty(speechPath)) { gather.Play(new Uri($"{_settings.CallbackHost}/{speechPath}")); + if (speechPath.Contains("hold-on-")) + { + int audioIndex = Random.Shared.Next(1, 4); + gather.Play(new Uri($"{_settings.CallbackHost}/twilio/typing-{audioIndex}.mp3")); + } } response.Append(gather); return response; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs index 1247d0d4..fa971a7c 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs @@ -23,6 +23,6 @@ public class TwilioPlugin : IBotSharpPlugin services.AddSingleton(sessionManager); services.AddSingleton(); services.AddHostedService(); - + services.AddTwilioRequestValidation(); } }