Skip to content

Commit b6d3667

Browse files
committed
feat: add dashboard API with metrics and recent activity endpoints
- Implemented `/dashboard` endpoint to retrieve dashboard metrics and recent data. - Added `/dashboard/metrics` endpoint for lightweight metrics retrieval. - Created Swagger documentation for both endpoints. feat: add leads metadata endpoint - Introduced `/leads/metadata` endpoint to provide lead statuses, sources, ratings, and industries. - Updated Swagger documentation for leads metadata. feat: create tasks API with CRUD operations and comments - Added `/tasks` endpoint for retrieving and creating tasks with filtering options. - Implemented task detail retrieval, updating, and deletion endpoints. - Introduced comments functionality for tasks with associated endpoints. - Updated Swagger documentation for tasks API. feat: add JwtToken model to Prisma schema - Created `JwtToken` model to store JWT tokens with relevant fields. - Added necessary migrations for the new model. chore: update server to include new routes - Integrated dashboard and tasks routes into the main server file. - Set trust proxy for rate limiting.
1 parent 93e588a commit b6d3667

File tree

10 files changed

+1701
-34
lines changed

10 files changed

+1701
-34
lines changed

api/middleware/auth.js

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,28 +11,65 @@ export const verifyToken = async (req, res, next) => {
1111
return res.status(401).json({ error: 'Access denied. No token provided.' });
1212
}
1313

14+
// First verify JWT signature and decode
1415
const decoded = jwt.verify(token, process.env.JWT_SECRET);
1516

16-
const user = await prisma.user.findUnique({
17-
where: { id: decoded.userId },
17+
// Check if token exists in database and is not revoked
18+
const dbToken = await prisma.jwtToken.findUnique({
19+
where: { token },
1820
include: {
19-
userOrganizations: {
21+
user: {
2022
include: {
21-
organization: true
23+
organizations: {
24+
include: {
25+
organization: true
26+
}
27+
}
2228
}
2329
}
2430
}
2531
});
2632

27-
if (!user) {
33+
if (!dbToken) {
34+
return res.status(401).json({ error: 'Invalid token. Token not found.' });
35+
}
36+
37+
if (dbToken.isRevoked) {
38+
return res.status(401).json({ error: 'Token has been revoked.' });
39+
}
40+
41+
if (dbToken.expiresAt < new Date()) {
42+
// Mark token as expired in database
43+
await prisma.jwtToken.update({
44+
where: { id: dbToken.id },
45+
data: { isRevoked: true }
46+
});
47+
return res.status(401).json({ error: 'Token has expired.' });
48+
}
49+
50+
if (!dbToken.user) {
2851
return res.status(401).json({ error: 'Invalid token. User not found.' });
2952
}
3053

31-
req.user = user;
32-
req.userId = user.id;
54+
// Update last used timestamp
55+
await prisma.jwtToken.update({
56+
where: { id: dbToken.id },
57+
data: { lastUsedAt: new Date() }
58+
});
59+
60+
req.user = dbToken.user;
61+
req.userId = dbToken.user.id;
62+
req.tokenId = dbToken.id;
3363
next();
3464
} catch (error) {
35-
return res.status(401).json({ error: 'Invalid token.' });
65+
if (error.name === 'JsonWebTokenError') {
66+
return res.status(401).json({ error: 'Invalid token format.' });
67+
}
68+
if (error.name === 'TokenExpiredError') {
69+
return res.status(401).json({ error: 'Token has expired.' });
70+
}
71+
console.error('Token verification error:', error);
72+
return res.status(401).json({ error: 'Token validation failed.' });
3673
}
3774
};
3875

@@ -44,7 +81,7 @@ export const requireOrganization = async (req, res, next) => {
4481
return res.status(400).json({ error: 'Organization ID is required in X-Organization-ID header.' });
4582
}
4683

47-
const userOrg = req.user.userOrganizations.find(
84+
const userOrg = req.user.organizations.find(
4885
uo => uo.organizationId === organizationId
4986
);
5087

api/routes/auth.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,22 @@ router.post('/google', async (req, res) => {
183183
{ expiresIn: process.env.JWT_EXPIRES_IN || '24h' }
184184
);
185185

186+
// Calculate expiration date
187+
const expiresIn = process.env.JWT_EXPIRES_IN || '24h';
188+
const expirationHours = expiresIn.includes('h') ? parseInt(expiresIn) : 24;
189+
const expiresAt = new Date(Date.now() + expirationHours * 60 * 60 * 1000);
190+
191+
// Store JWT token in database
192+
await prisma.jwtToken.create({
193+
data: {
194+
token: JWTtoken,
195+
userId: user.id,
196+
expiresAt: expiresAt,
197+
deviceInfo: req.get('User-Agent'),
198+
ipAddress: req.ip || req.socket.remoteAddress
199+
}
200+
});
201+
186202
// Format response to match SvelteKit patterns
187203
const userResponse = {
188204
id: user.id,
@@ -210,4 +226,98 @@ router.post('/google', async (req, res) => {
210226
}
211227
});
212228

229+
/**
230+
* @swagger
231+
* /auth/logout:
232+
* post:
233+
* summary: Logout and revoke current JWT token
234+
* tags: [Authentication]
235+
* security:
236+
* - bearerAuth: []
237+
* responses:
238+
* 200:
239+
* description: Successfully logged out
240+
* content:
241+
* application/json:
242+
* schema:
243+
* type: object
244+
* properties:
245+
* success:
246+
* type: boolean
247+
* message:
248+
* type: string
249+
* 401:
250+
* description: Unauthorized
251+
*/
252+
router.post('/logout', verifyToken, async (req, res) => {
253+
try {
254+
// Revoke the current token
255+
await prisma.jwtToken.update({
256+
where: { id: req.tokenId },
257+
data: {
258+
isRevoked: true,
259+
updatedAt: new Date()
260+
}
261+
});
262+
263+
res.json({
264+
success: true,
265+
message: 'Successfully logged out'
266+
});
267+
} catch (error) {
268+
console.error('Logout error:', error);
269+
res.status(500).json({ error: 'Internal server error' });
270+
}
271+
});
272+
273+
/**
274+
* @swagger
275+
* /auth/revoke-all:
276+
* post:
277+
* summary: Revoke all JWT tokens for current user
278+
* tags: [Authentication]
279+
* security:
280+
* - bearerAuth: []
281+
* responses:
282+
* 200:
283+
* description: Successfully revoked all tokens
284+
* content:
285+
* application/json:
286+
* schema:
287+
* type: object
288+
* properties:
289+
* success:
290+
* type: boolean
291+
* message:
292+
* type: string
293+
* revokedCount:
294+
* type: integer
295+
* 401:
296+
* description: Unauthorized
297+
*/
298+
router.post('/revoke-all', verifyToken, async (req, res) => {
299+
try {
300+
// Revoke all tokens for the user
301+
const result = await prisma.jwtToken.updateMany({
302+
where: {
303+
userId: req.userId,
304+
isRevoked: false
305+
},
306+
data: {
307+
isRevoked: true,
308+
updatedAt: new Date()
309+
}
310+
});
311+
312+
res.json({
313+
success: true,
314+
message: 'Successfully revoked all tokens',
315+
revokedCount: result.count
316+
});
317+
} catch (error) {
318+
console.error('Revoke all tokens error:', error);
319+
res.status(500).json({ error: 'Internal server error' });
320+
}
321+
});
322+
213323
export default router;

0 commit comments

Comments
 (0)