mirror of
https://github.com/strapi/strapi.git
synced 2025-10-13 09:03:25 +00:00
177 lines
6.0 KiB
TypeScript
177 lines
6.0 KiB
TypeScript
'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);
|
|
});
|
|
});
|