355 lines
9.8 KiB
JavaScript
355 lines
9.8 KiB
JavaScript
/**
|
|
* Tests for Logout and Token Verification Endpoints
|
|
* Task 14: User Logout & Token Verification
|
|
*/
|
|
|
|
const request = require('supertest');
|
|
const app = require('../server');
|
|
const { User } = require('../models');
|
|
const jwt = require('jsonwebtoken');
|
|
const config = require('../config/config');
|
|
|
|
describe('POST /api/auth/logout', () => {
|
|
test('Should logout successfully (stateless JWT approach)', async () => {
|
|
const response = await request(app)
|
|
.post('/api/auth/logout')
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.message).toContain('Logout successful');
|
|
});
|
|
|
|
test('Should return success even without token (stateless approach)', async () => {
|
|
// In a stateless JWT system, logout is client-side only
|
|
const response = await request(app)
|
|
.post('/api/auth/logout')
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('GET /api/auth/verify', () => {
|
|
let testUser;
|
|
let validToken;
|
|
|
|
beforeAll(async () => {
|
|
// Create a test user
|
|
testUser = await User.create({
|
|
username: 'verifyuser',
|
|
email: 'verify@test.com',
|
|
password: 'Test@123',
|
|
role: 'user'
|
|
});
|
|
|
|
// Generate valid token
|
|
validToken = jwt.sign(
|
|
{
|
|
userId: testUser.id,
|
|
email: testUser.email,
|
|
username: testUser.username,
|
|
role: testUser.role
|
|
},
|
|
config.jwt.secret,
|
|
{ expiresIn: config.jwt.expire }
|
|
);
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Cleanup
|
|
if (testUser) {
|
|
await testUser.destroy({ force: true });
|
|
}
|
|
});
|
|
|
|
test('Should verify valid token and return user info', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${validToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.message).toBe('Token valid');
|
|
expect(response.body.data.user).toBeDefined();
|
|
expect(response.body.data.user.id).toBe(testUser.id);
|
|
expect(response.body.data.user.email).toBe(testUser.email);
|
|
expect(response.body.data.user.username).toBe(testUser.username);
|
|
// Password should not be included
|
|
expect(response.body.data.user.password).toBeUndefined();
|
|
});
|
|
|
|
test('Should reject request without token', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('No token provided');
|
|
});
|
|
|
|
test('Should reject invalid token', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', 'Bearer invalid_token_here')
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('Invalid token');
|
|
});
|
|
|
|
test('Should reject expired token', async () => {
|
|
// Create an expired token
|
|
const expiredToken = jwt.sign(
|
|
{
|
|
userId: testUser.id,
|
|
email: testUser.email,
|
|
username: testUser.username,
|
|
role: testUser.role
|
|
},
|
|
config.jwt.secret,
|
|
{ expiresIn: '0s' } // Immediately expired
|
|
);
|
|
|
|
// Wait a moment to ensure expiration
|
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${expiredToken}`)
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('expired');
|
|
});
|
|
|
|
test('Should reject token with invalid format (no Bearer prefix)', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', validToken) // Missing "Bearer " prefix
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
});
|
|
|
|
test('Should reject token for inactive user', async () => {
|
|
// Deactivate the user
|
|
await testUser.update({ is_active: false });
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${validToken}`)
|
|
.expect(404);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('not found or inactive');
|
|
|
|
// Reactivate for cleanup
|
|
await testUser.update({ is_active: true });
|
|
});
|
|
|
|
test('Should reject token for non-existent user', async () => {
|
|
// Create token with non-existent user ID
|
|
const fakeToken = jwt.sign(
|
|
{
|
|
userId: '00000000-0000-0000-0000-000000000000',
|
|
email: 'fake@test.com',
|
|
username: 'fakeuser',
|
|
role: 'user'
|
|
},
|
|
config.jwt.secret,
|
|
{ expiresIn: config.jwt.expire }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${fakeToken}`)
|
|
.expect(404);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('User not found');
|
|
});
|
|
|
|
test('Should handle malformed Authorization header', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', 'InvalidFormat')
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('Token Verification Integration Tests', () => {
|
|
let registeredUser;
|
|
let userToken;
|
|
|
|
beforeAll(async () => {
|
|
// Register a new user
|
|
const registerResponse = await request(app)
|
|
.post('/api/auth/register')
|
|
.send({
|
|
username: `integrationuser_${Date.now()}`,
|
|
email: `integration_${Date.now()}@test.com`,
|
|
password: 'Test@123'
|
|
})
|
|
.expect(201);
|
|
|
|
registeredUser = registerResponse.body.data.user;
|
|
userToken = registerResponse.body.data.token;
|
|
});
|
|
|
|
afterAll(async () => {
|
|
// Cleanup
|
|
if (registeredUser && registeredUser.id) {
|
|
const user = await User.findByPk(registeredUser.id);
|
|
if (user) {
|
|
await user.destroy({ force: true });
|
|
}
|
|
}
|
|
});
|
|
|
|
test('Should verify token immediately after registration', async () => {
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${userToken}`)
|
|
.expect(200);
|
|
|
|
expect(response.body.success).toBe(true);
|
|
expect(response.body.data.user.id).toBe(registeredUser.id);
|
|
});
|
|
|
|
test('Should verify token after login', async () => {
|
|
// Login with the registered user
|
|
const loginResponse = await request(app)
|
|
.post('/api/auth/login')
|
|
.send({
|
|
email: registeredUser.email,
|
|
password: 'Test@123'
|
|
})
|
|
.expect(200);
|
|
|
|
const loginToken = loginResponse.body.data.token;
|
|
|
|
// Verify the login token
|
|
const verifyResponse = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${loginToken}`)
|
|
.expect(200);
|
|
|
|
expect(verifyResponse.body.success).toBe(true);
|
|
expect(verifyResponse.body.data.user.id).toBe(registeredUser.id);
|
|
});
|
|
|
|
test('Should complete full auth flow: register -> verify -> logout', async () => {
|
|
// 1. Register
|
|
const registerResponse = await request(app)
|
|
.post('/api/auth/register')
|
|
.send({
|
|
username: `flowuser_${Date.now()}`,
|
|
email: `flow_${Date.now()}@test.com`,
|
|
password: 'Test@123'
|
|
})
|
|
.expect(201);
|
|
|
|
const token = registerResponse.body.data.token;
|
|
const userId = registerResponse.body.data.user.id;
|
|
|
|
// 2. Verify token
|
|
const verifyResponse = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${token}`)
|
|
.expect(200);
|
|
|
|
expect(verifyResponse.body.success).toBe(true);
|
|
|
|
// 3. Logout
|
|
const logoutResponse = await request(app)
|
|
.post('/api/auth/logout')
|
|
.expect(200);
|
|
|
|
expect(logoutResponse.body.success).toBe(true);
|
|
|
|
// 4. Token should still be valid (stateless JWT)
|
|
// In a real app, client would delete the token
|
|
const verifyAfterLogout = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${token}`)
|
|
.expect(200);
|
|
|
|
expect(verifyAfterLogout.body.success).toBe(true);
|
|
|
|
// Cleanup
|
|
const user = await User.findByPk(userId);
|
|
if (user) {
|
|
await user.destroy({ force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Token Security Tests', () => {
|
|
test('Should reject token signed with wrong secret', async () => {
|
|
const fakeToken = jwt.sign(
|
|
{
|
|
userId: '12345',
|
|
email: 'fake@test.com',
|
|
username: 'fakeuser',
|
|
role: 'user'
|
|
},
|
|
'wrong_secret_key',
|
|
{ expiresIn: '24h' }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${fakeToken}`)
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
expect(response.body.message).toContain('Invalid token');
|
|
});
|
|
|
|
test('Should reject tampered token', async () => {
|
|
// Create a valid token
|
|
const validToken = jwt.sign(
|
|
{
|
|
userId: '12345',
|
|
email: 'test@test.com',
|
|
username: 'testuser',
|
|
role: 'user'
|
|
},
|
|
config.jwt.secret,
|
|
{ expiresIn: '24h' }
|
|
);
|
|
|
|
// Tamper with the token by changing a character
|
|
const tamperedToken = validToken.slice(0, -5) + 'XXXXX';
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${tamperedToken}`)
|
|
.expect(401);
|
|
|
|
expect(response.body.success).toBe(false);
|
|
});
|
|
|
|
test('Should reject token with missing payload fields', async () => {
|
|
// Create token with incomplete payload
|
|
const incompleteToken = jwt.sign(
|
|
{
|
|
userId: '12345'
|
|
// Missing email, username, role
|
|
},
|
|
config.jwt.secret,
|
|
{ expiresIn: '24h' }
|
|
);
|
|
|
|
const response = await request(app)
|
|
.get('/api/auth/verify')
|
|
.set('Authorization', `Bearer ${incompleteToken}`)
|
|
.expect(404);
|
|
|
|
// Token is valid but user doesn't exist
|
|
expect(response.body.success).toBe(false);
|
|
});
|
|
});
|