Skip to content

Commit 2931fb2

Browse files
authored
Merge branch 'master' into limit-M2M-usage-node-auth0-support
2 parents c143fa5 + a62b620 commit 2931fb2

3 files changed

Lines changed: 160 additions & 0 deletions

File tree

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ export * from './auth/index.js';
33
export * from './userinfo/index.js';
44
export * from './lib/errors.js';
55
export * from './lib/models.js';
6+
export * from './lib/httpResponseHeadersUtils.js';
67
export * from './deprecations.js';
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
export interface TokenQuotaLimit {
2+
quota: number;
3+
remaining: number;
4+
resetAfter: number;
5+
}
6+
7+
export interface TokenQuotaBucket {
8+
perHour?: TokenQuotaLimit;
9+
perDay?: TokenQuotaLimit;
10+
}
11+
12+
export class HttpResponseHeadersUtils {
13+
/**
14+
* Gets the client token quota limits from the provided headers.
15+
*
16+
* @param headers the HTTP response headers.
17+
* @return a TokenQuotaBucket containing client rate limits, or null if not present.
18+
*/
19+
static getClientQuotaLimit(headers: Headers | Record<string, string>): TokenQuotaBucket | null {
20+
const getHeaderValue = (key: string): string | null => {
21+
if (headers instanceof Headers) {
22+
return headers.get(key);
23+
}
24+
return headers[key] || null;
25+
};
26+
27+
const quotaHeader = getHeaderValue('auth0-client-quota-limit');
28+
return quotaHeader ? this.parseQuota(quotaHeader) : null;
29+
}
30+
31+
/**
32+
* Gets the organization token quota limits from the provided headers.
33+
*
34+
* @param headers the HTTP response headers.
35+
* @return a TokenQuotaBucket containing organization rate limits, or null if not present.
36+
*/
37+
static getOrganizationQuotaLimit(
38+
headers: Headers | Record<string, string>
39+
): TokenQuotaBucket | null {
40+
const getHeaderValue = (key: string): string | null => {
41+
if (headers instanceof Headers) {
42+
return headers.get(key);
43+
}
44+
return headers[key] || null;
45+
};
46+
47+
const quotaHeader = getHeaderValue('auth0-organization-quota-limit');
48+
return quotaHeader ? this.parseQuota(quotaHeader) : null;
49+
}
50+
51+
/**
52+
* Parses a token quota string into a TokenQuotaBucket.
53+
*
54+
* @param tokenQuota the token quota string.
55+
* @return a TokenQuotaBucket containing parsed rate limits.
56+
*/
57+
private static parseQuota(tokenQuota: string): TokenQuotaBucket {
58+
let perHour: TokenQuotaLimit | undefined;
59+
let perDay: TokenQuotaLimit | undefined;
60+
61+
const parts = tokenQuota.split(',');
62+
for (const part of parts) {
63+
const attributes = part.split(';');
64+
let quota = 0,
65+
remaining = 0,
66+
resetAfter = 0;
67+
68+
for (const attribute of attributes) {
69+
const [key, value] = attribute.split('=').map((s) => s.trim());
70+
if (!key || !value) continue;
71+
72+
switch (key) {
73+
case 'q':
74+
quota = parseInt(value, 10);
75+
break;
76+
case 'r':
77+
remaining = parseInt(value, 10);
78+
break;
79+
case 't':
80+
resetAfter = parseInt(value, 10);
81+
break;
82+
}
83+
}
84+
85+
if (attributes.length > 0 && attributes[0].includes('per_hour')) {
86+
perHour = { quota, remaining, resetAfter };
87+
} else if (attributes.length > 0 && attributes[0].includes('per_day')) {
88+
perDay = { quota, remaining, resetAfter };
89+
}
90+
}
91+
92+
return { perHour, perDay };
93+
}
94+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import { HttpResponseHeadersUtils } from '../../src/lib/httpResponseHeadersUtils.js';
2+
3+
describe('HttpResponseHeadersUtils', () => {
4+
describe('getClientQuotaLimit', () => {
5+
it('should return a valid TokenQuotaBucket when auth0-client-quota-limit header is present', () => {
6+
const headers = {
7+
'auth0-client-quota-limit': 'per_hour;q=100;r=50;t=3600,per_day;q=1000;r=500;t=86400',
8+
};
9+
const result = HttpResponseHeadersUtils.getClientQuotaLimit(headers);
10+
expect(result).toEqual({
11+
perHour: { quota: 100, remaining: 50, resetAfter: 3600 },
12+
perDay: { quota: 1000, remaining: 500, resetAfter: 86400 },
13+
});
14+
});
15+
16+
it('should return null when no relevant headers are present', () => {
17+
const headers = { 'some-other-header': 'value' };
18+
const result = HttpResponseHeadersUtils.getClientQuotaLimit(headers);
19+
expect(result).toBeNull();
20+
});
21+
});
22+
23+
describe('getOrganizationQuotaLimit', () => {
24+
it('should return a valid TokenQuotaBucket when auth0-organization-quota-limit header is present', () => {
25+
const headers = { 'auth0-organization-quota-limit': 'per_hour;q=200;r=150;t=3600' };
26+
const result = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers);
27+
expect(result).toEqual({
28+
perHour: { quota: 200, remaining: 150, resetAfter: 3600 },
29+
perDay: undefined,
30+
});
31+
});
32+
33+
it('should return null when no relevant headers are present', () => {
34+
const headers = { 'some-other-header': 'value' };
35+
const result = HttpResponseHeadersUtils.getOrganizationQuotaLimit(headers);
36+
expect(result).toBeNull();
37+
});
38+
});
39+
40+
describe('parseQuota', () => {
41+
it('should correctly parse a token quota string into a TokenQuotaBucket', () => {
42+
const tokenQuota = 'per_hour;q=300;r=250;t=3600,per_day;q=3000;r=2500;t=86400';
43+
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
44+
expect(result).toEqual({
45+
perHour: { quota: 300, remaining: 250, resetAfter: 3600 },
46+
perDay: { quota: 3000, remaining: 2500, resetAfter: 86400 },
47+
});
48+
});
49+
50+
it('should handle missing attributes gracefully', () => {
51+
const tokenQuota = 'per_hour;q=300;r=250';
52+
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
53+
expect(result).toEqual({
54+
perHour: { quota: 300, remaining: 250, resetAfter: 0 },
55+
perDay: undefined,
56+
});
57+
});
58+
59+
it('should return an empty TokenQuotaBucket for invalid input', () => {
60+
const tokenQuota = 'invalid_format';
61+
const result = HttpResponseHeadersUtils['parseQuota'](tokenQuota);
62+
expect(result).toEqual({ perHour: undefined, perDay: undefined });
63+
});
64+
});
65+
});

0 commit comments

Comments
 (0)