Skip to content

Switch to auth headers #10

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 4, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=${P


# Gitea Webhook Configuration
GITEA_WEBHOOK_SECRET="your_webhook_secret_here"
GITEA_WEBHOOK_AUTH="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 renaming the environment variable GITEA_WEBHOOK_AUTH to something more descriptive if it is intended to store an authentication header value, as AUTH might be too generic and could lead to confusion.


# Kafka Configuration
KAFKA_BROKERS=localhost:9092
Expand Down
67 changes: 12 additions & 55 deletions docs/GITEA_WEBHOOK_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Overview

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

## Table of Contents

Expand All @@ -21,7 +21,7 @@ The Topcoder Review API includes a secure Gitea webhook integration that receive

For immediate setup, follow these steps:

1. Generate a secure webhook secret
1. Generate a secure webhook auth secret
2. Configure environment variables
3. Set up Gitea webhook in repository settings
4. Test with a sample event
Expand All @@ -34,7 +34,7 @@ Add the following environment variable to your application configuration:

```bash
# .env file
GITEA_WEBHOOK_SECRET=your_generated_secret_here
GITEA_WEBHOOK_AUTH=your_generated_secret_here
```

### Generate Webhook Secret
Expand All @@ -51,7 +51,7 @@ openssl rand -hex 32
a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
```

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

### Database Setup

Expand Down Expand Up @@ -92,10 +92,10 @@ Note: The `/v6/review` prefix is only added in production when `NODE_ENV=product

- Select `application/json`

#### Secret
#### Authorization

- Enter the webhook secret you generated earlier
- This must exactly match your `GITEA_WEBHOOK_SECRET` environment variable
- Enter the webhook auth secret you generated earlier
- This must exactly match your `GITEA_WEBHOOK_AUTH` environment variable

#### SSL Verification

Expand Down Expand Up @@ -192,33 +192,7 @@ git push origin main
3. Push branch: `git push origin test-webhook`
4. Open pull request on Gitea

### 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/gitea" # 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-Gitea-Event: $EVENT_TYPE" \
-H "X-Gitea-Delivery: $DELIVERY_ID" \
-H "X-Hub-Signature-256: $SIGNATURE" \
-d "$PAYLOAD"
```

## API Endpoint Reference
### API Endpoint Reference

### Webhook Endpoint

Expand All @@ -229,7 +203,7 @@ curl -X POST "$WEBHOOK_URL" \
- `Content-Type: application/json`
- `X-Gitea-Event: {event_type}` - Gitea event type (push, pull_request, etc.)
- `X-Gitea-Delivery: {delivery_id}` - Unique delivery identifier from Gitea
- `X-Hub-Signature-256: sha256={signature}` - HMAC-SHA256 signature for verification
- `Authorization: Bearer {GITEA_WEBHOOK_AUTH}` - Token used to verify authorization

**Request Body:**

Expand Down Expand Up @@ -323,10 +297,8 @@ WHERE "eventId" = 'your-delivery-id';

The webhook implementation uses Gitea'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 Gitea headers
1. **Secret Protection:** Webhook auth secrets are stored as environment variables
2. **Header Validation:** Validates all required Gitea headers

### Best Practices

Expand All @@ -338,22 +310,7 @@ The webhook implementation uses Gitea's recommended security practices:

### Environment Security

- Store `GITEA_WEBHOOK_SECRET` securely using your deployment platform's secret management
- Store `GITEA_WEBHOOK_AUTH` 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 Gitea webhook

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

# Configuration errors
[GiteaSignatureGuard] Gitea_WEBHOOK_SECRET environment variable is not configured
```
4 changes: 2 additions & 2 deletions src/api/api.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ReviewHistoryController } from './review-history/reviewHistory.controll
import { ChallengeApiService } from 'src/shared/modules/global/challenge.service';
import { WebhookController } from './webhook/webhook.controller';
import { WebhookService } from './webhook/webhook.service';
import { GiteaSignatureGuard } from '../shared/guards/gitea-signature.guard';
import { GiteaWebhookAuthGuard } from '../shared/guards/gitea-webhook-auth.guard';
Copy link

Choose a reason for hiding this comment

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

The import statement has been updated to use GiteaWebhookAuthGuard instead of GiteaSignatureGuard. Ensure that the new guard GiteaWebhookAuthGuard is correctly implemented and provides the necessary functionality that was previously handled by GiteaSignatureGuard. Also, verify that all references to GiteaSignatureGuard in the codebase have been updated accordingly.


@Module({
imports: [HttpModule, GlobalProvidersModule],
Expand All @@ -36,7 +36,7 @@ import { GiteaSignatureGuard } from '../shared/guards/gitea-signature.guard';
ReviewApplicationService,
ChallengeApiService,
WebhookService,
GiteaSignatureGuard,
GiteaWebhookAuthGuard,
],
})
export class ApiModule {}
8 changes: 4 additions & 4 deletions src/api/webhook/webhook.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
WebhookEventDto,
WebhookResponseDto,
} from '../../dto/webhook-event.dto';
import { GiteaSignatureGuard } from '../../shared/guards/gitea-signature.guard';
import { GiteaWebhookAuthGuard } from '../../shared/guards/gitea-webhook-auth.guard';
Copy link

Choose a reason for hiding this comment

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

The import statement has been updated to use GiteaWebhookAuthGuard instead of GiteaSignatureGuard. Ensure that the new guard GiteaWebhookAuthGuard is correctly implemented and that all necessary changes in the codebase have been made to accommodate this switch. Verify that the new guard provides the required authentication functionality and that any dependencies or configurations have been updated accordingly.

import { LoggerService } from '../../shared/modules/global/logger.service';

@ApiTags('Webhooks')
Expand All @@ -25,7 +25,7 @@ export class WebhookController {

@Post('gitea')
@HttpCode(HttpStatus.OK)
@UseGuards(GiteaSignatureGuard)
@UseGuards(GiteaWebhookAuthGuard)
@ApiOperation({
summary: 'Gitea Webhook Endpoint',
description:
Expand All @@ -42,8 +42,8 @@ export class WebhookController {
required: true,
})
@ApiHeader({
name: 'X-Hub-Signature-256',
description: 'HMAC-SHA256 signature for request verification',
name: 'authorization',
description: 'Authorization header for Gitea webhook',
required: true,
})
@ApiResponse({
Expand Down
105 changes: 0 additions & 105 deletions src/shared/guards/gitea-signature.guard.ts

This file was deleted.

69 changes: 69 additions & 0 deletions src/shared/guards/gitea-webhook-auth.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
Injectable,
CanActivate,
ExecutionContext,
ForbiddenException,
BadRequestException,
InternalServerErrorException,
} from '@nestjs/common';
import { Request } from 'express';
import { LoggerService } from '../modules/global/logger.service';

@Injectable()
export class GiteaWebhookAuthGuard implements CanActivate {
private readonly logger = LoggerService.forRoot('GiteaWebhookAuthGuard');
Copy link

Choose a reason for hiding this comment

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

Consider using dependency injection for the LoggerService instead of calling forRoot. This will make the service easier to test and mock.


canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const delivery = request.headers['x-gitea-delivery'] as string;
const event = request.headers['x-gitea-event'] as string;
const authHeader = request.headers['authorization'] as string;

// Check if GITEA_WEBHOOK_AUTH is configured
const auth = process.env.GITEA_WEBHOOK_AUTH;
Copy link

Choose a reason for hiding this comment

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

It might be more secure to use a configuration service or a secrets manager to handle sensitive environment variables like GITEA_WEBHOOK_AUTH instead of accessing them directly from process.env.

if (!auth) {
this.logger.error(
'GITEA_WEBHOOK_AUTH environment variable is not configured',
);
throw new InternalServerErrorException('Webhook auth not configured');
}

if (!delivery) {
this.logger.error('Missing X-Gitea-Delivery header');
throw new BadRequestException('Missing delivery header');
}

if (!event) {
this.logger.error('Missing X-Gitea-Event header');
throw new BadRequestException('Missing event header');
}

try {
// Validate the authorization header
if (!authHeader) {
this.logger.error('Missing Authorization header');
throw new BadRequestException('Missing authorization header');
}

if (authHeader !== `Bearer ${auth}`) {
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 more secure method for comparing the authorization header, such as a constant-time comparison function, to prevent timing attacks.

this.logger.error('Invalid authorization header');
throw new ForbiddenException('Invalid authorization');
}

this.logger.log(
`Valid webhook authorization verified for delivery ${delivery}, event ${event}`,
);
return true;
} catch (error) {
if (
error instanceof ForbiddenException ||
error instanceof BadRequestException
) {
throw error;
}

this.logger.error(`Error validating webhook signature: ${error.message}`);
throw new InternalServerErrorException('Signature validation failed');
}
}
}