UNPKG

unleash-server

Version:

Unleash is an enterprise ready feature flag service. It provides different strategies for handling feature flags.

215 lines • 7.33 kB
import { setupAppWithCustomConfig, } from '../../helpers/test-helper.js'; import getLogger from '../../../fixtures/no-logger.js'; import dbInit from '../../helpers/database-init.js'; import { createHmac, randomBytes } from 'node:crypto'; import { createHash } from 'crypto'; import { ApiTokenType } from '../../../../lib/server-impl.js'; import { subMinutes } from 'date-fns'; let app; let db; const clientId = 'enterprise-edge'; const edgeClientSecret = randomBytes(32).toString('base64url'); const edgeMasterKey = randomBytes(32).toString('base64'); const environment = 'development'; describe('HMAC authenticated create token requests', () => { beforeAll(async () => { db = await dbInit('edge_create_token_request', getLogger); app = await setupAppWithCustomConfig(db.stores, { edgeMasterKey, edgeClientSecret, }, db.rawDatabase); await app.services.edgeService.saveClient(clientId, edgeClientSecret); }); test('Happy case, all headers in place and valid signature', async () => { const body = { tokens: [ { environment, projects: ['*'], }, ], }; const headers = buildRequest({ body }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-timestamp', headers.timestamp) .set('x-nonce', headers.nonce) .set('content-sha256', headers.bodyHash) .send(body) .expect(200) .expect((res) => { expect(res.body.tokens).toHaveLength(1); expect(res.body.tokens[0].projects).toStrictEqual(['*']); expect(res.body.tokens[0].type).toStrictEqual(ApiTokenType.BACKEND); const token = res.body.tokens[0].token; expect(token).toMatch(/^\*:development\..*/); }); }); test('no hmac signature gets rejected', async () => { await app.request .post('/edge/issue-token') .send({ tokens: [ { environment, projects: ['*'], }, ], }) .expect(401) .expect((res) => { expect(res.body.error).toStrictEqual('Missing HMAC authorization header'); }); }); test('missing timestamp gets rejected', async () => { const body = { tokens: [ { environment, projects: ['*'], }, ], }; const headers = buildRequest({ body }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-nonce', headers.nonce) .send(body) .expect(401) .expect((res) => { expect(res.body).toEqual({ error: 'Missing content headers' }); }); }); test('rejects multiple requests with same nonce', async () => { const body = { tokens: [ { environment: 'development', projects: ['*'], }, ], }; const headers = buildRequest({ body }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-timestamp', headers.timestamp) .set('x-nonce', headers.nonce) .set('content-sha256', headers.bodyHash) .send(body) .expect(200) .expect((res) => { expect(res.body.tokens).toHaveLength(1); expect(res.body.tokens[0].projects).toStrictEqual(['*']); expect(res.body.tokens[0].type).toStrictEqual(ApiTokenType.BACKEND); const token = res.body.tokens[0].token; expect(token).toMatch(/^\*:development\..*/); }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-timestamp', headers.timestamp) .set('x-nonce', headers.nonce) .set('content-sha256', headers.bodyHash) .send(body) .expect(401) .expect((res) => { expect(res.body.error).toStrictEqual('Replay detected'); }); }); test('reject requests if body is modified after signature is created', async () => { const signedBody = { tokens: [ { environment, projects: ['*'], }, ], }; const headers = buildRequest({ body: signedBody }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-timestamp', headers.timestamp) .set('x-nonce', headers.nonce) .set('content-sha256', headers.bodyHash) .send({ tokens: [ { environment: 'production', projects: ['*'], }, ], }) .expect(401) .expect((res) => { expect(res.body.error).toStrictEqual('Body tampering detected'); }); }); test('stale request gets rejected', async () => { const signedBody = { tokens: [ { environment, projects: ['*'], }, ], }; const headers = buildRequest({ timestamp: subMinutes(new Date(), 10), body: signedBody, }); await app.request .post('/edge/issue-token') .set('Authorization', headers.authorization) .set('x-timestamp', headers.timestamp) .set('x-nonce', headers.nonce) .set('content-sha256', headers.bodyHash) .send({ tokens: [ { environment, projects: ['*'], }, ], }) .expect(401) .expect((res) => { expect(res.body).toStrictEqual({ error: 'Stale request', }); }); }); afterAll(async () => { if (db) { await db.destroy(); } }); }); const buildRequest = ({ body, timestamp, }) => { const actualTimestamp = timestamp?.toISOString() ?? new Date().toISOString(); const nonce = randomBytes(16).toString('hex'); const bodyString = JSON.stringify(body); const bodyHash = createHash('sha256').update(bodyString).digest('hex'); const canonical = 'POST' + '\n' + '/edge/issue-token' + '\n' + actualTimestamp + '\n' + nonce + '\n' + bodyHash; const signature = createHmac('sha256', Buffer.from(edgeClientSecret, 'base64url')) .update(canonical) .digest('base64url'); return { timestamp: actualTimestamp, nonce, bodyHash, authorization: `HMAC ${clientId}:${signature}`, }; }; //# sourceMappingURL=create-token-request.test.js.map