public void Validate_IncorrectSecret_ReturnsInvalid()
        {
            // Arrange
            const string secret    = "wrong-secret";
            const string signature =
                "9wLF3xAfnzyB4ilHecbxOdbf9l+q+KL+NxRZhRxI/LWUFVWNcgV7gJV1lgjVpAaqqY/zBb+hhfpDeFY7aoE+Qg==";

            // Act
            var(_, valid) = WebhookPayloadValidator.Validate(signature, secret, "{}");

            // Assert
            Assert.False(valid);
        }
        public void Validate_CorrectSecret_ReturnsValid()
        {
            // Arrange
            const string secret    = "ded6ca53-64c1-4deb-b684-83eea6f8301d";
            const string signature =
                "9wLF3xAfnzyB4ilHecbxOdbf9l+q+KL+NxRZhRxI/LWUFVWNcgV7gJV1lgjVpAaqqY/zBb+hhfpDeFY7aoE+Qg==";

            // Act
            var(_, valid) = WebhookPayloadValidator.Validate(signature, secret, "{}");

            // Assert
            Assert.True(valid);
        }
        public static async Task <HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Anonymous, "post")] string data,
                                                           [Table("transactions", "AzureWebJobsStorage")] CloudTable table, HttpRequestMessage req, TraceWriter log)
        {
            if (string.IsNullOrWhiteSpace(data))
            {
                log.Error("Webhook content empty.");
                return(req.CreateResponse(HttpStatusCode.OK));
            }

// #if !DEBUG
            req.Headers.TryGetValues("X-Hook-Signature", out var headers);
            var sign = headers?.FirstOrDefault();

            var secret = EnvironmentExtensions.GetEnvString("STARLING_WEBHOOK_SECRET");

            var(_, valid) = WebhookPayloadValidator.Validate(sign, secret, data);

            if (!valid)
            {
                log.Error("Webhook signature mismatch. Rejected.");
                return(req.CreateResponse(HttpStatusCode.OK));
            }
// #endif

            var payload = StarlingDeserialiser.WebhookPayload(data);

            var existing = await table.GetExistingTransaction(payload.AccountHolderUid, payload.Content.TransactionUid);

            if (existing != null)
            {
                log.Warning($"Transaction {existing.RowKey} already processed on {existing.Timestamp}.");
                return(req.CreateResponse(HttpStatusCode.OK));
            }

            var goalId     = EnvironmentExtensions.GetEnvString("STARLING_GOAL_ID");
            var remainder  = (long)(Math.Abs(payload.Content.Amount) * 100 % 100);
            var difference = 100 - remainder;

            var threshold = EnvironmentExtensions.GetEnvInt("ROUND_UP_THRESHOLD");

            if (remainder >= threshold)
            {
                await _httpClient.SavingsGoalsAddMoney(goalId, difference);
            }

            await table.SaveTransaction(payload.AccountHolderUid, payload.Content.TransactionUid, difference);

            return(req.CreateResponse(HttpStatusCode.OK));
        }
 public void Validate_NullEmptyWhitespaceJson_ThrowsException(string val, Type exception)
 {
     Assert.Throws(exception, () => WebhookPayloadValidator.Validate("signature", "secret", val));
 }
 public void Validate_NullEmptyWhitespaceSecret_ThrowsException(string value, Type exception)
 {
     // Act/Assert
     Assert.Throws(exception, () => WebhookPayloadValidator.Validate("signature", value, "{}"));
 }