refactor: change config place and visibility

Signed-off-by: Camila Belo <camilaibs@gmail.com>
This commit is contained in:
Camila Belo
2024-03-19 11:40:41 +01:00
parent 4e265180aa
commit e65c9eb8b7
5 changed files with 42 additions and 37 deletions
+17 -14
View File
@@ -34,21 +34,24 @@ export interface Config {
* unless you configure credentials for service calls.
*/
dangerouslyDisableDefaultAuthPolicy?: boolean;
rateLimit?:
| false
| {
/**
* Limit each IP to max requests per window
*/
max?: number;
/**
* The duration for which the rate limit is enforced
*/
window?: HumanDuration;
disable?: true;
};
};
/** @visibility frontend */
rateLimit?:
| false
| {
/**
* Limit each IP to max requests per window, defaults to 60 requests.
*/
max?: number;
/**
* The duration for which the rate limit is enforced, defaults to 1 minute.
*/
window?: HumanDuration;
/**
* Disable the rate limit verification.
*/
disable?: true;
};
};
/** Discovery options. */
@@ -188,8 +188,8 @@ it('do not limit authenticated requests', async () => {
const max = 2;
const configMock = {
backend: {
auth: {
rateLimit: {
rateLimit: {
unauthorized: {
max, // 2 requests per window
window: { minutes: 2 }, // rate limit window expiration time,
},
@@ -237,8 +237,8 @@ it('limit the number of unauthenticated requests', async () => {
const configMock = {
backend: {
auth: {
rateLimit: {
rateLimit: {
unauthorized: {
max: 2, // 2 requests per window
window: { minutes: 2 }, // rate limit window expiration time,
},
@@ -279,25 +279,25 @@ it('limit the number of unauthenticated requests', async () => {
expect(cacheMock.get).toHaveBeenCalledTimes(4);
expect(cacheMock.set).toHaveBeenNthCalledWith(
1,
`rl_${randomIp}`, // rl is the default prefix for the rate limit store
`unauthorized_rate_limit_${randomIp}`, // unauthorized_rate_limit_ is the default prefix for the rate limit store
{ resetTime, totalHits: 1 },
{ ttl: twoMinutesInMilliseconds },
);
expect(cacheMock.set).toHaveBeenNthCalledWith(
2,
`rl_${randomIp}`,
`unauthorized_rate_limit_${randomIp}`,
{ resetTime, totalHits: 2 },
{ ttl: twoMinutesInMilliseconds },
);
expect(cacheMock.set).toHaveBeenNthCalledWith(
3,
`rl_${randomIp}`,
`unauthorized_rate_limit_${randomIp}`,
{ resetTime, totalHits: 3 },
{ ttl: twoMinutesInMilliseconds },
);
expect(cacheMock.set).toHaveBeenNthCalledWith(
4,
`rl_${randomIp}`,
`unauthorized_rate_limit_${randomIp}`,
{ resetTime, totalHits: 1 },
{ ttl: twoMinutesInMilliseconds },
);
@@ -320,8 +320,8 @@ it('skip limiting requests when the rate limit is disabled', async () => {
cache: cacheMock,
config: {
backend: {
auth: {
rateLimit: {
rateLimit: {
unauthorized: {
max,
window: { minutes: 2 },
disabled: true,
@@ -333,7 +333,6 @@ it('skip limiting requests when the rate limit is disabled', async () => {
barrier1.addAuthPolicy({ allow: 'unauthenticated', path: '/public' });
// exceed the rate limit for authenticated requests
for (let i = 0; i < max + 1; i += 1) {
await request(app1)
.get('/public')
@@ -349,8 +348,8 @@ it('skip limiting requests when the rate limit is disabled', async () => {
cache: cacheMock,
config: {
backend: {
auth: {
rateLimit: false,
rateLimit: {
unauthorized: false,
},
},
},
@@ -358,7 +357,6 @@ it('skip limiting requests when the rate limit is disabled', async () => {
barrier2.addAuthPolicy({ allow: 'unauthenticated', path: '/public' });
// exceed the rate limit for authenticated requests
await request(app2).get('/public').send().expect(200);
jest.useRealTimers();
@@ -65,18 +65,19 @@ export function createCredentialsBarrier(options: {
const unauthenticatedPredicates = new Array<(path: string) => boolean>();
const cookiePredicates = new Array<(path: string) => boolean>();
const rateLimitConfig = config.getOptional('backend.auth.rateLimit');
const rateLimitConfig = config.getOptional('backend.rateLimit.unauthorized');
const disabled =
rateLimitConfig === false ||
(typeof rateLimitConfig === 'object' &&
config?.getOptionalBoolean('backend.auth.rateLimit.disabled') === true);
config?.getOptionalBoolean('backend.rateLimit.unauthorized.disabled') ===
true);
const duration =
typeof rateLimitConfig === 'object' &&
config?.has('backend.auth.rateLimit.window')
config?.has('backend.rateLimit.unauthorized.window')
? readDurationFromConfig(
config.getConfig('backend.auth.rateLimit.window'),
config.getConfig('backend.rateLimit.unauthorized.window'),
)
: undefined;
@@ -84,8 +85,8 @@ export function createCredentialsBarrier(options: {
const max =
typeof rateLimitConfig === 'object' &&
config?.has('backend.auth.rateLimit.max')
? config.getNumber('backend.auth.rateLimit.max')
config?.has('backend.rateLimit.unauthorized.max')
? config.getNumber('backend.rateLimit.unauthorized.max')
: 60;
// Default rate limit is 60 requests per 1 minute
@@ -18,13 +18,12 @@ import { ServiceMock, mockServices } from '@backstage/backend-test-utils';
import { RateLimitStore } from './rateLimitStore';
import { CacheService } from '@backstage/backend-plugin-api';
jest.setSystemTime(Date.parse('2024-03-15T08:39:11.869Z'));
describe('RateLimitStore', () => {
let cacheServiceMock: ServiceMock<CacheService>;
let rateLimitStore: RateLimitStore;
beforeEach(() => {
jest.useFakeTimers({ now: Date.parse('2024-03-15T08:39:11.869Z') });
jest.clearAllMocks();
cacheServiceMock = mockServices.cache.mock();
rateLimitStore = RateLimitStore.fromOptions({
@@ -33,6 +32,10 @@ describe('RateLimitStore', () => {
});
});
afterEach(() => {
jest.useRealTimers();
});
it('should initialize with default options', () => {
expect(rateLimitStore.windowMs).toBe(1 * 60 * 1000);
});
@@ -51,7 +51,7 @@ export class RateLimitStore implements Store {
#cache: CacheService;
private constructor(options: CacheStoreOptions) {
this.prefix = options.prefix ?? 'rl_';
this.prefix = options.prefix ?? 'unauthorized_rate_limit_';
this.#cache = options.cache;
}