strapi/tests/api/core/admin/admin-jwt-configuration.test.api.ts
Ben Irvin 723a2f0c62 fix: support auth.options config in sessions (#24460)
* fix: support auth.options config in sessions

* chore: fix lint

* feat(users-permissions): add jwt algorithm configuration

* refactor: remove deprecated admin.auth.options warnings

* test(token): add TypeScript error suppression for mock config in token tests

* feat(bootstrap): re-add warning for deprecated expiresIn option in session settings

---------

Co-authored-by: Jamie Howard <jhoward1994@gmail.com>
Co-authored-by: Jamie Howard <48524071+jhoward1994@users.noreply.github.com>
2025-09-29 16:58:18 +03:00

305 lines
9.5 KiB
TypeScript

'use strict';
import { createAuthRequest } from 'api-tests/request';
import { createStrapiInstance, superAdmin } from 'api-tests/strapi';
import { createUtils } from 'api-tests/utils';
import jwt from 'jsonwebtoken';
import crypto from 'crypto';
// Generate test RSA key pair
const generateRSAKeyPair = () => {
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
},
});
return { publicKey, privateKey };
};
const { publicKey: testPublicKey, privateKey: testPrivateKey } = generateRSAKeyPair();
describe('Admin JWT Configuration API Tests', () => {
let rq: any;
let strapi: any;
let utils: any;
afterEach(async () => {
if (strapi) {
await strapi.destroy();
strapi = null;
rq = null;
utils = null;
}
});
describe('Default JWT Configuration', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('Uses default HS256 algorithm when no configuration is provided', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify algorithm
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('HS256');
expect(decoded?.header.typ).toBe('JWT');
});
});
describe('Legacy admin.auth.options Configuration', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
s.config.set('admin.auth.options', {
algorithm: 'HS512',
issuer: 'legacy-test-issuer',
audience: 'legacy-test-audience',
});
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('Uses legacy admin.auth.options configuration with deprecation warning', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify configuration
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('HS512');
expect(decoded?.payload.iss).toBe('legacy-test-issuer');
expect(decoded?.payload.aud).toBe('legacy-test-audience');
});
});
describe('New admin.auth.sessions.options Configuration', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
s.config.set('admin.auth.sessions.options', {
algorithm: 'HS384',
issuer: 'sessions-test-issuer',
audience: 'sessions-test-audience',
subject: 'sessions-test-subject',
});
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('Uses new admin.auth.sessions.options configuration', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify configuration
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('HS384');
expect(decoded?.payload.iss).toBe('sessions-test-issuer');
expect(decoded?.payload.aud).toBe('sessions-test-audience');
expect(decoded?.payload.sub).toBe('sessions-test-subject');
});
});
describe('Configuration Priority: sessions.options over auth.options', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
// Set legacy options
s.config.set('admin.auth.options', {
algorithm: 'HS256',
issuer: 'legacy-issuer',
audience: 'legacy-audience',
});
// Set new options (should take priority)
s.config.set('admin.auth.sessions.options', {
algorithm: 'HS512',
issuer: 'sessions-issuer',
audience: 'sessions-audience',
subject: 'sessions-subject',
});
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('sessions.options takes priority over legacy auth.options', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify sessions.options takes priority
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('HS512'); // From sessions.options
expect(decoded?.payload.iss).toBe('sessions-issuer'); // From sessions.options
expect(decoded?.payload.aud).toBe('sessions-audience'); // From sessions.options
expect(decoded?.payload.sub).toBe('sessions-subject'); // From sessions.options
});
});
describe('Asymmetric Algorithm Configuration (RS256)', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
s.config.set('admin.auth.sessions.options', {
algorithm: 'RS256',
privateKey: testPrivateKey,
publicKey: testPublicKey,
issuer: 'rsa-test-issuer',
audience: 'rsa-test-audience',
});
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('Uses RS256 algorithm with RSA key pair', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify configuration
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('RS256');
expect(decoded?.payload.iss).toBe('rsa-test-issuer');
expect(decoded?.payload.aud).toBe('rsa-test-audience');
// Verify the token can be verified with the public key
const verified = jwt.verify(token, testPublicKey, {
algorithms: ['RS256'],
issuer: 'rsa-test-issuer',
audience: 'rsa-test-audience',
});
expect(verified).toBeDefined();
expect(verified.type).toBe('access');
});
test('Access token can be used for authenticated requests', async () => {
// First login to get token
const loginRes = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(loginRes.statusCode).toBe(200);
const token = loginRes.body.data.token;
// Use token for authenticated request
const authRes = await rq({
url: '/admin/users/me',
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
expect(authRes.statusCode).toBe(200);
expect(authRes.body.data).toBeDefined();
});
});
describe('Mixed Configuration Scenarios', () => {
beforeEach(async () => {
strapi = await createStrapiInstance({
async bootstrap({ strapi: s }) {
s.config.set('admin.rateLimit.enabled', false);
// Legacy configuration
s.config.set('admin.auth.options', {
algorithm: 'HS256',
issuer: 'legacy-issuer',
});
// New configuration (should take priority)
s.config.set('admin.auth.sessions.options', {
algorithm: 'HS384',
audience: 'new-audience',
subject: 'new-subject',
});
},
});
rq = await createAuthRequest({ strapi });
utils = createUtils(strapi);
});
test('Works with both legacy and new configuration (sessions.options priority)', async () => {
const res = await rq({
url: '/admin/login',
method: 'POST',
body: superAdmin.loginInfo,
});
expect(res.statusCode).toBe(200);
expect(res.body.data.token).toBeDefined();
// Decode the JWT to verify mixed configuration
const token = res.body.data.token;
const decoded = jwt.decode(token, { complete: true });
expect(decoded?.header.alg).toBe('HS384'); // From sessions.options
expect(decoded?.payload.iss).toBe('legacy-issuer'); // From legacy auth.options
expect(decoded?.payload.aud).toBe('new-audience'); // From sessions.options
expect(decoded?.payload.sub).toBe('new-subject'); // From sessions.options
});
});
});