public bool ParseHeaders(HttpRequest request, EventSubContext context) { if (!request.Headers.TryGetValue(EventSubHeaderNames.MessageId, out var msgIdValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.MessageRetry, out var retryValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.MessageType, out var typeValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.MessageSignature, out var signatureValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.MessageTimeStamp, out var timestampValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.SubscriptionType, out var subTypeValues)) { return(false); } if (!request.Headers.TryGetValue(EventSubHeaderNames.SubscriptionVersion, out var subVersionValues)) { return(false); } context.Headers.MessageId = msgIdValues.First(); context.Headers.MessageRetry = int.Parse(retryValues.First()); context.Headers.MessageType = typeValues.First(); context.Headers.MessageSignature = signatureValues.First(); context.Headers.MessageTimeStamp = timestampValues.First(); context.Headers.SubscriptionType = subTypeValues.First(); context.Headers.SubscriptionVersion = subVersionValues.First(); return(true); }
public async Task HandleRequestAsync(HttpContext context) { var eventSubContext = new EventSubContext { Logger = _logger, Services = context.RequestServices, }; if (!ParseHeaders(context.Request, eventSubContext)) { context.Response.StatusCode = StatusCodes.Status400BadRequest; return; } TwitchEventSubCallbackPayload payload = null; var isFirstSegment = true; var key = System.Text.Encoding.UTF8.GetBytes(_options.WebHookSecret); using (var signatureAlg = IncrementalHash.CreateHMAC(HashAlgorithmName.SHA256, key)) { ReadResult result; signatureAlg.AppendData(System.Text.Encoding.UTF8.GetBytes(eventSubContext.Headers.MessageId)); signatureAlg.AppendData(System.Text.Encoding.UTF8.GetBytes(eventSubContext.Headers.MessageTimeStamp)); do { result = await context.Request.BodyReader.ReadAsync(); foreach (var segment in result.Buffer) { signatureAlg.AppendData(segment.Span); _logger.LogWarning(System.Text.Encoding.UTF8.GetString(segment.Span)); } // If the first segment contains the whole body, read the payload from the segment if (isFirstSegment && result.Buffer.IsSingleSegment) { payload = ReadPayload(eventSubContext.Headers.SubscriptionType, result.Buffer); break; } isFirstSegment = false; } while (!result.IsCompleted && !result.IsCanceled); // Finalizing signature validation var hashBytes = signatureAlg.GetCurrentHash(); var hashString = Convert.ToHexString(hashBytes).ToLowerInvariant(); _logger.LogWarning("Signature = {signature}", hashString); if (hashString != eventSubContext.Headers.MessageSignature.Split("=").Last()) { _logger.LogError("Signature mismatch {received} =/= {computer}", eventSubContext.Headers.MessageSignature, hashString); context.Response.StatusCode = StatusCodes.Status400BadRequest; return; } // Degraded case: Body was more than a single segment, use Stream interface if (payload == null) { payload = await ReadPayloadAsync(eventSubContext.Headers.SubscriptionType, context.Request.Body); } if (eventSubContext.Headers.MessageType == EventSubMessageTypes.WebHookCallbackVerification) { await context.Response.WriteAsync(payload.Challenge); context.Response.StatusCode = StatusCodes.Status200OK; context.Response.ContentType = "text/plain"; return; } eventSubContext.Subscription = payload.Subscription; foreach (var handler in _handlers) { if (handler.CanHandleEvent(eventSubContext, payload.BaseEvent)) { await handler.OnEventSubNotification(eventSubContext, payload.BaseEvent); } } } context.Response.StatusCode = StatusCodes.Status200OK; }