Fix jwt-token
This commit is contained in:
@@ -52,12 +52,24 @@ api.interceptors.response.use(
|
|||||||
if (error.response?.status === 401 || error.response?.status === 403) {
|
if (error.response?.status === 401 || error.response?.status === 403) {
|
||||||
// Check if it's a token-related error
|
// Check if it's a token-related error
|
||||||
const errorMessage = error.response?.data?.message || '';
|
const errorMessage = error.response?.data?.message || '';
|
||||||
if (errorMessage.includes('token') || errorMessage.includes('expired') || error.response?.status === 401) {
|
if (errorMessage.includes('token') || errorMessage.includes('expired') ||
|
||||||
console.warn('🔐 Token expired or invalid - logging out');
|
errorMessage.includes('invalid') || errorMessage.includes('required') ||
|
||||||
// Token expired or invalid - remove token and let ProtectedRoute handle navigation
|
error.response?.status === 401) {
|
||||||
|
console.warn('🔐 Authentication failed:', errorMessage);
|
||||||
|
console.warn('🔐 Redirecting to login page');
|
||||||
|
|
||||||
|
// Clear authentication data
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
// Force a state update by dispatching a custom event
|
localStorage.removeItem('user');
|
||||||
|
|
||||||
|
// Dispatch logout event for components that need to react
|
||||||
window.dispatchEvent(new CustomEvent('auth-logout'));
|
window.dispatchEvent(new CustomEvent('auth-logout'));
|
||||||
|
|
||||||
|
// Redirect to login page
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
if (currentPath !== '/login' && currentPath !== '/') {
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|||||||
@@ -27,9 +27,17 @@ api.interceptors.response.use(
|
|||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error.response?.status === 401) {
|
if (error.response?.status === 401) {
|
||||||
|
const errorData = error.response.data;
|
||||||
|
console.warn('🔐 Authentication failed:', errorData?.message || 'Unknown error');
|
||||||
|
|
||||||
|
// Clear authentication data
|
||||||
localStorage.removeItem('management_token')
|
localStorage.removeItem('management_token')
|
||||||
localStorage.removeItem('management_user')
|
localStorage.removeItem('management_user')
|
||||||
window.location.href = '/login'
|
|
||||||
|
// Check if the backend indicates we should redirect to login
|
||||||
|
if (errorData?.redirectToLogin !== false) {
|
||||||
|
window.location.href = '/login'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return Promise.reject(error)
|
return Promise.reject(error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,22 +104,51 @@ async function authenticateToken(req, res, next) {
|
|||||||
|
|
||||||
next();
|
next();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Only log unexpected errors, not common JWT validation failures
|
// Log authentication errors for monitoring (but not in tests)
|
||||||
if (process.env.NODE_ENV !== 'test' || error.name === 'TypeError') {
|
if (process.env.NODE_ENV !== 'test' || error.name === 'TypeError') {
|
||||||
console.error('Token verification error:', error);
|
console.error('🔐 Authentication error:', {
|
||||||
}
|
error: error.name,
|
||||||
|
message: error.message,
|
||||||
// Handle specific JWT errors
|
userAgent: req.headers['user-agent'],
|
||||||
if (error.name === 'TokenExpiredError') {
|
ip: req.ip || req.connection.remoteAddress,
|
||||||
return res.status(401).json({
|
path: req.path
|
||||||
success: false,
|
|
||||||
message: 'Token expired'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle specific JWT errors with detailed responses
|
||||||
|
if (error.name === 'TokenExpiredError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
error: 'TOKEN_EXPIRED',
|
||||||
|
message: 'Token expired',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.name === 'JsonWebTokenError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
error: 'INVALID_TOKEN',
|
||||||
|
message: 'Invalid token',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.name === 'NotBeforeError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
error: 'TOKEN_NOT_ACTIVE',
|
||||||
|
message: 'Token not active',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic authentication error
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
success: false,
|
success: false,
|
||||||
message: 'Invalid token'
|
error: 'AUTHENTICATION_FAILED',
|
||||||
|
message: 'Authentication failed',
|
||||||
|
redirectToLogin: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
285
server/tests/integration/auth-security.test.js
Normal file
285
server/tests/integration/auth-security.test.js
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
// CRITICAL: Set environment variables FIRST
|
||||||
|
process.env.NODE_ENV = 'test';
|
||||||
|
process.env.JWT_SECRET = 'test-jwt-secret-key-for-testing-only';
|
||||||
|
|
||||||
|
const { describe, it, beforeEach, afterEach, before, after } = require('mocha');
|
||||||
|
const { expect } = require('chai');
|
||||||
|
const request = require('supertest');
|
||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const { setupTestEnvironment, teardownTestEnvironment, cleanDatabase, createTestUser, createTestTenant } = require('../setup');
|
||||||
|
|
||||||
|
describe('Authentication Integration Tests', () => {
|
||||||
|
let app, models, sequelize, user, tenant;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
({ app, models, sequelize } = await setupTestEnvironment());
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
await teardownTestEnvironment();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await cleanDatabase();
|
||||||
|
tenant = await createTestTenant({ slug: 'test-tenant' });
|
||||||
|
user = await createTestUser({
|
||||||
|
username: 'testuser',
|
||||||
|
role: 'admin',
|
||||||
|
tenant_id: tenant.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Protected Route Access', () => {
|
||||||
|
it('should deny access to protected route without token', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'Access token required'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access with malformed authorization header', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', 'InvalidToken')
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid token format'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access with expired token', async () => {
|
||||||
|
const expiredToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '-1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${expiredToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
error: 'TOKEN_EXPIRED',
|
||||||
|
message: 'Token expired',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access with invalid token signature', async () => {
|
||||||
|
const invalidToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
|
'wrong-secret',
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${invalidToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
error: 'INVALID_TOKEN',
|
||||||
|
message: 'Invalid token',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access for non-existent user', async () => {
|
||||||
|
const fakeToken = jwt.sign(
|
||||||
|
{ userId: 'non-existent-id', username: 'fake', role: 'admin' },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${fakeToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'User not found'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access for inactive user', async () => {
|
||||||
|
// Deactivate the user
|
||||||
|
await user.update({ is_active: false });
|
||||||
|
|
||||||
|
const validToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${validToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'User account is inactive'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access with valid token', async () => {
|
||||||
|
const validToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role, tenantId: tenant.slug },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${validToken}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.true;
|
||||||
|
expect(response.body.data).to.have.property('username', user.username);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Role-Based Access Control', () => {
|
||||||
|
let adminUser, regularUser, adminToken, userToken;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
adminUser = await createTestUser({
|
||||||
|
username: 'admin',
|
||||||
|
role: 'admin',
|
||||||
|
tenant_id: tenant.id
|
||||||
|
});
|
||||||
|
regularUser = await createTestUser({
|
||||||
|
username: 'user',
|
||||||
|
role: 'user',
|
||||||
|
tenant_id: tenant.id
|
||||||
|
});
|
||||||
|
|
||||||
|
adminToken = jwt.sign(
|
||||||
|
{ userId: adminUser.id, username: adminUser.username, role: adminUser.role, tenantId: tenant.slug },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
userToken = jwt.sign(
|
||||||
|
{ userId: regularUser.id, username: regularUser.username, role: regularUser.role, tenantId: tenant.slug },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow admin access to admin-only endpoints', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/health/metrics')
|
||||||
|
.set('Authorization', `Bearer ${adminToken}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny regular user access to admin-only endpoints', async () => {
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/health/metrics')
|
||||||
|
.set('Authorization', `Bearer ${userToken}`)
|
||||||
|
.expect(403);
|
||||||
|
|
||||||
|
expect(response.body).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'Insufficient permissions'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Token Security Validation', () => {
|
||||||
|
it('should reject token with incorrect algorithm', async () => {
|
||||||
|
// Create token with different algorithm (if supported by library)
|
||||||
|
const maliciousToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: 'admin' },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ algorithm: 'none' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${maliciousToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.false;
|
||||||
|
expect(response.body.error).to.equal('INVALID_TOKEN');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reject tampered token payload', async () => {
|
||||||
|
const validToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: 'user' },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tamper with the token by modifying the payload
|
||||||
|
const parts = validToken.split('.');
|
||||||
|
const tamperedPayload = Buffer.from('{"userId":"' + user.id + '","username":"' + user.username + '","role":"admin"}').toString('base64');
|
||||||
|
const tamperedToken = parts[0] + '.' + tamperedPayload + '.' + parts[2];
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${tamperedToken}`)
|
||||||
|
.expect(401);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.false;
|
||||||
|
expect(response.body.error).to.equal('INVALID_TOKEN');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Tenant Context Validation', () => {
|
||||||
|
let otherTenant, otherUser;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
otherTenant = await createTestTenant({ slug: 'other-tenant' });
|
||||||
|
otherUser = await createTestUser({
|
||||||
|
username: 'otheruser',
|
||||||
|
role: 'admin',
|
||||||
|
tenant_id: otherTenant.id
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set correct tenant context from JWT', async () => {
|
||||||
|
const validToken = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role, tenantId: tenant.slug },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
// Make a request that would show tenant context in logs
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/devices')
|
||||||
|
.set('Authorization', `Bearer ${validToken}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing tenant context gracefully', async () => {
|
||||||
|
const tokenWithoutTenant = jwt.sign(
|
||||||
|
{ userId: user.id, username: user.username, role: user.role },
|
||||||
|
process.env.JWT_SECRET,
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const response = await request(app)
|
||||||
|
.get('/api/users/profile')
|
||||||
|
.set('Authorization', `Bearer ${tokenWithoutTenant}`)
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(response.body.success).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -188,5 +188,179 @@ describe('Authentication Middleware', () => {
|
|||||||
expect(res.data.success).to.be.false;
|
expect(res.data.success).to.be.false;
|
||||||
expect(res.data.message).to.equal('User account is inactive');
|
expect(res.data.message).to.equal('User account is inactive');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle malformed JWT token', async () => {
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: 'Bearer malformed.jwt' }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data.success).to.be.false;
|
||||||
|
expect(res.data.error).to.equal('INVALID_TOKEN');
|
||||||
|
expect(res.data.redirectToLogin).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle JWT with invalid signature', async () => {
|
||||||
|
const invalidToken = jwt.sign(
|
||||||
|
{ userId: 1, username: 'test' },
|
||||||
|
'wrong-secret',
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: `Bearer ${invalidToken}` }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data.success).to.be.false;
|
||||||
|
expect(res.data.error).to.equal('INVALID_TOKEN');
|
||||||
|
expect(res.data.redirectToLogin).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle JWT not before error', async () => {
|
||||||
|
const futureToken = jwt.sign(
|
||||||
|
{ userId: 1, username: 'test', nbf: Math.floor(Date.now() / 1000) + 3600 },
|
||||||
|
process.env.JWT_SECRET || 'test-secret',
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: `Bearer ${futureToken}` }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data.success).to.be.false;
|
||||||
|
expect(res.data.error).to.equal('TOKEN_NOT_ACTIVE');
|
||||||
|
expect(res.data.redirectToLogin).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle missing JWT_SECRET environment variable', async () => {
|
||||||
|
const originalSecret = process.env.JWT_SECRET;
|
||||||
|
delete process.env.JWT_SECRET;
|
||||||
|
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ userId: 1, username: 'test' },
|
||||||
|
'any-secret',
|
||||||
|
{ expiresIn: '1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: `Bearer ${token}` }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data.success).to.be.false;
|
||||||
|
expect(res.data.error).to.equal('AUTHENTICATION_FAILED');
|
||||||
|
expect(res.data.redirectToLogin).to.be.true;
|
||||||
|
|
||||||
|
// Restore original secret
|
||||||
|
process.env.JWT_SECRET = originalSecret;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should include error details for monitoring', async () => {
|
||||||
|
const originalEnv = process.env.NODE_ENV;
|
||||||
|
process.env.NODE_ENV = 'production';
|
||||||
|
|
||||||
|
const consoleSpy = sinon.spy(console, 'error');
|
||||||
|
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: {
|
||||||
|
authorization: 'Bearer invalid.jwt.token',
|
||||||
|
'user-agent': 'Test Agent',
|
||||||
|
},
|
||||||
|
ip: '127.0.0.1',
|
||||||
|
path: '/api/test',
|
||||||
|
connection: { remoteAddress: '127.0.0.1' }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(consoleSpy.calledOnce).to.be.true;
|
||||||
|
const logCall = consoleSpy.firstCall.args;
|
||||||
|
expect(logCall[0]).to.equal('🔐 Authentication error:');
|
||||||
|
expect(logCall[1]).to.have.property('error');
|
||||||
|
expect(logCall[1]).to.have.property('userAgent', 'Test Agent');
|
||||||
|
expect(logCall[1]).to.have.property('ip', '127.0.0.1');
|
||||||
|
expect(logCall[1]).to.have.property('path', '/api/test');
|
||||||
|
|
||||||
|
consoleSpy.restore();
|
||||||
|
process.env.NODE_ENV = originalEnv;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Enhanced Error Response Format', () => {
|
||||||
|
it('should return consistent error format for expired tokens', async () => {
|
||||||
|
const expiredToken = jwt.sign(
|
||||||
|
{ userId: 1, username: 'test' },
|
||||||
|
process.env.JWT_SECRET || 'test-secret',
|
||||||
|
{ expiresIn: '-1h' }
|
||||||
|
);
|
||||||
|
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: `Bearer ${expiredToken}` }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
error: 'TOKEN_EXPIRED',
|
||||||
|
message: 'Token expired',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return consistent error format for invalid tokens', async () => {
|
||||||
|
const req = mockRequest({
|
||||||
|
headers: { authorization: 'Bearer invalid.jwt.token' }
|
||||||
|
});
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
error: 'INVALID_TOKEN',
|
||||||
|
message: 'Invalid token',
|
||||||
|
redirectToLogin: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return consistent error format for missing tokens', async () => {
|
||||||
|
const req = mockRequest();
|
||||||
|
const res = mockResponse();
|
||||||
|
const next = mockNext();
|
||||||
|
|
||||||
|
await authenticateToken(req, res, next);
|
||||||
|
|
||||||
|
expect(res.statusCode).to.equal(401);
|
||||||
|
expect(res.data).to.deep.equal({
|
||||||
|
success: false,
|
||||||
|
message: 'Access token required'
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user