Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
POSTGRES_SCHEMA="public"
DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=${POSTGRES_SCHEMA}"

# GitHub Webhook Configuration
GITHUB_WEBHOOK_SECRET="your_webhook_secret_here"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a placeholder or example value for GITHUB_WEBHOOK_SECRET that indicates it should be replaced with an actual secret, such as your_actual_webhook_secret_here.


# API configs
BUS_API_URL="https://api.topcoder-dev.com/v5/bus/events"
CHALLENGE_API_URL="https://api.topcoder-dev.com/v5/challenges/"
Expand Down
350 changes: 350 additions & 0 deletions GITHUB_WEBHOOK_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,350 @@
# GitHub Webhook Integration Setup and Testing Guide

## Overview

The Topcoder Review API includes a secure GitHub webhook integration that receives webhook events from GitHub repositories, validates them using HMAC-SHA256 signature verification, and stores them in the database for audit and future processing.

## Table of Contents

1. [Quick Start](#quick-start)
2. [Environment Setup](#environment-setup)
3. [GitHub Repository Configuration](#github-repository-configuration)
4. [Local Development Setup](#local-development-setup)
5. [Testing the Integration](#testing-the-integration)
6. [API Endpoint Reference](#api-endpoint-reference)
7. [Database Schema](#database-schema)
8. [Security Considerations](#security-considerations)
9. [Troubleshooting](#troubleshooting)
10. [Monitoring and Maintenance](#monitoring-and-maintenance)

## Quick Start

For immediate setup, follow these steps:

1. Generate a secure webhook secret
2. Configure environment variables
3. Set up GitHub webhook in repository settings
4. Test with a sample event

## Environment Setup

### Required Environment Variables

Add the following environment variable to your application configuration:

```bash
# .env file
GITHUB_WEBHOOK_SECRET=your_generated_secret_here
```

### Generate Webhook Secret

**Using OpenSSL:**
```bash
openssl rand -hex 32
```

**Example Output:**
```
a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
```

⚠️ **Important:** Store this secret securely and use the same value in both your application environment and GitHub webhook configuration.

### Database Setup

The webhook integration requires the `gitWebhookLog` table. If not already created, run the database migration:

```bash
npx prisma migrate dev
```

## GitHub Repository Configuration

### Step 1: Access Repository Settings

1. Navigate to your GitHub repository
2. Click on the **Settings** tab (requires admin permissions)
3. In the left sidebar, click **Webhooks**
4. Click **Add webhook**

### Step 2: Configure Webhook Settings

#### Payload URL

**Production/Staging Environment:**
```
https://your-api-domain.com/v6/review/webhooks/git
```

**Development Environment:**
```
https://your-dev-domain.com/webhooks/git
```

Note: The `/v6/review` prefix is only added in production when `NODE_ENV=production`.

#### Content Type
- Select `application/json`

#### Secret
- Enter the webhook secret you generated earlier
- This must exactly match your `GITHUB_WEBHOOK_SECRET` environment variable

#### SSL Verification
- Keep **Enable SSL verification** checked (recommended for production)
- For development with proper HTTPS setup, this should remain enabled

### Step 3: Select Events

Choose one of the following options:

**Option A: Send Everything (Recommended for Testing)**
- Select "Send me everything" to receive all GitHub event types

**Option B: Select Individual Events**
Common events for development workflows:
- **Pushes** - Code pushes to repository
- **Pull requests** - PR creation, updates, merges
- **Issues** - Issue creation, updates, comments
- **Issue comments** - Comments on issues and PRs
- **Releases** - Release creation and updates
- **Create** - Branch or tag creation
- **Delete** - Branch or tag deletion

### Step 4: Activate and Create

1. Ensure **Active** checkbox is checked
2. Click **Add webhook**
3. GitHub will automatically send a `ping` event to test the webhook

## Local Development Setup

Since GitHub webhooks require a publicly accessible URL, local development requires exposing your local server to the internet.

**Install ngrok:**
```bash
npm install -g ngrok
```

**Setup process:**
```bash
# 1. Start your local API server
pnpm run start:dev

# 2. In another terminal, expose your local server
ngrok http 3000

# 3. Copy the HTTPS URL from ngrok output
# Example: https://abc123.ngrok.io

# 4. Use this URL in GitHub webhook settings
# https://abc123.ngrok.io/webhooks/git
```
## Testing the Integration

### Manual Testing

#### 1. Verify Initial Setup

After creating the webhook, GitHub automatically sends a `ping` event:

1. Go to your repository's webhook settings
2. Click on your webhook
3. Check **Recent Deliveries** section
4. Look for the `ping` event with status 200 OK

#### 2. Trigger Test Events

**Create a Push Event:**
```bash
# Make a small change
echo "webhook test" >> test-webhook.txt
git add test-webhook.txt
git commit -m "Test webhook integration"
git push origin main
```

**Create an Issue:**
1. Go to your repository on GitHub
2. Click **Issues** tab
3. Click **New issue**
4. Create a test issue

**Create a Pull Request:**
1. Create a new branch: `git checkout -b test-webhook`
2. Make changes and commit
3. Push branch: `git push origin test-webhook`
4. Open pull request on GitHub

### Testing with curl

You can test the webhook endpoint directly using curl with proper signature generation:

```bash
#!/bin/bash

# Configuration
WEBHOOK_URL="http://localhost:3000/webhooks/git" # Adjust for your environment
WEBHOOK_SECRET="your_webhook_secret_here"
PAYLOAD='{"test": "data", "repository": {"name": "test-repo"}}'
DELIVERY_ID="test-delivery-$(date +%s)"
EVENT_TYPE="push"

# Generate signature
SIGNATURE="sha256=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$WEBHOOK_SECRET" | sed 's/^.* //')"

# Send test webhook
curl -X POST "$WEBHOOK_URL" \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: $EVENT_TYPE" \
-H "X-GitHub-Delivery: $DELIVERY_ID" \
-H "X-Hub-Signature-256: $SIGNATURE" \
-d "$PAYLOAD"
```

## API Endpoint Reference

### Webhook Endpoint

**URL:** `POST /webhooks/git` (development) or `POST /v6/review/webhooks/git` (production)

**Required Headers:**
- `Content-Type: application/json`
- `X-GitHub-Event: {event_type}` - GitHub event type (push, pull_request, etc.)
- `X-GitHub-Delivery: {delivery_id}` - Unique delivery identifier from GitHub
- `X-Hub-Signature-256: sha256={signature}` - HMAC-SHA256 signature for verification

**Request Body:**
- GitHub webhook payload (varies by event type)

**Response Codes:**
- `200 OK` - Webhook processed successfully
- `400 Bad Request` - Missing required headers or invalid payload
- `403 Forbidden` - Invalid signature verification
- `500 Internal Server Error` - Processing error or configuration issue

**Success Response:**
```json
{
"success": true,
"message": "Webhook processed successfully"
}
```

**Error Response:**
```json
{
"statusCode": 403,
"message": "Invalid signature",
"error": "Forbidden",
"timestamp": "2024-01-01T00:00:00.000Z",
"path": "/webhooks/git"
}
```

## Database Schema

Webhook events are stored in the `gitWebhookLog` table:

```sql
CREATE TABLE "gitWebhookLog" (
"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
"eventId" TEXT NOT NULL,
"event" TEXT NOT NULL,
"eventPayload" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "gitWebhookLog_pkey" PRIMARY KEY ("id")
);

-- Indexes for efficient querying
CREATE INDEX "gitWebhookLog_eventId_idx" ON "gitWebhookLog"("eventId");
CREATE INDEX "gitWebhookLog_event_idx" ON "gitWebhookLog"("event");
CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt");
```

### Query Examples

**View recent webhook events:**
```sql
SELECT
id,
"eventId",
event,
"createdAt"
FROM "gitWebhookLog"
ORDER BY "createdAt" DESC
LIMIT 10;
```

**Filter by event type:**
```sql
SELECT * FROM "gitWebhookLog"
WHERE event = 'push'
ORDER BY "createdAt" DESC;
```

**View specific webhook payload:**
```sql
SELECT
event,
"eventPayload"
FROM "gitWebhookLog"
WHERE "eventId" = 'your-delivery-id';
```

## Security Considerations

### Signature Verification

The webhook implementation uses GitHub's recommended security practices:

1. **HMAC-SHA256 Signature:** All incoming webhooks are verified using HMAC-SHA256
2. **Timing-Safe Comparison:** Uses `crypto.timingSafeEqual()` to prevent timing attacks
3. **Secret Protection:** Webhook secrets are stored as environment variables
4. **Header Validation:** Validates all required GitHub headers

### Best Practices

1. **Use HTTPS:** Always use HTTPS URLs for production webhooks
2. **Rotate Secrets:** Periodically rotate webhook secrets
3. **Monitor Access:** Regularly review webhook delivery logs
4. **Limit Events:** Only subscribe to events you actually need
5. **Access Control:** Restrict webhook configuration to repository administrators

### Environment Security

- Store `GITHUB_WEBHOOK_SECRET` securely using your deployment platform's secret management
- Never commit secrets to version control
- Use different secrets for different environments
- Implement proper secret rotation procedures

### Log Analysis

Key log messages to monitor:

```
# Successful webhook processing
[WebhookController] Successfully processed GitHub webhook

# Signature validation failures
[GitHubSignatureGuard] Invalid webhook signature for delivery

# Configuration errors
[GitHubSignatureGuard] GITHUB_WEBHOOK_SECRET environment variable is not configured
```

Example

```
[2025-08-02T01:06:48.312Z] [LOG] [Bootstrap] Server started on port 3000
[2025-08-02T01:07:15.700Z] [LOG] [HttpRequest] {"type":"request","method":"POST","url":"/webhooks/git","ip":"::1","userAgent":"GitHub-Hookshot/4f8bd7a"}
[2025-08-02T01:07:15.739Z] [LOG] [GitHubSignatureGuard] Valid webhook signature verified for delivery 0722d0bc-6f3d-11f0-8a2d-6cc18966c098, event push
[2025-08-02T01:07:15.740Z] [LOG] [WebhookController] {"message":"Received GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"}
[2025-08-02T01:07:15.740Z] [LOG] [WebhookService] {"message":"Processing GitHub webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","timestamp":"2025-08-02T01:07:15.740Z"}
[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Successfully stored webhook event","eventId":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","storedId":"9aHvEgDYPCYYnU","createdAt":"2025-08-02T01:07:15.747Z"}
[2025-08-02T01:07:15.804Z] [LOG] [WebhookService] {"message":"Event-specific processing placeholder","event":"push","payloadSize":7979}
[2025-08-02T01:07:15.804Z] [LOG] [WebhookController] {"message":"Successfully processed GitHub webhook","delivery":"0722d0bc-6f3d-11f0-8a2d-6cc18966c098","event":"push","success":true}
[2025-08-02T01:07:15.804Z] [LOG] [HttpRequest] {"type":"response","statusCode":200,"method":"POST","url":"/webhooks/git","responseTime":"104ms"}
```
32 changes: 32 additions & 0 deletions prisma/migrations/20250802073602_github_webhook/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-- DropForeignKey
ALTER TABLE "reviewApplication" DROP CONSTRAINT "reviewApplication_opportunityId_fkey";

-- AlterTable
ALTER TABLE "reviewApplication" ALTER COLUMN "opportunityId" SET DATA TYPE TEXT,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider verifying if changing the data type of opportunityId to TEXT aligns with the application's data integrity requirements, especially if it was previously a different type such as INTEGER.

ALTER COLUMN "updatedAt" DROP DEFAULT;

-- AlterTable
ALTER TABLE "reviewOpportunity" ALTER COLUMN "updatedAt" DROP DEFAULT;

-- CreateTable
CREATE TABLE "gitWebhookLog" (
"id" VARCHAR(14) NOT NULL DEFAULT nanoid(),
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the nanoid() function is supported by your database system and that it generates IDs that meet your application's requirements for uniqueness and length.

"eventId" TEXT NOT NULL,
"event" TEXT NOT NULL,
"eventPayload" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "gitWebhookLog_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "gitWebhookLog_eventId_idx" ON "gitWebhookLog"("eventId");

-- CreateIndex
CREATE INDEX "gitWebhookLog_event_idx" ON "gitWebhookLog"("event");

-- CreateIndex
CREATE INDEX "gitWebhookLog_createdAt_idx" ON "gitWebhookLog"("createdAt");

-- AddForeignKey
ALTER TABLE "reviewApplication" ADD CONSTRAINT "reviewApplication_opportunityId_fkey" FOREIGN KEY ("opportunityId") REFERENCES "reviewOpportunity"("id") ON DELETE CASCADE ON UPDATE CASCADE;
Loading