evolution-api/.cursor/rules/specialized-rules/guard-rules.mdc
Davidson Gomes 7088ad05d2 feat: add project guidelines and configuration files for development standards
- Introduce AGENTS.md for repository guidelines and project structure
- Add core development principles in .cursor/rules/core-development.mdc
- Establish project-specific context in .cursor/rules/project-context.mdc
- Implement Cursor IDE configuration in .cursor/rules/cursor.json
- Create specialized rules for controllers, services, DTOs, guards, routes, and integrations
- Update .gitignore to exclude unnecessary files
- Enhance CLAUDE.md with project overview and common development commands
2025-09-17 15:43:32 -03:00

416 lines
10 KiB
Plaintext

---
description: Guard patterns for authentication and authorization in Evolution API
globs:
- "src/api/guards/**/*.ts"
alwaysApply: false
---
# Evolution API Guard Rules
## Guard Structure Pattern
### Standard Guard Function
```typescript
import { NextFunction, Request, Response } from 'express';
import { Logger } from '@config/logger.config';
import { UnauthorizedException, ForbiddenException } from '@exceptions';
const logger = new Logger('GUARD');
async function guardFunction(req: Request, _: Response, next: NextFunction) {
// Guard logic here
if (validationFails) {
throw new UnauthorizedException();
}
return next();
}
export const guardName = { guardFunction };
```
## Authentication Guard Pattern
### API Key Authentication
```typescript
async function apikey(req: Request, _: Response, next: NextFunction) {
const env = configService.get<Auth>('AUTHENTICATION').API_KEY;
const key = req.get('apikey');
const db = configService.get<Database>('DATABASE');
if (!key) {
throw new UnauthorizedException();
}
// Global API key check
if (env.KEY === key) {
return next();
}
// Special routes handling
if ((req.originalUrl.includes('/instance/create') || req.originalUrl.includes('/instance/fetchInstances')) && !key) {
throw new ForbiddenException('Missing global api key', 'The global api key must be set');
}
const param = req.params as unknown as InstanceDto;
try {
if (param?.instanceName) {
const instance = await prismaRepository.instance.findUnique({
where: { name: param.instanceName },
});
if (instance.token === key) {
return next();
}
} else {
if (req.originalUrl.includes('/instance/fetchInstances') && db.SAVE_DATA.INSTANCE) {
const instanceByKey = await prismaRepository.instance.findFirst({
where: { token: key },
});
if (instanceByKey) {
return next();
}
}
}
} catch (error) {
logger.error(error);
}
throw new UnauthorizedException();
}
export const authGuard = { apikey };
```
## Instance Validation Guards
### Instance Exists Guard
```typescript
async function getInstance(instanceName: string) {
try {
const cacheConf = configService.get<CacheConf>('CACHE');
const exists = !!waMonitor.waInstances[instanceName];
if (cacheConf.REDIS.ENABLED && cacheConf.REDIS.SAVE_INSTANCES) {
const keyExists = await cache.has(instanceName);
return exists || keyExists;
}
return exists || (await prismaRepository.instance.findMany({ where: { name: instanceName } })).length > 0;
} catch (error) {
throw new InternalServerErrorException(error?.toString());
}
}
export async function instanceExistsGuard(req: Request, _: Response, next: NextFunction) {
if (req.originalUrl.includes('/instance/create')) {
return next();
}
const param = req.params as unknown as InstanceDto;
if (!param?.instanceName) {
throw new BadRequestException('"instanceName" not provided.');
}
if (!(await getInstance(param.instanceName))) {
throw new NotFoundException(`The "${param.instanceName}" instance does not exist`);
}
next();
}
```
### Instance Logged Guard
```typescript
export async function instanceLoggedGuard(req: Request, _: Response, next: NextFunction) {
if (req.originalUrl.includes('/instance/create')) {
const instance = req.body as InstanceDto;
if (await getInstance(instance.instanceName)) {
throw new ForbiddenException(`This name "${instance.instanceName}" is already in use.`);
}
if (waMonitor.waInstances[instance.instanceName]) {
delete waMonitor.waInstances[instance.instanceName];
}
}
next();
}
```
## Telemetry Guard Pattern
### Telemetry Collection
```typescript
class Telemetry {
public collectTelemetry(req: Request, res: Response, next: NextFunction): void {
// Collect telemetry data
const telemetryData = {
route: req.originalUrl,
method: req.method,
timestamp: new Date(),
userAgent: req.get('User-Agent'),
};
// Send telemetry asynchronously (don't block request)
setImmediate(() => {
this.sendTelemetry(telemetryData);
});
next();
}
private async sendTelemetry(data: any): Promise<void> {
try {
// Send telemetry data
} catch (error) {
// Silently fail - don't affect main request
}
}
}
export default Telemetry;
```
## Guard Composition Pattern
### Multiple Guards Usage
```typescript
// In router setup
const guards = [instanceExistsGuard, instanceLoggedGuard, authGuard['apikey']];
router
.use('/instance', new InstanceRouter(configService, ...guards).router)
.use('/message', new MessageRouter(...guards).router)
.use('/chat', new ChatRouter(...guards).router);
```
## Error Handling in Guards
### Proper Exception Throwing
```typescript
// CORRECT - Use proper HTTP exceptions
if (!apiKey) {
throw new UnauthorizedException('API key required');
}
if (instanceExists) {
throw new ForbiddenException('Instance already exists');
}
if (!instanceFound) {
throw new NotFoundException('Instance not found');
}
if (validationFails) {
throw new BadRequestException('Invalid request parameters');
}
// INCORRECT - Don't use generic Error
if (!apiKey) {
throw new Error('API key required'); // ❌ Use specific exceptions
}
```
## Configuration Access in Guards
### Config Service Usage
```typescript
async function configAwareGuard(req: Request, _: Response, next: NextFunction) {
const authConfig = configService.get<Auth>('AUTHENTICATION');
const cacheConfig = configService.get<CacheConf>('CACHE');
const dbConfig = configService.get<Database>('DATABASE');
// Use configuration for guard logic
if (authConfig.API_KEY.KEY === providedKey) {
return next();
}
throw new UnauthorizedException();
}
```
## Database Access in Guards
### Prisma Repository Usage
```typescript
async function databaseGuard(req: Request, _: Response, next: NextFunction) {
try {
const param = req.params as unknown as InstanceDto;
const instance = await prismaRepository.instance.findUnique({
where: { name: param.instanceName },
});
if (!instance) {
throw new NotFoundException('Instance not found');
}
// Additional validation logic
if (instance.status !== 'active') {
throw new ForbiddenException('Instance not active');
}
return next();
} catch (error) {
logger.error('Database guard error:', error);
throw new InternalServerErrorException('Database access failed');
}
}
```
## Cache Integration in Guards
### Cache Service Usage
```typescript
async function cacheAwareGuard(req: Request, _: Response, next: NextFunction) {
const cacheConf = configService.get<CacheConf>('CACHE');
if (cacheConf.REDIS.ENABLED) {
const cached = await cache.get(`guard:${req.params.instanceName}`);
if (cached) {
// Use cached validation result
return next();
}
}
// Perform validation and cache result
const isValid = await performValidation(req.params.instanceName);
if (cacheConf.REDIS.ENABLED) {
await cache.set(`guard:${req.params.instanceName}`, isValid, 300); // 5 min TTL
}
if (isValid) {
return next();
}
throw new UnauthorizedException();
}
```
## Logging in Guards
### Structured Logging
```typescript
const logger = new Logger('GUARD');
async function loggedGuard(req: Request, _: Response, next: NextFunction) {
logger.log(`Guard validation started for ${req.originalUrl}`);
try {
// Guard logic
const isValid = await validateRequest(req);
if (isValid) {
logger.log(`Guard validation successful for ${req.params.instanceName}`);
return next();
}
logger.warn(`Guard validation failed for ${req.params.instanceName}`);
throw new UnauthorizedException();
} catch (error) {
logger.error(`Guard validation error: ${error.message}`, error.stack);
throw error;
}
}
```
## Guard Testing Pattern
### Unit Test Structure
```typescript
describe('authGuard', () => {
let req: Partial<Request>;
let res: Partial<Response>;
let next: NextFunction;
beforeEach(() => {
req = {
get: jest.fn(),
params: {},
originalUrl: '/test',
};
res = {};
next = jest.fn();
});
describe('apikey', () => {
it('should pass with valid global API key', async () => {
(req.get as jest.Mock).mockReturnValue('valid-global-key');
await authGuard.apikey(req as Request, res as Response, next);
expect(next).toHaveBeenCalled();
});
it('should throw UnauthorizedException with no API key', async () => {
(req.get as jest.Mock).mockReturnValue(undefined);
await expect(
authGuard.apikey(req as Request, res as Response, next)
).rejects.toThrow(UnauthorizedException);
});
it('should pass with valid instance token', async () => {
(req.get as jest.Mock).mockReturnValue('instance-token');
req.params = { instanceName: 'test-instance' };
// Mock prisma repository
jest.spyOn(prismaRepository.instance, 'findUnique').mockResolvedValue({
token: 'instance-token',
} as any);
await authGuard.apikey(req as Request, res as Response, next);
expect(next).toHaveBeenCalled();
});
});
});
```
## Guard Performance Considerations
### Efficient Validation
```typescript
// CORRECT - Efficient guard with early returns
async function efficientGuard(req: Request, _: Response, next: NextFunction) {
// Quick checks first
if (req.originalUrl.includes('/public')) {
return next(); // Skip validation for public routes
}
const apiKey = req.get('apikey');
if (!apiKey) {
throw new UnauthorizedException(); // Fail fast
}
// More expensive checks only if needed
if (apiKey === globalKey) {
return next(); // Skip database check
}
// Database check only as last resort
const isValid = await validateInDatabase(apiKey);
if (isValid) {
return next();
}
throw new UnauthorizedException();
}
// INCORRECT - Inefficient guard
async function inefficientGuard(req: Request, _: Response, next: NextFunction) {
// Always do expensive database check first
const dbResult = await expensiveDatabaseQuery(); // ❌ Expensive operation first
const apiKey = req.get('apikey');
if (!apiKey && dbResult) { // ❌ Complex logic
throw new UnauthorizedException();
}
next();
}
```