Skip to content

Commit e3858f2

Browse files
authoredOct 23, 2024··
feat(internet): add jwt method (#2936)
1 parent 48931a5 commit e3858f2

File tree

7 files changed

+212
-0
lines changed

7 files changed

+212
-0
lines changed
 

‎src/definitions/internet.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@ export type InternetDefinition = LocaleEntry<{
2929
* List of some HTTP status codes.
3030
*/
3131
http_status_code: Record<HTTPStatusCodeType, number[]>;
32+
33+
jwt_algorithm: string[];
3234
}>;

‎src/internal/base64.ts

+23
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,26 @@ export const toBase64: (input: string) => string =
2323
return btoa(binaryString);
2424
}
2525
: (input) => Buffer.from(input).toString('base64');
26+
27+
/**
28+
* This works the same as `Buffer.from(input).toString('base64url')`
29+
* to work on both Node.js and browser environment.
30+
*
31+
* @internal
32+
*
33+
* @param input The string to encode to Base64 URL.
34+
*
35+
* @returns Base64 URL encoded string.
36+
*
37+
* @see https://datatracker.ietf.org/doc/html/rfc4648
38+
*
39+
* @example const encodedHeader = toBase64Url(JSON.stringify(header));
40+
*/
41+
export const toBase64Url: (input: string) => string =
42+
typeof Buffer === 'undefined'
43+
? (input) =>
44+
toBase64(input)
45+
.replaceAll('+', '-')
46+
.replaceAll('/', '_')
47+
.replaceAll(/=+$/g, '')
48+
: (input) => Buffer.from(input).toString('base64url');

‎src/locales/base/internet/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@
55
import type { InternetDefinition } from '../../..';
66
import emoji from './emoji';
77
import http_status_code from './http_status_code';
8+
import jwt_algorithm from './jwt_algorithm';
89

910
const internet: InternetDefinition = {
1011
emoji,
1112
http_status_code,
13+
jwt_algorithm,
1214
};
1315

1416
export default internet;
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export default [
2+
'HS256',
3+
'HS384',
4+
'HS512',
5+
'RS256',
6+
'RS384',
7+
'RS512',
8+
'ES256',
9+
'ES384',
10+
'ES512',
11+
'PS256',
12+
'PS384',
13+
'PS512',
14+
'none',
15+
];

‎src/modules/internet/index.ts

+103
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { FakerError } from '../../errors/faker-error';
2+
import { toBase64Url } from '../../internal/base64';
23
import { deprecated } from '../../internal/deprecated';
34
import { ModuleBase } from '../../internal/module-base';
45
import { charMapping } from './char-mappings';
@@ -1019,4 +1020,106 @@ export class InternetModule extends ModuleBase {
10191020
this.faker.definitions.internet.emoji[emojiType]
10201021
);
10211022
}
1023+
1024+
/**
1025+
* Generates a random JWT (JSON Web Token) Algorithm.
1026+
*
1027+
* @see faker.internet.jwt(): For generating random JWT (JSON Web Token).
1028+
*
1029+
* @example
1030+
* faker.internet.jwtAlgorithm() // 'HS256'
1031+
* faker.internet.jwtAlgorithm() // 'RS512'
1032+
*
1033+
* @since 9.1.0
1034+
*/
1035+
jwtAlgorithm(): string {
1036+
return this.faker.helpers.arrayElement(
1037+
this.faker.definitions.internet.jwt_algorithm
1038+
);
1039+
}
1040+
1041+
/**
1042+
* Generates a random JWT (JSON Web Token).
1043+
*
1044+
* Please note that this method generates a random signature instead of a valid one.
1045+
*
1046+
* @param options The optional options object.
1047+
* @param options.header The Header to use for the token. Defaults to a random object with the following fields: `alg` and `typ`.
1048+
* @param options.payload The Payload to use for the token. Defaults to a random object with the following fields: `iat`, `exp`, `nbf`, `iss`, `sub`, `aud`, and `jti`.
1049+
* @param options.refDate The date to use as reference point for the newly generated date.
1050+
*
1051+
* @see https://datatracker.ietf.org/doc/html/rfc7519
1052+
* @see faker.internet.jwtAlgorithm(): For generating random JWT (JSON Web Token) Algorithm.
1053+
*
1054+
* @example
1055+
* faker.internet.jwt()
1056+
* faker.internet.jwt({ header: { alg: 'HS256' }}) // 'eyJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE3MTg2MTM3MTIsImV4cCI6MTcxODYzMzY3OSwibmJmIjoxNjk3MjYzNjMwLCJpc3MiOiJEb3lsZSBhbmQgU29ucyIsInN1YiI6IjYxYWRkYWFmLWY4MjktNDkzZS1iNTI1LTJjMGJkNjkzOTdjNyIsImF1ZCI6IjczNjcyMjVjLWIwMWMtNGE1My1hYzQyLTYwOWJkZmI1MzBiOCIsImp0aSI6IjU2Y2ZkZjAxLWRhMzMtNGUxNi04MzJiLTFlYTk3ZGY1MTQ2YSJ9.5iUgaCaFVPZ8d1QD0xMjoeJbmPVyUfKfoRQ6Njzm5MLp5F4UMh5REbPCrW70fAkr'
1057+
* faker.internet.jwt({ payload: { iss: 'Acme' }}) // 'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBY21lIn0.syUt0GBukNac8Cn1AGKFq2SWAXWy1YIfl0uOYiwg6TZ3omAW0c7FGWY6bC7ZOFSt'
1058+
* faker.internet.jwt({ refDate: '2020-01-01T00:00:00.000Z' }) // 'eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc4MDY4NDUsImV4cCI6MTU3Nzg0NjI4MCwibmJmIjoxNTgxNTQyMDYwLCJpc3MiOiJLcmVpZ2VyLCBBbHRlbndlcnRoIGFuZCBQYXVjZWsiLCJzdWIiOiI5NzVjMjMyOS02MDlhLTRjYTYtYjBkZi05ZmY4MGZiNDUwN2QiLCJhdWQiOiI0ODQxZWYwNi01OWYwLTQzMWEtYmFmZi0xMjkxZmRhZDdhNjgiLCJqdGkiOiJmNDBjZTJiYi00ZWYyLTQ1MjMtOGIxMy1kN2Q4NTA5N2M2ZTUifQ.cuClEZQ0CyPIMVS5uxrMwWXz0wcqFFdt0oNne3PMryyly0jghkxVurss2TapMC3C'
1059+
*
1060+
* @since 9.1.0
1061+
*/
1062+
jwt(
1063+
options: {
1064+
/**
1065+
* The header to use for the token. If present, it will replace any default values.
1066+
*
1067+
* @default
1068+
* {
1069+
* alg: faker.internet.jwtAlgorithm(),
1070+
* typ: 'JWT'
1071+
* }
1072+
*/
1073+
header?: Record<string, unknown>;
1074+
/**
1075+
* The payload to use for the token. If present, it will replace any default values.
1076+
*
1077+
* @default
1078+
* {
1079+
* iat: faker.date.recent(),
1080+
* exp: faker.date.soon(),
1081+
* nbf: faker.date.anytime(),
1082+
* iss: faker.company.name(),
1083+
* sub: faker.string.uuid(),
1084+
* aud: faker.string.uuid(),
1085+
* jti: faker.string.uuid()
1086+
* }
1087+
*/
1088+
payload?: Record<string, unknown>;
1089+
/**
1090+
* The date to use as reference point for the newly generated date.
1091+
*
1092+
* @default faker.defaultRefDate()
1093+
*/
1094+
refDate?: string | Date | number;
1095+
} = {}
1096+
): string {
1097+
const { refDate = this.faker.defaultRefDate() } = options;
1098+
1099+
const iatDefault = this.faker.date.recent({ refDate });
1100+
1101+
const {
1102+
header = {
1103+
alg: this.jwtAlgorithm(),
1104+
typ: 'JWT',
1105+
},
1106+
payload = {
1107+
iat: Math.round(iatDefault.valueOf() / 1000),
1108+
exp: Math.round(
1109+
this.faker.date.soon({ refDate: iatDefault }).valueOf() / 1000
1110+
),
1111+
nbf: Math.round(this.faker.date.anytime({ refDate }).valueOf() / 1000),
1112+
iss: this.faker.company.name(),
1113+
sub: this.faker.string.uuid(),
1114+
aud: this.faker.string.uuid(),
1115+
jti: this.faker.string.uuid(),
1116+
},
1117+
} = options;
1118+
1119+
const encodedHeader = toBase64Url(JSON.stringify(header));
1120+
const encodedPayload = toBase64Url(JSON.stringify(payload));
1121+
const signature = this.faker.string.alphanumeric(64);
1122+
1123+
return `${encodedHeader}.${encodedPayload}.${signature}`;
1124+
}
10221125
}

‎test/modules/__snapshots__/internet.spec.ts.snap

+24
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,14 @@ exports[`internet > 42 > ipv4 > with network 1`] = `"229.254.29.199"`;
7474

7575
exports[`internet > 42 > ipv6 1`] = `"8ead:331d:df0f:c444:6b96:d368:ab4b:d1d3"`;
7676

77+
exports[`internet > 42 > jwt > noArgs 1`] = `"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpYXQiOjE1Nzc3ODI3NjAsImV4cCI6MTU3Nzg0NjAwNCwibmJmIjoxNTg0MDU5Mzg4LCJpc3MiOiJDcmlzdCAtIEJlZXIiLCJzdWIiOiJkOWIwZmQzMi0yNDg2LTQ0OTItODQ1Ny1jMzg5MDkyMWZmYzQiLCJhdWQiOiJhNzE3MGU0YS00ODgyLTRmY2YtOGU5ZS0xMzA1NjRkNTQ4MmMiLCJqdGkiOiJmYzMwZGJiYy0xNTFkLTQ5NTEtODQ1Yi1hZTcxYmM4Yzc4NjAifQ.1DjvUfpKe4h9VODSNbTxOTj6eqOR0vpd7kWkwHmYXfuih2Bv3hUe8uZfFLeJmDDx"`;
78+
79+
exports[`internet > 42 > jwt > with custom header 1`] = `"eyJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE1Nzc3ODI3NjAsImV4cCI6MTU3Nzg2NDkwMiwibmJmIjoxNTkyNDY5MTIyLCJpc3MiOiJDcmlzdCBHcm91cCIsInN1YiI6IjBkOWIwZmQzLTIyNDgtNDY0OS05MjQ1LTdjMzg5MDkyMWZmYyIsImF1ZCI6IjFhNzE3MGU0LWE0ODgtNDJmYy1iZmU5LWUxMzA1NjRkNTQ4MiIsImp0aSI6IjFmYzMwZGJiLWMxNTEtNGQ5NS04MTQ1LWJhZTcxYmM4Yzc4NiJ9.61DjvUfpKe4h9VODSNbTxOTj6eqOR0vpd7kWkwHmYXfuih2Bv3hUe8uZfFLeJmDD"`;
80+
81+
exports[`internet > 42 > jwt > with custom payload 1`] = `"eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJpc3MiOiJBY21lIn0.JB993RBH1YPdbbiwqiB8imsMcvA2Ba4WXOi6Gr7u2UgFjwxbYMWTBV5c2kogPmhx"`;
82+
83+
exports[`internet > 42 > jwtAlgorithm 1`] = `"RS384"`;
84+
7785
exports[`internet > 42 > mac > noArgs 1`] = `"5f:b9:22:0d:9b:0f"`;
7886

7987
exports[`internet > 42 > mac > with separator 1`] = `"5f:b9:22:0d:9b:0f"`;
@@ -210,6 +218,14 @@ exports[`internet > 1211 > ipv4 > with network 1`] = `"238.219.55.242"`;
210218

211219
exports[`internet > 1211 > ipv6 1`] = `"ed4f:efa7:fbae:c9dc:4c48:fa8e:bf46:fb7c"`;
212220

221+
exports[`internet > 1211 > jwt > noArgs 1`] = `"eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc4MzA2MjMsImV4cCI6MTU3Nzg1MDExMywibmJmIjoxNjA5MTk5Nzk2LCJpc3MiOiJQYXVjZWssIFJ1bm9sZnNkb3R0aXIgYW5kIEhlcm1hbm4iLCJzdWIiOiJiZGNhZDZkZC0zOTM1LTRmYzYtOGU4Zi0zNGI4NWQ2NDQyOGIiLCJhdWQiOiJiNzM2M2Q5Ny0wYjJiLTQ0YzgtYjczOS1kMWQ3Njc5YzhlZmQiLCJqdGkiOiIzYmQ1ZTA4Yy03MTQyLTQ0M2UtYWY2My05OTk5ZGFkY2RlODUifQ.rlPcW5f2VqUAXZlCKU8wGJfPV9H4qDj1eeVBExFATrtOy1ztr5Cp9avVZNMtsWSD"`;
222+
223+
exports[`internet > 1211 > jwt > with custom header 1`] = `"eyJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE1Nzc4MzA2MjMsImV4cCI6MTU3NzkwNzgxOSwibmJmIjoxNTYwNTI3OTk5LCJpc3MiOiJPc2luc2tpLCBQYXVjZWsgYW5kIFJ1bm9sZnNkb3R0aXIiLCJzdWIiOiI1YmRjYWQ2ZC1kMzkzLTQ1ZmMtYTZlOC1mMzRiODVkNjQ0MjgiLCJhdWQiOiIyYjczNjNkOS03MGIyLTRiNGMtYjg3My05ZDFkNzY3OWM4ZWYiLCJqdGkiOiJkM2JkNWUwOC1jNzE0LTQyNDMtOWVmNi0zOTk5OWRhZGNkZTgifQ.xrlPcW5f2VqUAXZlCKU8wGJfPV9H4qDj1eeVBExFATrtOy1ztr5Cp9avVZNMtsWS"`;
224+
225+
exports[`internet > 1211 > jwt > with custom payload 1`] = `"eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBY21lIn0.dZFGLlHOLEPqRRdAcmZLoWxYdiHwlQngjayH8HufpcQzt2HbHhMvsdBS6QsntzOx"`;
226+
227+
exports[`internet > 1211 > jwtAlgorithm 1`] = `"none"`;
228+
213229
exports[`internet > 1211 > mac > noArgs 1`] = `"ee:3f:aa:c5:bd:ca"`;
214230

215231
exports[`internet > 1211 > mac > with separator 1`] = `"ee:3f:aa:c5:bd:ca"`;
@@ -346,6 +362,14 @@ exports[`internet > 1337 > ipv4 > with network 1`] = `"228.49.64.201"`;
346362

347363
exports[`internet > 1337 > ipv6 1`] = `"536a:7b5f:a28d:2f9b:b79c:a46e:a394:bc4f"`;
348364

365+
exports[`internet > 1337 > jwt > noArgs 1`] = `"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1Nzc3NzMwMzksImV4cCI6MTU3Nzc5NzA3MCwibmJmIjoxNTc1MjcwODM1LCJpc3MiOiJMZWFubm9uIC0gR2lic29uIiwic3ViIjoiZmIxNmEyZjctY2M1Ni00OWMzLWI0YTctMjYzOGQyZjY4ODBiIiwiYXVkIjoiMjI1YjA1MGMtNWI3Zi00ZDk5LThmNDAtMWZmNzViMGNhM2FlIiwianRpIjoiZTViNDgyNzctNmM3Yi00YzVlLThiZTYtN2VhODNmOGMzNjY4In0.G1eJVCpQZioHm1lu2UIL52g7eGtWAbbkq4D3IE0LkMkzaQgKyTx14Xs9FCyUTgIu"`;
366+
367+
exports[`internet > 1337 > jwt > with custom header 1`] = `"eyJhbGciOiJFUzI1NiJ9.eyJpYXQiOjE1Nzc3NzMwMzksImV4cCI6MTU3Nzc4Njc1MCwibmJmIjoxNTYzODQyNzk2LCJpc3MiOiJIYW1tZXMgTExDIiwic3ViIjoiNGZiMTZhMmYtN2NjNS00NjljLWEzNGEtNzI2MzhkMmY2ODgwIiwiYXVkIjoiZjIyNWIwNTAtYzViNy00ZmQ5LWI5ZjQtMDFmZjc1YjBjYTNhIiwianRpIjoiMmU1YjQ4MjctNzZjNy00YmM1LWFlYmUtNjdlYTgzZjhjMzY2In0.7G1eJVCpQZioHm1lu2UIL52g7eGtWAbbkq4D3IE0LkMkzaQgKyTx14Xs9FCyUTgI"`;
368+
369+
exports[`internet > 1337 > jwt > with custom payload 1`] = `"eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJBY21lIn0.hsjwgYJ7nC7YrMNmpALbhFubpcwPbXqvv0JZa7nG0m3MlHuYPBzZf05WYulI0LFb"`;
370+
371+
exports[`internet > 1337 > jwtAlgorithm 1`] = `"RS256"`;
372+
349373
exports[`internet > 1337 > mac > noArgs 1`] = `"42:47:58:4f:b1:6a"`;
350374

351375
exports[`internet > 1337 > mac > with separator 1`] = `"42:47:58:4f:b1:6a"`;

‎test/modules/internet.spec.ts

+43
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { times } from './../support/times';
88

99
const NON_SEEDED_BASED_RUN = 5;
1010

11+
const refDate = '2020-01-01T00:00:00.000Z';
12+
1113
describe('internet', () => {
1214
seededTests(faker, 'internet', (t) => {
1315
t.itEach(
@@ -18,6 +20,7 @@ describe('internet', () => {
1820
'domainWord',
1921
'ip',
2022
'ipv6',
23+
'jwtAlgorithm',
2124
'port',
2225
'userAgent'
2326
);
@@ -154,6 +157,12 @@ describe('internet', () => {
154157
.it('with cidrBlock', { cidrBlock: '192.168.13.37/24' })
155158
.it('with network', { network: IPv4Network.Multicast });
156159
});
160+
161+
t.describe('jwt', (t) => {
162+
t.it('noArgs', { refDate })
163+
.it('with custom header', { header: { alg: 'ES256' }, refDate })
164+
.it('with custom payload', { payload: { iss: 'Acme' }, refDate });
165+
});
157166
});
158167

159168
describe.each(times(NON_SEEDED_BASED_RUN).map(() => faker.seed()))(
@@ -971,6 +980,40 @@ describe('internet', () => {
971980
expect(emoji.length).toBeGreaterThanOrEqual(1);
972981
});
973982
});
983+
984+
describe('jwt', () => {
985+
it('should return a random jwt', () => {
986+
const jwt = faker.internet.jwt();
987+
988+
expect(jwt).toBeTruthy();
989+
expect(jwt).toBeTypeOf('string');
990+
expect(jwt).toSatisfy(validator.isJWT);
991+
});
992+
993+
it('should return the header and payload values from the token', () => {
994+
const header = {
995+
kid: faker.string.alphanumeric(),
996+
};
997+
998+
const payload = {
999+
nonce: faker.string.alphanumeric(),
1000+
};
1001+
1002+
const actual = faker.internet.jwt({ header, payload });
1003+
1004+
expect(actual).toBeTypeOf('string');
1005+
expect(actual).toSatisfy(validator.isJWT);
1006+
1007+
const parts = actual.split('.');
1008+
1009+
expect(
1010+
JSON.parse(Buffer.from(parts[0], 'base64url').toString('ascii'))
1011+
).toMatchObject(header);
1012+
expect(
1013+
JSON.parse(Buffer.from(parts[1], 'base64url').toString('ascii'))
1014+
).toMatchObject(payload);
1015+
});
1016+
});
9741017
}
9751018
);
9761019
});

0 commit comments

Comments
 (0)
Please sign in to comment.