Skip to content

Added Support for Limit M2M Auth API #1107

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export * from './auth/index.js';
export * from './userinfo/index.js';
export * from './lib/errors.js';
export * from './lib/models.js';
export * from './lib/httpResponseHeadersUtils.js';
export * from './deprecations.js';
97 changes: 97 additions & 0 deletions src/lib/httpResponseHeadersUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
export interface TokenQuotaLimit {
quota: number;
remaining: number;
time: number;
}

export interface TokenQuotaBucket {
perHour?: TokenQuotaLimit;
perDay?: TokenQuotaLimit;
}

export class HttpResponseHeadersUtils {
/**
* Gets the client token quota limits from the provided headers.
*
* @param headers the HTTP response headers.
* @return a TokenQuotaBucket containing client rate limits, or null if not present.
*/
static getClientQuotaLimit(headers: Headers | Record<string, string>): TokenQuotaBucket | null {
const getHeaderValue = (key: string): string | null => {
if (headers instanceof Headers) {
return headers.get(key);
}
return headers[key] || null;
};

const quotaHeader =
getHeaderValue('x-quota-client-limit') || getHeaderValue('auth0-quota-client-limit');
return quotaHeader ? this.parseQuota(quotaHeader) : null;
}

/**
* Gets the organization token quota limits from the provided headers.
*
* @param headers the HTTP response headers.
* @return a TokenQuotaBucket containing organization rate limits, or null if not present.
*/
static getOrganizationQuotaLimit(
headers: Headers | Record<string, string>
): TokenQuotaBucket | null {
const getHeaderValue = (key: string): string | null => {
if (headers instanceof Headers) {
return headers.get(key);
}
return headers[key] || null;
};

const quotaHeader =
getHeaderValue('x-quota-Organization-limit') ||
getHeaderValue('auth0-quota-Organization-limit');
return quotaHeader ? this.parseQuota(quotaHeader) : null;
}

/**
* Parses a token quota string into a TokenQuotaBucket.
*
* @param tokenQuota the token quota string.
* @return a TokenQuotaBucket containing parsed rate limits.
*/
private static parseQuota(tokenQuota: string): TokenQuotaBucket {
let perHour: TokenQuotaLimit | undefined;
let perDay: TokenQuotaLimit | undefined;

const parts = tokenQuota.split(',');
for (const part of parts) {
const attributes = part.split(';');
let quota = 0,
remaining = 0,
time = 0;

for (const attribute of attributes) {
const [key, value] = attribute.split('=').map((s) => s.trim());
if (!key || !value) continue;

switch (key) {
case 'q':
quota = parseInt(value, 10);
break;
case 'r':
remaining = parseInt(value, 10);
break;
case 't':
time = parseInt(value, 10);
break;
}
}

if (attributes[0].includes('per_hour')) {
perHour = { quota, remaining, time };
} else if (attributes[0].includes('per_day')) {
perDay = { quota, remaining, time };
}
}

return { perHour, perDay };
}
}
65 changes: 65 additions & 0 deletions test/lib/HttpResponseHeadersUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { HttpResponseHeadersUtils } from '../../src/lib/httpResponseHeadersUtils.js';

describe('HttpResponseHeadersUtils', () => {
describe('getClientQuotaLimit', () => {
it('should return a valid TokenQuotaBucket when x-quota-client-limit header is present', () => {
const headers = {
'x-quota-client-limit': 'per_hour;q=100;r=50;t=3600,per_day;q=1000;r=500;t=86400',
};
const result = HttpResponseHeadersUtils.getClientQuotaLimit(headers);
expect(result).toEqual({
perHour: { quota: 100, remaining: 50, time: 3600 },
perDay: { quota: 1000, remaining: 500, time: 86400 },
});
});

it('should return null when no relevant headers are present', () => {
const headers = { 'some-other-header': 'value' };
const result = HttpResponseHeadersUtils.getClientQuotaLimit(headers);
expect(result).toBeNull();
});
});

describe('getOrganizationQuotaLimit', () => {
it('should return a valid TokenQuotaBucket when x-quota-Organization-limit header is present', () => {
const headers = { 'x-quota-Organization-limit': 'per_hour;q=200;r=150;t=3600' };
const result = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers);
expect(result).toEqual({
perHour: { quota: 200, remaining: 150, time: 3600 },
perDay: undefined,
});
});

it('should return null when no relevant headers are present', () => {
const headers = { 'some-other-header': 'value' };
const result = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers);
expect(result).toBeNull();
});
});

describe('parseQuota', () => {
it('should correctly parse a token quota string into a TokenQuotaBucket', () => {
const tokenQuota = 'per_hour;q=300;r=250;t=3600,per_day;q=3000;r=2500;t=86400';
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
expect(result).toEqual({
perHour: { quota: 300, remaining: 250, time: 3600 },
perDay: { quota: 3000, remaining: 2500, time: 86400 },
});
});

it('should handle missing attributes gracefully', () => {
const tokenQuota = 'per_hour;q=300;r=250';
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
expect(result).toEqual({
perHour: { quota: 300, remaining: 250, time: 0 },
perDay: undefined,
});
});

it('should return an empty TokenQuotaBucket for invalid input', () => {
const tokenQuota = 'invalid_format';
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
expect(result).toEqual({ perHour: undefined, perDay: undefined });
});
});
});
Loading