strapi/tests/api/core/admin/admin-access-token-exchange.test.api.ts

177 lines
6.0 KiB
TypeScript
Raw Normal View History

'use strict';
import { createStrapiInstance, superAdmin } from 'api-tests/strapi';
import { createRequest } from 'api-tests/request';
import jwt from 'jsonwebtoken';
describe('Admin Access Token Exchange', () => {
let strapi: any;
const cookieName = 'strapi_admin_refresh';
const decode = (token: string): any => {
const secret = strapi.config.get('admin.auth.secret');
return jwt.verify(token, secret);
};
beforeAll(async () => {
strapi = await createStrapiInstance({
bootstrap: async ({ strapi: s }: any) => {
s.config.set('admin.rateLimit.enabled', false);
},
});
});
afterAll(async () => {
await strapi.destroy();
});
const getCookie = (res: any, name: string): string | undefined => {
const setCookies: string[] = res.headers['set-cookie'] || [];
return setCookies.find((c) => c.startsWith(`${name}=`));
};
it('returns 200 with an access token when using refresh cookie', async () => {
const rq = createRequest({ strapi });
// login and capture refresh cookie
const loginRes = await rq.post('/admin/login', { body: superAdmin.loginInfo });
expect(loginRes.statusCode).toBe(200);
const setCookies: string[] = loginRes.headers['set-cookie'] || [];
const refreshCookie = setCookies.find((c) => c.startsWith(`strapi_admin_refresh=`));
expect(refreshCookie).toBeDefined();
const cookiePair = refreshCookie!.split(';')[0];
const cookieRq = createRequest({ strapi });
// Forward the cookie header explicitly.
const res = await cookieRq.post('/admin/access-token', { headers: { Cookie: cookiePair } });
expect(res.statusCode).toBe(200);
const token = res.body?.data?.token;
expect(token).toEqual(expect.any(String));
const payload = decode(token);
expect(payload).toMatchObject({
type: 'access',
userId: expect.any(String),
sessionId: expect.any(String),
});
});
it('returns 401 for missing token (no cookie and no body)', async () => {
const rq = createRequest({ strapi });
const res = await rq.post('/admin/access-token');
expect(res.statusCode).toBe(401);
});
it('returns 401 when idle window elapsed since parent token creation', async () => {
const rq = createRequest({ strapi });
const loginRes = await rq.post('/admin/login', { body: superAdmin.loginInfo });
expect(loginRes.statusCode).toBe(200);
const refreshCookie = getCookie(loginRes, cookieName)!;
const pair = refreshCookie.split(';')[0];
const refreshToken = pair.split('=')[1];
const payload = decode(refreshToken);
const sessionId = payload.sessionId as string;
await strapi.db.query('admin::session').update({
where: { sessionId },
data: { createdAt: new Date(Date.now() - (7 * 24 * 60 * 60 * 1000 + 60 * 1000)) },
});
const res = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: pair },
});
expect(res.statusCode).toBe(401);
});
it('returns 401 when max family window elapsed', async () => {
const rq = createRequest({ strapi });
const loginRes = await rq.post('/admin/login', { body: superAdmin.loginInfo });
expect(loginRes.statusCode).toBe(200);
const refreshCookie = getCookie(loginRes, cookieName)!;
const pair = refreshCookie.split(';')[0];
const refreshToken = pair.split('=')[1];
const payload = decode(refreshToken);
const sessionId = payload.sessionId as string;
await strapi.db.query('admin::session').update({
where: { sessionId },
data: { absoluteExpiresAt: new Date(Date.now() - 1000) },
});
const res = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: pair },
});
expect(res.statusCode).toBe(401);
});
it('rotates refresh token and preserves remember-me cookie persistence', async () => {
const rq = createRequest({ strapi });
// Login with rememberMe=true -> persistent cookie expected
const loginRes = await rq.post('/admin/login', {
body: { ...superAdmin.loginInfo, rememberMe: true },
});
expect(loginRes.statusCode).toBe(200);
const initialCookie = getCookie(loginRes, cookieName)!;
expect(initialCookie).toMatch(/expires=/i);
// Exchange for an access token (this should rotate refresh token)
const pair = initialCookie.split(';')[0];
const tokenRes = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: pair },
});
expect(tokenRes.statusCode).toBe(200);
// Set-Cookie should carry a persistent expiry after rotation
const rotatedCookie = getCookie(tokenRes, cookieName)!;
expect(rotatedCookie).toBeDefined();
expect(rotatedCookie).toMatch(/expires=/i);
// Token string in cookie should change after rotation
expect(rotatedCookie.split(';')[0]).not.toBe(pair);
});
it('rotates refresh token and keeps session cookie when rememberMe=false', async () => {
const rq = createRequest({ strapi });
// Login without rememberMe -> session cookie expected
const loginRes = await rq.post('/admin/login', {
body: { ...superAdmin.loginInfo, rememberMe: false },
});
expect(loginRes.statusCode).toBe(200);
const cookie = getCookie(loginRes, cookieName)!;
expect(cookie).toBeDefined();
expect(cookie).not.toMatch(/expires=/i);
// Exchange for an access token; rotated cookie should also be a session cookie
const pair = cookie.split(';')[0];
const tokenRes = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: pair },
});
expect(tokenRes.statusCode).toBe(200);
const rotated = getCookie(tokenRes, cookieName)!;
expect(rotated).toBeDefined();
expect(rotated).not.toMatch(/expires=/i);
// Token string in cookie should change after rotation
expect(rotated.split(';')[0]).not.toBe(pair);
});
it('returns 401 when refresh cookie is invalid', async () => {
const res = await createRequest({ strapi }).post('/admin/access-token', {
headers: { Cookie: `${cookieName}=invalid.jwt` },
});
expect(res.statusCode).toBe(401);
});
});