first commit

This commit is contained in:
2026-10-03 13:40:26 +02:00
parent 732a5b5a16
commit 566f415fa7
555 changed files with 62704 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
import {
BadRequestException,
Controller,
Headers,
HttpCode,
Post,
RawBodyRequest,
Req,
UnauthorizedException,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { IdentityService } from '@task-stream/api/identity/application';
import { Request } from 'express';
import { createHmac, timingSafeEqual } from 'node:crypto';
import { Public } from '../auth/auth.decorators';
/** Receives `user.deactivated` / `token.revoked` from Pireon. */
@Controller('webhooks/pireon')
export class PireonWebhookController {
constructor(
private readonly identity: IdentityService,
private readonly config: ConfigService,
) {}
@Public()
@Post()
@HttpCode(204)
async receive(
@Req() req: RawBodyRequest<Request>,
@Headers('x-webhook-signature') signature?: string,
): Promise<void> {
this.verifySignature(req.rawBody, signature);
const body = req.body as {
event?: string;
type?: string;
data?: unknown;
payload?: unknown;
};
const event = body.event ?? body.type;
if (!event) throw new BadRequestException('Missing event name');
const data = (body.data ?? body.payload ?? body) as Record<string, unknown>;
await this.identity.handleWebhook(event, data);
}
/** X-Webhook-Signature = HMAC-SHA256(raw body, PIREON_WEBHOOK_SECRET). */
private verifySignature(
rawBody: Buffer | undefined,
signature: string | undefined,
): void {
const secret = this.config.get<string>('PIREON_WEBHOOK_SECRET');
if (!secret) {
throw new UnauthorizedException(
'Webhooks are disabled (PIREON_WEBHOOK_SECRET not set)',
);
}
if (!rawBody || !signature)
throw new UnauthorizedException('Missing signature');
const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
const received = signature.replace(/^sha256=/, '');
const valid =
received.length === expected.length &&
timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!valid) throw new UnauthorizedException('Invalid signature');
}
}