balena-supervisor/test/lib/api-keys.spec.ts
Felipe Lalanne ac2db38742 Move api-keys module to src/lib
This removes circular dependencies between the device-api module and
the compose module, reducing total circular dependencies to 15

Change-type: patch
2024-05-27 14:36:03 -04:00

136 lines
3.7 KiB
TypeScript

import express from 'express';
import request from 'supertest';
import { expect } from 'chai';
import * as config from '~/src/config';
import * as testDb from '~/src/db';
import * as apiKeys from '~/lib/api-keys';
import * as middleware from '~/src/device-api/middleware';
import type { AuthorizedRequest } from '~/lib/api-keys';
describe('device-api/api-keys', () => {
let app: express.Application;
before(async () => {
await config.initialized();
app = express();
app.use(middleware.auth);
app.get('/test/:appId', (req: AuthorizedRequest, res) => {
if (req.auth.isScoped({ apps: [parseInt(req.params.appId, 10)] })) {
res.sendStatus(200);
} else {
res.sendStatus(401);
}
});
});
afterEach(async () => {
// Delete all scoped API keys between calls to prevent leaking tests
await testDb.models('apiSecret').whereNot({ appId: 0 }).del();
});
it('should generate a key which is scoped for a single application', async () => {
const appOneKey = await apiKeys.generateScopedKey(111, 'one');
const appTwoKey = await apiKeys.generateScopedKey(222, 'two');
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${appOneKey}`)
.expect(200);
await request(app)
.get('/test/222')
.set('Authorization', `Bearer ${appTwoKey}`)
.expect(200);
await request(app)
.get('/test/222')
.set('Authorization', `Bearer ${appOneKey}`)
.expect(401);
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${appTwoKey}`)
.expect(401);
});
it('should generate a key which is scoped for multiple applications', async () => {
const multiAppKey = await apiKeys.generateScopedKey(111, 'three', {
scopes: [111, 222].map((appId) => ({ type: 'app', appId })),
});
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${multiAppKey}`)
.expect(200);
await request(app)
.get('/test/222')
.set('Authorization', `Bearer ${multiAppKey}`)
.expect(200);
});
it('should generate a key which is scoped for all applications', async () => {
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
.expect(200);
await request(app)
.get('/test/222')
.set('Authorization', `Bearer ${await apiKeys.getGlobalApiKey()}`)
.expect(200);
});
it('should have a cached lookup of key scopes', async () => {
const globalScopes = await apiKeys.getScopesForKey(
await apiKeys.getGlobalApiKey(),
);
const key = 'my-new-key';
await testDb
.models('apiSecret')
.where({ key: await apiKeys.getGlobalApiKey() })
.update({ key });
// Key has been changed, but cache should retain the old key
expect(
await apiKeys.getScopesForKey(await apiKeys.getGlobalApiKey()),
).to.deep.equal(globalScopes);
// Bust the cache and generate a new global API key
const refreshedKey = await apiKeys.refreshKey(
await apiKeys.getGlobalApiKey(),
);
// Key that we changed in db is no longer valid
expect(await apiKeys.getScopesForKey(key)).to.be.null;
// Refreshed key should have the global scopes
expect(await apiKeys.getScopesForKey(refreshedKey)).to.deep.equal(
globalScopes,
);
});
it('should regenerate a key and invalidate the old one', async () => {
const appScopedKey = await apiKeys.generateScopedKey(111, 'four');
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${appScopedKey}`)
.expect(200);
const newScopedKey = await apiKeys.refreshKey(appScopedKey);
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${appScopedKey}`)
.expect(401);
await request(app)
.get('/test/111')
.set('Authorization', `Bearer ${newScopedKey}`)
.expect(200);
});
});