diff --git a/src/WhatsApp/AzureFunctionsWebhook.cs b/src/WhatsApp/AzureFunctionsWebhook.cs index e3f385f..d52bd7b 100644 --- a/src/WhatsApp/AzureFunctionsWebhook.cs +++ b/src/WhatsApp/AzureFunctionsWebhook.cs @@ -44,73 +44,7 @@ public async Task Message([HttpTrigger(AuthorizationLevel.Anonymo // Detect encrypted flow request setup for flows endpoints if (JsonSerializer.Deserialize(json) is { Data.Length: > 0, IV.Length: > 0, Key.Length: > 0 } encrypted) { - if (string.IsNullOrEmpty(metaOptions.Value.PrivateKey)) - return new StatusCodeResult(421); - - var crypto = new FlowCryptography(metaOptions.Value.PrivateKey); - if (!crypto.TryDecrypt(encrypted, out var data) || data is null) - return new StatusCodeResult(421); - - if (data.Data.TryGetProperty("action", out var action) && - action.ValueKind == JsonValueKind.String && - action.GetString() == "ping") - { - // This satisfies the flow publishing requirement that the endpoint is active. - return new OkObjectResult(crypto.Encrypt(data.With( - new { data = new { status = "active" } }))); - } - - if (!data.Data.TryGetProperty("flow_token", out var t) || - t.ValueKind != JsonValueKind.String || t.GetString() is not { Length: > 0 } value || - !FlowToken.TryDecode(value, out var token)) - { - logger.LogWarning("Received flow request without a valid flow_token."); - return new BadRequestObjectResult("Missing or invalid flow_token."); - } - - var node = JsonObject.Create(data.Data); - Debug.Assert(node != null, "Node should not be null after decryption."); - - node.Add("service", token.ServiceId); - node.Add("user", token.UserNumber); - - var flow = JsonSerializer.Deserialize(node, JsonContext.DefaultOptions); - if (flow?.Flow is null) - { - logger.LogWarning("Failed to deserialize flow message from: {Json}", json); - return new BadRequestObjectResult("Invalid flow message format."); - } - - FlowDataResponse? flowResponse = default; - - await foreach (var response in handler.HandleAsync([flow])) - { - if (response is FlowDataResponse fdr) - { - if (flowResponse is not null) - { - logger.LogWarning("At most one flow data response can be provided for {Token}", token.RawToken); - return new ConflictObjectResult("Multiple flow data responses are not allowed."); - } - else - { - flowResponse = fdr; - } - } - } - - if (flowResponse is null) - { - logger.LogWarning("No flow data response provided for {Token}", token.RawToken); - return new NotFoundObjectResult("No flow data response provided."); - } - - return new OkObjectResult(crypto.Encrypt(data.With( - new - { - screen = flowResponse.Screen, - data = flowResponse.Data - }))); + return await ProcessFlowDataAsync(json, encrypted); } if (await WhatsApp.Message.DeserializeAsync(json) is { } message) @@ -161,6 +95,78 @@ public async Task Message([HttpTrigger(AuthorizationLevel.Anonymo return new OkResult(); } + + async Task ProcessFlowDataAsync(string json, EncryptedFlowData encrypted) + { + if (string.IsNullOrEmpty(metaOptions.Value.PrivateKey)) + return new StatusCodeResult(421); + + var crypto = new FlowCryptography(metaOptions.Value.PrivateKey); + if (!crypto.TryDecrypt(encrypted, out var data) || data is null) + return new StatusCodeResult(421); + + if (data.Data.TryGetProperty("action", out var action) && + action.ValueKind == JsonValueKind.String && + action.GetString() == "ping") + { + // This satisfies the flow publishing requirement that the endpoint is active. + return new OkObjectResult(crypto.Encrypt(data.With( + new { data = new { status = "active" } }))); + } + + if (!data.Data.TryGetProperty("flow_token", out var t) || + t.ValueKind != JsonValueKind.String || t.GetString() is not { Length: > 0 } value || + !FlowToken.TryDecode(value, out var token)) + { + logger.LogWarning("Received flow request without a valid flow_token."); + return new BadRequestObjectResult("Missing or invalid flow_token."); + } + + var node = JsonObject.Create(data.Data); + Debug.Assert(node != null, "Node should not be null after decryption."); + + node.Add("service", token.ServiceId); + node.Add("user", token.UserNumber); + + var flow = JsonSerializer.Deserialize(node, JsonContext.DefaultOptions); + if (flow?.Flow is null) + { + logger.LogWarning("Failed to deserialize flow message from: {Json}", json); + return new BadRequestObjectResult("Invalid flow message format."); + } + + FlowDataResponse? flowResponse = default; + + await foreach (var response in handler.HandleAsync([flow])) + { + if (response is FlowDataResponse fdr) + { + if (flowResponse is not null) + { + logger.LogWarning("At most one flow data response can be provided for {Token}", token.RawToken); + return new ConflictObjectResult("Multiple flow data responses are not allowed."); + } + else + { + flowResponse = fdr; + } + } + } + + if (flowResponse is null) + { + logger.LogWarning("No flow data response provided for {Token}", token.RawToken); + return new NotFoundObjectResult("No flow data response provided."); + } + + return new OkObjectResult(crypto.Encrypt(data.With( + new + { + screen = flowResponse.Screen, + data = flowResponse.Data + }))); + } + [Function("whatsapp_register")] public IActionResult Register([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "whatsapp")] HttpRequest req) {