strapi/tests/api/core/admin/admin-legacy-migration.test.api.ts
2025-09-23 12:04:29 +02:00

181 lines
6.1 KiB
TypeScript

'use strict';
import { createStrapiInstance, superAdmin } from 'api-tests/strapi';
import { createRequest } from 'api-tests/request';
import jwt from 'jsonwebtoken';
describe('Legacy Token Migration on Version Update', () => {
let strapi: any;
let rq: any;
beforeAll(async () => {
strapi = await createStrapiInstance();
rq = createRequest({ strapi });
});
afterAll(async () => {
await strapi.destroy();
});
const createLegacyToken = (payload: any = {}): string => {
const secret = strapi.config.get('admin.auth.secret');
const defaultPayload = {
userId: 1,
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
...payload,
};
return jwt.sign(defaultPayload, secret);
};
describe('Legacy JWT rejection', () => {
it('should reject old-format JWT tokens without type field', async () => {
const legacyToken = createLegacyToken(); // No 'type' field
const res = await rq.setToken(legacyToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should reject tokens with invalid type field', async () => {
const invalidToken = createLegacyToken({ type: 'legacy' });
const res = await rq.setToken(invalidToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should reject tokens with wrong type (refresh used as access)', async () => {
const refreshTypeToken = createLegacyToken({ type: 'refresh' });
const res = await rq.setToken(refreshTypeToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should reject malformed tokens', async () => {
const malformedToken = 'invalid.jwt.token';
const res = await rq.setToken(malformedToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
});
describe('Simulated post-upgrade authentication flow', () => {
it('should force re-authentication after upgrade simulation', async () => {
// Simulate legacy token stored in client
const legacyToken = createLegacyToken({ userId: 1 });
// Legacy token should be rejected
const legacyRes = await rq.setToken(legacyToken).get('/admin/users/me');
expect(legacyRes.statusCode).toBe(401);
// Fresh login should work with new session system
const loginRes = await rq.post('/admin/login', {
body: superAdmin.loginInfo,
});
expect(loginRes.statusCode).toBe(200);
expect(loginRes.body.data.token).toBeDefined(); // New access token
expect(loginRes.headers['set-cookie']).toBeDefined(); // Refresh cookie
// New token should work for API calls
const newRes = await rq.setToken(loginRes.body.data.accessToken).get('/admin/users/me');
expect(newRes.statusCode).toBe(200);
});
it('should maintain session persistence across requests', async () => {
const loginRes = await rq.post('/admin/login', {
body: { ...superAdmin.loginInfo, rememberMe: true },
});
const accessToken = loginRes.body.data.accessToken;
const cookieHeader = loginRes.headers['set-cookie'][0];
// Multiple API calls should work
const res1 = await rq.setToken(accessToken).get('/admin/users/me');
const res2 = await rq.setToken(accessToken).get('/admin/roles');
expect(res1.statusCode).toBe(200);
expect(res2.statusCode).toBe(200);
// Token exchange should work with refresh cookie
const tokenRes = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: cookieHeader },
});
expect(tokenRes.statusCode).toBe(200);
expect(tokenRes.body.data.token).toBeDefined();
});
});
describe('Malformed tokens', () => {
it('should handle expired legacy tokens gracefully', async () => {
const expiredToken = createLegacyToken({
exp: Math.floor(Date.now() / 1000) - 3600, // Expired 1 hour ago
});
const res = await rq.setToken(expiredToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should handle tokens with missing required fields', async () => {
const incompleteToken = createLegacyToken({ userId: undefined });
const res = await rq.setToken(incompleteToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should reject tokens signed with wrong secret', async () => {
const wrongSecretToken = jwt.sign(
{ userId: 1, type: 'access', exp: Math.floor(Date.now() / 1000) + 3600 },
'wrong-secret'
);
const res = await rq.setToken(wrongSecretToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
});
describe('Session table validation', () => {
it('should validate access tokens against active sessions', async () => {
// Login and get tokens
const loginRes = await rq.post('/admin/login', {
body: superAdmin.loginInfo,
});
const accessToken = loginRes.body.data.accessToken;
// Decode to get sessionId
const secret = strapi.config.get('admin.auth.secret');
const decoded = jwt.verify(accessToken, secret) as any;
const sessionId = decoded.sessionId;
// Verify session exists in database
const session = await strapi.db.query('admin::session').findOne({ where: { sessionId } });
expect(session).toBeTruthy();
expect(session.status).toBe('active');
// Delete session from database
await strapi.db.query('admin::session').delete({ where: { sessionId } });
// Access token should now be invalid
const res = await rq.setToken(accessToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
it('should reject tokens with non-existent sessionId', async () => {
// Create valid JWT but with fake sessionId
const secret = strapi.config.get('admin.auth.secret');
const fakeToken = jwt.sign(
{
userId: '1',
sessionId: 'non-existent-session-id',
type: 'access',
},
secret,
{ expiresIn: '1h' }
);
const res = await rq.setToken(fakeToken).get('/admin/users/me');
expect(res.statusCode).toBe(401);
});
});
});