Skip to content

Commit 60c416c

Browse files
committed
refactor: 시그니처 인증 과정 추가
1 parent 7db5b02 commit 60c416c

File tree

3 files changed

+37
-3
lines changed

3 files changed

+37
-3
lines changed

.env.sample

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,5 @@ POSTGRES_HOST=localhost
2222
POSTGRES_PORT=5432
2323

2424
# ETC
25-
SLACK_WEBHOOK_URL=https://hooks.slack.com/services
25+
SLACK_WEBHOOK_URL=https://hooks.slack.com/services
26+
SLACK_SENTRY_SIGNATURE=374708bedd34ae70f814471ff24db7dedc4b9bee06a7e8ef9255a4f6c8bd9049 # 실제 키를 사용하세요

src/controllers/webhook.controller.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { NextFunction, Request, RequestHandler, Response } from 'express';
22
import { EmptyResponseDto, SentryWebhookData } from '@/types';
33
import logger from '@/configs/logger.config';
44
import { sendSlackMessage } from '@/modules/slack/slack.notifier';
5+
import { verifySignature } from '@/utils/verify.util';
56

67
export class WebhookController {
78
private readonly STATUS_EMOJI = {
@@ -16,8 +17,8 @@ export class WebhookController {
1617
next: NextFunction,
1718
): Promise<void> => {
1819
try {
19-
20-
if (req.body?.action !== "created") {
20+
// 시그니처 검증 과정 추가
21+
if (req.body?.action !== "created" || !verifySignature(req)) {
2122
const response = new EmptyResponseDto(true, 'Sentry 웹훅 처리에 실패했습니다', {}, null);
2223
res.status(400).json(response);
2324
return;

src/utils/verify.util.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import crypto from "crypto"
2+
import dotenv from "dotenv";
3+
import { Request } from "express";
4+
5+
dotenv.config();
6+
7+
/**
8+
* Sentry 웹훅 요청의 시그니처 헤더를 검증합니다.
9+
*
10+
* HMAC SHA256과 Sentry의 Client Secret를 사용하여 요청 본문을 해시화하고,
11+
*
12+
* Sentry에서 제공하는 시그니처 헤더와 비교하여 요청의 무결성을 확인합니다.
13+
*
14+
* @param {Request} request - Express 요청 객체
15+
* @returns {boolean} 헤더가 유효하면 true, 그렇지 않으면 false
16+
*
17+
* @example
18+
* ```typescript
19+
* const isValid = verifySignature(req, process.env.SENTRY_WEBHOOK_SECRET);
20+
* if (!isValid) {
21+
* return res.status(401).json({ error: 'Invalid signature' });
22+
* }
23+
* ```
24+
*/
25+
export function verifySignature(request: Request) {
26+
if(!process.env.SENTRY_CLIENT_SECRET) throw new Error("SENTRY_CLIENT_SECRET is not defined");
27+
const hmac = crypto.createHmac("sha256", process.env.SENTRY_CLIENT_SECRET);
28+
hmac.update(JSON.stringify(request.body), "utf8");
29+
const digest = hmac.digest("hex");
30+
31+
return digest === request.headers["sentry-hook-signature"];
32+
}

0 commit comments

Comments
 (0)