import { timingSafeEqual } from "node:crypto"; import { Body, Controller, Delete, Get, Headers, Param, Patch, Post, ServiceUnavailableException, UnauthorizedException, UseGuards, } from "@nestjs/common"; import { Public } from "../common/decorators/public.decorator"; import { Roles } from "../common/decorators/roles.decorator"; import { RolesGuard } from "../common/guards/roles.guard"; import type { CreateChangelogEntry, UpdateChangelogEntry } from "./changelog.dto"; import { ChangelogService } from "./changelog.service"; @Controller("changelog") export class ChangelogController { constructor(private readonly changelogService: ChangelogService) {} @Get() @Public() async findAll() { return this.changelogService.findAll(); } @Post() @UseGuards(RolesGuard) @Roles("admin") async create(@Body() body: CreateChangelogEntry) { return this.changelogService.create(body); } @Patch(":id") @UseGuards(RolesGuard) @Roles("admin") async update(@Param("id") id: string, @Body() body: UpdateChangelogEntry) { return this.changelogService.update(id, body); } @Delete(":id") @UseGuards(RolesGuard) @Roles("admin") async delete(@Param("id") id: string) { return this.changelogService.delete(id); } @Post("internal") @Public() async createInternal( @Headers("authorization") authHeader: string, @Body() body: CreateChangelogEntry, ) { // 1. Check if the automation token is configured const token = process.env.CHANGELOG_AUTOMATION_TOKEN; if (!token) { throw new ServiceUnavailableException("Changelog automation endpoint is not configured"); } // 2. Validate the Authorization header if (!authHeader || !authHeader.startsWith("Bearer ")) { throw new UnauthorizedException("Missing or invalid Authorization header"); } // 3. Extract the token value const providedToken = authHeader.slice(7); // 4. Constant-time comparison if (providedToken.length !== token.length) { throw new UnauthorizedException("Invalid token"); } const isMatch = timingSafeEqual(Buffer.from(providedToken), Buffer.from(token)); if (!isMatch) { throw new UnauthorizedException("Invalid token"); } // 5. Create the entry return this.changelogService.create(body); } }