Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/config/swagger.config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { createSwaggerConfig } from './swagger.config';

describe('createSwaggerConfig', () => {
// Regression test for issue #104: Swagger UI returned "Unauthorized" because the
// X-API-Key scheme was defined but never applied — no operation declared a security
// requirement, so Swagger UI never sent the key. The fix applies it globally.
it('applies the X-API-Key security scheme as a global requirement', () => {
const config = createSwaggerConfig();

expect(config.security).toContainEqual({ 'X-API-Key': [] });
});
});
32 changes: 32 additions & 0 deletions src/config/swagger.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DocumentBuilder, OpenAPIObject } from '@nestjs/swagger';

/**
* Security scheme name for the API key, used both when defining the scheme and
* when applying it as a global requirement so Swagger UI sends the header.
*/
export const API_KEY_SECURITY_SCHEME = 'X-API-Key';

/**
* Builds the OpenAPI document configuration for the OpenWA API.
*/
export function createSwaggerConfig(): Omit<OpenAPIObject, 'paths'> {
return (
new DocumentBuilder()
.setTitle('OpenWA API')
.setDescription('Open Source WhatsApp API Gateway - Free, Self-Hosted HTTP API')
.setVersion('0.1.6')
.addApiKey({ type: 'apiKey', name: 'X-API-Key', in: 'header' }, API_KEY_SECURITY_SCHEME)
// Apply the scheme globally so Swagger UI sends the key with every request
// (mirrors the global ApiKeyGuard). Without this, "Authorize" is cosmetic.
.addSecurityRequirements(API_KEY_SECURITY_SCHEME)
.addTag('sessions', 'WhatsApp session management')
.addTag('messages', 'Send and manage messages')
.addTag('webhooks', 'Webhook configuration')
.addTag('contacts', 'Contact management')
.addTag('groups', 'Group management')
.addTag('labels', 'Label management (WhatsApp Business)')
.addTag('channels', 'Channel/Newsletter management')
.addTag('health', 'Health check endpoints')
.build()
);
}
18 changes: 3 additions & 15 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { SwaggerModule } from '@nestjs/swagger';
import helmet from 'helmet';
import { AppModule } from './app.module';
import { ShutdownService } from './common/services/shutdown.service';
import { createSwaggerConfig } from './config/swagger.config';
import * as dotenv from 'dotenv';
import * as fs from 'fs';
import * as path from 'path';
Expand Down Expand Up @@ -141,20 +142,7 @@ async function bootstrap() {
);

// Swagger documentation
const config = new DocumentBuilder()
.setTitle('OpenWA API')
.setDescription('Open Source WhatsApp API Gateway - Free, Self-Hosted HTTP API')
.setVersion('0.1.6')
.addApiKey({ type: 'apiKey', name: 'X-API-Key', in: 'header' }, 'X-API-Key')
.addTag('sessions', 'WhatsApp session management')
.addTag('messages', 'Send and manage messages')
.addTag('webhooks', 'Webhook configuration')
.addTag('contacts', 'Contact management')
.addTag('groups', 'Group management')
.addTag('labels', 'Label management (WhatsApp Business)')
.addTag('channels', 'Channel/Newsletter management')
.addTag('health', 'Health check endpoints')
.build();
const config = createSwaggerConfig();

const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document);
Expand Down
3 changes: 1 addition & 2 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { Controller, Get, Post, Put, Delete, Body, Param, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { AuthService } from './auth.service';
import { CreateApiKeyDto, UpdateApiKeyDto, ApiKeyResponseDto, ApiKeyCreatedResponseDto } from './dto';
import { RequireRole } from './decorators/auth.decorators';
import { ApiKeyRole } from './entities/api-key.entity';

@ApiTags('auth')
@ApiBearerAuth()
@Controller('auth/api-keys')
export class AuthController {
constructor(private readonly authService: AuthService) {}
Expand Down
3 changes: 1 addition & 2 deletions src/modules/catalog/catalog.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Controller, Get, Post, Param, Body, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { CatalogService } from './catalog.service';
import { SendProductDto, SendCatalogDto, ProductQueryDto } from './dto/send-product.dto';

@ApiTags('Catalog')
@ApiBearerAuth()
@Controller('sessions/:sessionId')
export class CatalogController {
constructor(private readonly catalogService: CatalogService) {}
Expand Down
3 changes: 1 addition & 2 deletions src/modules/plugins/plugins.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Controller, Get, Post, Put, Param, Body, HttpCode, HttpStatus } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger';
import { PluginsService } from './plugins.service';
import { PluginDto, PluginConfigDto } from './dto/plugin.dto';

@ApiTags('plugins')
@ApiBearerAuth()
@Controller('plugins')
export class PluginsController {
constructor(private readonly pluginsService: PluginsService) {}
Expand Down
3 changes: 1 addition & 2 deletions src/modules/stats/stats.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Controller, Get, Param, Query } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { StatsService } from './stats.service';
import { StatsQueryDto } from './dto/stats-query.dto';

@ApiTags('Statistics')
@ApiBearerAuth()
@Controller('stats')
export class StatsController {
constructor(private readonly statsService: StatsService) {}
Expand Down
3 changes: 1 addition & 2 deletions src/modules/status/status.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { Controller, Get, Post, Delete, Param, Body } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { ApiTags, ApiOperation } from '@nestjs/swagger';
import { StatusService } from './status.service';
import { SendTextStatusDto } from './dto/send-text-status.dto';
import { SendImageStatusDto, SendVideoStatusDto } from './dto/send-media-status.dto';

@ApiTags('Status')
@ApiBearerAuth()
@Controller('sessions/:sessionId/status')
export class StatusController {
constructor(private readonly statusService: StatusService) {}
Expand Down
Loading