Skip to content
Draft
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
7 changes: 7 additions & 0 deletions .jules/sentinel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## 2024-05-22 - Incomplete Token Revocation Bypass
**Vulnerability:** The application implemented a token blacklist mechanism in `AuthService` but failed to enforce it in the API routes. The `preHandler` hooks only called `request.jwtVerify()`, which validates the signature and expiration but ignores the blacklist. This meant that a "logged out" token (present in the blacklist) could still be used to access protected endpoints until it naturally expired.
**Learning:** Security features must be enforced at the gate (middleware). Implementing a service method (`isTokenBlacklisted`) is useless if it's not invoked during the request lifecycle. Code duplication in route definitions (`preHandler` blocks) makes it easy to miss security checks and hard to update them globally.
**Prevention:**
1. Centralize authentication logic in a single middleware (e.g., `auth.middleware.ts`).
2. Avoid inline `preHandler` definitions that repeat boilerplate code.
3. Ensure that "logout" actions actually invalidate the session on the server side (via blacklist/revocation list) and that this list is checked on *every* request.
9 changes: 9 additions & 0 deletions backend/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest',
},
setupFiles: ['<rootDir>/jest.setup.js'],
};
18 changes: 18 additions & 0 deletions backend/jest.setup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
process.env.NODE_ENV = 'test';
process.env.DATABASE_URL = 'postgresql://test:test@localhost:5432/test';
process.env.JWT_SECRET = 'test-secret';
process.env.JWT_EXPIRES_IN = '1h';
process.env.JWT_REFRESH_EXPIRES_IN = '1d';
process.env.LUNES_RPC_URL = 'http://localhost:8545';
process.env.CONTRACT_ADDRESS = '0x123';
process.env.PRIVATE_KEY = '0x123';
process.env.SMTP_HOST = 'localhost';
process.env.AWS_ACCESS_KEY_ID = 'test';
process.env.AWS_SECRET_ACCESS_KEY = 'test';
process.env.AWS_BUCKET_NAME = 'test';
process.env.AWS_REGION = 'us-east-1';
process.env.KYC_API_KEY = 'test';
process.env.KYC_API_URL = 'http://localhost';
process.env.PRICE_ORACLE_API = 'test';
process.env.PRICE_ORACLE_URL = 'http://localhost';
process.env.REDIS_URL = 'redis://localhost:6379';
26 changes: 10 additions & 16 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

100 changes: 10 additions & 90 deletions backend/src/modules/ama/ama.routes.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { FastifyInstance } from 'fastify';
import { AmaController } from './ama.controller';
import { authenticate } from '../../shared/middleware/auth.middleware';

/**
* Rotas para AMA (Ask Me Anything)
Expand Down Expand Up @@ -133,16 +134,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.getAmaSessions.bind(amaController));

// GET /ama/sessions/:id - Obter sessão AMA específica
Expand All @@ -163,16 +155,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
200: { $ref: 'amaSessionResponse#' }
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.getAmaSession.bind(amaController));

// POST /ama/sessions - Criar nova sessão AMA
Expand All @@ -187,16 +170,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
201: { $ref: 'amaSessionResponse#' }
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.createAmaSession.bind(amaController));

// PUT /ama/sessions/:id - Atualizar sessão AMA
Expand Down Expand Up @@ -231,16 +205,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
200: { $ref: 'amaSessionResponse#' }
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.updateAmaSession.bind(amaController));

// GET /ama/sessions/:sessionId/questions - Listar perguntas de uma sessão
Expand Down Expand Up @@ -291,16 +256,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.getAmaQuestions.bind(amaController));

// POST /ama/sessions/:sessionId/questions - Criar pergunta em uma sessão
Expand Down Expand Up @@ -329,16 +285,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.createAmaQuestion.bind(amaController));

// PUT /ama/questions/:questionId/answer - Responder pergunta
Expand Down Expand Up @@ -367,16 +314,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.answerAmaQuestion.bind(amaController));

// POST /ama/questions/:questionId/vote - Votar em pergunta
Expand Down Expand Up @@ -405,16 +343,7 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.voteAmaQuestion.bind(amaController));

// GET /ama/sessions/:sessionId/stats - Estatísticas da sessão
Expand Down Expand Up @@ -449,15 +378,6 @@ export const amaRoutes = async (server: FastifyInstance) => {
}
}
},
preHandler: async (request, reply) => {
try {
await request.jwtVerify();
} catch (err) {
return reply.status(401).send({
success: false,
error: 'Token de autorização inválido'
});
}
}
preHandler: [authenticate]
}, amaController.getAmaStats.bind(amaController));
};
Loading
Loading