mirror of
https://github.com/strapi/strapi.git
synced 2025-10-14 17:43:01 +00:00
181 lines
6.1 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|