Skip to content

Commit ef0a63a

Browse files
authored
feat: pass-through (almost) all claims (#22)
* feat: claims from json * fix: cattpk need to be a hex string * fix: input validation of json * feat: validate json claims * feat: passing through cath claim * feat: passing through catif * chore: updated table with pass-throughs
1 parent 6048cb9 commit ef0a63a

File tree

8 files changed

+388
-120
lines changed

8 files changed

+388
-120
lines changed

readme.md

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -38,27 +38,30 @@ Features:
3838

3939
### Core Claims
4040

41-
| Claim | Validate |
42-
| --------------------------------------------------------- | -------- |
43-
| Issuer (`iss`) | Yes |
44-
| Audience (`aud`) | Yes |
45-
| Expiration (`exp`) | Yes |
46-
| Not Before (`nbf`) | Yes |
47-
| CWT ID (`cti`) | Yes |
48-
| Common Access Token Replay (`catreplay`) | Yes |
49-
| Common Access Token Probability of Rejection (`catpor`) | No |
50-
| Common Access Token Version (`catv`) | Yes |
51-
| Common Access Token Network IP (`catnip`) | No |
52-
| Common Access Token URI (`catu`) | Yes |
53-
| Common Access Token Methods (`catm`) | Yes |
54-
| Common Access Token ALPN (`catalpn`) | No |
55-
| Common Access Token Header (`cath`) | No |
56-
| Common Access Token Geographic ISO3166 (`catgeoiso3166`) | No |
57-
| Common Access Token Geographic Coordinate (`catgeocoord`) | No |
58-
| Geohash (`geohash`) | No |
59-
| Common Access Token Altitude (`catgeoalt`) | No |
60-
| Common Access Token TLS Public Key (`cattpk`) | No |
61-
| Common Access Token Renewal (`catr`) claim | Yes |
41+
| Claim | Pass-through | Validate |
42+
| --------------------------------------------------------- | ------------ | -------- |
43+
| Issuer (`iss`) | Yes | Yes |
44+
| Audience (`aud`) | Yes | Yes |
45+
| Expiration (`exp`) | Yes | Yes |
46+
| Not Before (`nbf`) | Yes | Yes |
47+
| CWT ID (`cti`) | Yes | Yes |
48+
| Subject (`sub`) | Yes | n/a |
49+
| Issued at (`iat`) | Yes | n/a |
50+
| Common Access Token Replay (`catreplay`) | Yes | Yes |
51+
| Common Access Token Probability of Rejection (`catpor`) | Yes | No |
52+
| Common Access Token Version (`catv`) | Yes | Yes |
53+
| Common Access Token Network IP (`catnip`) | Yes | No |
54+
| Common Access Token URI (`catu`) | Yes | Yes |
55+
| Common Access Token Methods (`catm`) | Yes | Yes |
56+
| Common Access Token ALPN (`catalpn`) | No | No |
57+
| Common Access Token Header (`cath`) | Yes | No |
58+
| Common Access Token Geographic ISO3166 (`catgeoiso3166`) | Yes | No |
59+
| Common Access Token Geographic Coordinate (`catgeocoord`) | Yes | No |
60+
| Geohash (`geohash`) | Yes | No |
61+
| Common Access Token Altitude (`catgeoalt`) | Yes | No |
62+
| Common Access Token TLS Public Key (`cattpk`) | Yes | No |
63+
| Common Access Token If (`catif`) claim | Yes | No |
64+
| Common Access Token Renewal (`catr`) claim | Yes | No |
6265

6366
## Requirements
6467

src/cat.ts

Lines changed: 89 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
InvalidAudienceError,
55
InvalidClaimTypeError,
66
InvalidIssuerError,
7+
InvalidJsonError,
78
RenewalClaimError,
89
TokenExpiredError,
910
TokenNotActiveError,
@@ -12,8 +13,10 @@ import {
1213
import { CatValidationOptions } from '.';
1314
import { CommonAccessTokenUri } from './catu';
1415
import { CommonAccessTokenRenewal } from './catr';
16+
import { CommonAccessTokenHeader } from './cath';
17+
import { CommonAccessTokenIf } from './catif';
1518

16-
const claimsToLabels: { [key: string]: number } = {
19+
export const claimsToLabels: { [key: string]: number } = {
1720
iss: 1, // 3
1821
sub: 2, // 3
1922
aud: 3, // 3
@@ -22,6 +25,7 @@ const claimsToLabels: { [key: string]: number } = {
2225
iat: 6, // 6 tag value 1
2326
cti: 7, // 2,
2427
cnf: 8,
28+
geohash: 282,
2529
catreplay: 308,
2630
catpor: 309,
2731
catv: 310,
@@ -39,7 +43,7 @@ const claimsToLabels: { [key: string]: number } = {
3943
catr: 323
4044
};
4145

42-
const labelsToClaim: { [key: number]: string } = {
46+
export const labelsToClaim: { [key: number]: string } = {
4347
1: 'iss',
4448
2: 'sub',
4549
3: 'aud',
@@ -48,6 +52,7 @@ const labelsToClaim: { [key: number]: string } = {
4852
6: 'iat',
4953
7: 'cti',
5054
8: 'cnf',
55+
282: 'geohash',
5156
308: 'catreplay',
5257
309: 'catpor',
5358
310: 'catv',
@@ -75,11 +80,64 @@ const claimTransformReverse: { [key: string]: (value: Buffer) => string } = {
7580
cattpk: (value: Buffer) => value.toString('hex')
7681
};
7782

78-
const claimTypeValidators: { [key: string]: (value: string) => boolean } = {
83+
const claimTypeValidators: {
84+
[key: string]: (value: CommonAccessTokenValue) => boolean;
85+
} = {
7986
iss: (value) => typeof value === 'string',
8087
exp: (value) => typeof value === 'number',
8188
aud: (value) => typeof value === 'string' || Array.isArray(value),
82-
nbf: (value) => typeof value === 'number'
89+
nbf: (value) => typeof value === 'number',
90+
cti: (value) => Buffer.isBuffer(value),
91+
catreplay: (value) => typeof value === 'number',
92+
catpor: (value) => Array.isArray(value),
93+
catv: (value) => typeof value === 'number' && value >= 1,
94+
catnip: (value) => typeof value === 'number' || typeof value === 'string',
95+
catu: (value) => value instanceof Map,
96+
catm: (value) => Array.isArray(value),
97+
cath: (value) => value instanceof Map,
98+
catgeoiso3166: (value) => Array.isArray(value),
99+
catgeocoord: (value) => Array.isArray(value),
100+
cattpk: (value) => Buffer.isBuffer(value),
101+
sub: (value) => typeof value === 'string',
102+
iat: (value) => typeof value === 'number',
103+
catifdata: (value) => typeof value === 'string' || Array.isArray(value),
104+
cnf: (value) => value instanceof Map,
105+
catdpop: (value) => value instanceof Map,
106+
catif: (value) => value instanceof Map,
107+
catr: (value) => value instanceof Map
108+
};
109+
110+
const isHex = (value: string) => /^[0-9a-fA-F]+$/.test(value);
111+
const isNetworkIp = (value: string) => /^[0-9a-fA-F:.]+$/.test(value);
112+
113+
const claimTypeDictValidators: {
114+
[key: string]: (value: unknown) => boolean;
115+
} = {
116+
iss: (value) => typeof value === 'string',
117+
exp: (value) => typeof value === 'number',
118+
aud: (value) => typeof value === 'string' || Array.isArray(value),
119+
nbf: (value) => typeof value === 'number',
120+
cti: (value) => typeof value === 'string' && isHex(value),
121+
catreplay: (value) => typeof value === 'number' && value >= 0,
122+
catpor: (value) => Array.isArray(value),
123+
catv: (value) => typeof value === 'number' && value >= 1,
124+
catnip: (value) =>
125+
typeof value === 'number' ||
126+
(typeof value === 'string' && isNetworkIp(value)),
127+
catu: (value) => typeof value === 'object',
128+
catm: (value) => Array.isArray(value),
129+
cath: (value) => typeof value === 'object',
130+
catgeoiso3166: (value) => Array.isArray(value),
131+
catgeocoord: (value) => Array.isArray(value),
132+
catgeoalt: (value) => Array.isArray(value),
133+
cattpk: (value) => typeof value === 'string' && isHex(value),
134+
sub: (value) => typeof value === 'string',
135+
iat: (value) => typeof value === 'number',
136+
catifdata: (value) => typeof value === 'string' || Array.isArray(value),
137+
cnf: (value) => typeof value === 'object',
138+
catdpop: (value) => typeof value === 'object',
139+
catif: (value) => typeof value === 'object',
140+
catr: (value) => typeof value === 'object'
83141
};
84142

85143
const CWT_TAG = 61;
@@ -88,7 +146,7 @@ const CWT_TAG = 61;
88146
* Common Access Token Claims
89147
*/
90148
export type CommonAccessTokenClaims = {
91-
[key: string]: string | number | Map<number, any>;
149+
[key: string]: string | number | Map<number | string, any>;
92150
};
93151
export type CommonAccessTokenDict = {
94152
[key: string]: string | number | { [key: string]: any };
@@ -97,7 +155,7 @@ export type CommonAccessTokenValue =
97155
| string
98156
| number
99157
| Buffer
100-
| Map<number, any>;
158+
| Map<number | string, any>;
101159

102160
/**
103161
* CWT Encryption Key
@@ -161,6 +219,12 @@ function updateMapFromDict(
161219
): CommonAccessTokenClaims {
162220
const claims: CommonAccessTokenClaims = {};
163221
for (const param in dict) {
222+
if (
223+
claimTypeDictValidators[param] &&
224+
!claimTypeDictValidators[param](dict[param])
225+
) {
226+
throw new InvalidJsonError(param);
227+
}
164228
const key = claimsToLabels[param];
165229
if (param === 'catu') {
166230
claims[key] = CommonAccessTokenUri.fromDict(
@@ -170,6 +234,14 @@ function updateMapFromDict(
170234
claims[key] = CommonAccessTokenRenewal.fromDict(
171235
dict[param] as { [key: string]: any }
172236
).payload;
237+
} else if (param == 'cath') {
238+
claims[key] = CommonAccessTokenHeader.fromDict(
239+
dict[param] as { [key: string]: any }
240+
).payload;
241+
} else if (param == 'catif') {
242+
claims[key] = CommonAccessTokenIf.fromDict(
243+
dict[param] as { [key: string]: any }
244+
).payload;
173245
} else {
174246
const value = claimTransform[param]
175247
? claimTransform[param](dict[param] as string)
@@ -193,6 +265,7 @@ export class CommonAccessToken {
193265
claims['catv'] = 1;
194266
}
195267
this.payload = updateMapFromClaims(claims);
268+
this.validateTypes();
196269
}
197270

198271
/**
@@ -290,8 +363,8 @@ export class CommonAccessToken {
290363
for (const [key, value] of this.payload) {
291364
const claim = labelsToClaim[key];
292365
if (value && claimTypeValidators[claim]) {
293-
if (!claimTypeValidators[claim](value as string)) {
294-
throw new InvalidClaimTypeError(claim, value as string);
366+
if (!claimTypeValidators[claim](value)) {
367+
throw new InvalidClaimTypeError(claim, value);
295368
}
296369
}
297370
}
@@ -396,6 +469,14 @@ export class CommonAccessToken {
396469
result[key] = CommonAccessTokenRenewal.fromMap(
397470
value as Map<number, any>
398471
).toDict();
472+
} else if (key === 'cath') {
473+
result[key] = CommonAccessTokenHeader.fromMap(
474+
value as Map<string, any>
475+
).toDict();
476+
} else if (key === 'catif') {
477+
result[key] = CommonAccessTokenIf.fromMap(
478+
value as Map<number, any>
479+
).toDict();
399480
} else {
400481
const theValue = claimTransformReverse[key]
401482
? claimTransformReverse[key](value as Buffer)

src/cath.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import {
2+
labelsToMatch,
3+
MatchMap,
4+
matchToLabels,
5+
MatchValue
6+
} from './cattypes/match';
7+
8+
export type CommonAccessTokenHeaderMap = Map<string, MatchMap>;
9+
10+
export class CommonAccessTokenHeader {
11+
private cathMap: CommonAccessTokenHeaderMap = new Map();
12+
13+
public static fromDict(dict: { [key: string]: any }) {
14+
const cath = new CommonAccessTokenHeader();
15+
for (const header in dict) {
16+
const matchMap = new Map<number, MatchValue>();
17+
for (const match in dict[header]) {
18+
matchMap.set(matchToLabels[match], dict[header][match]);
19+
}
20+
cath.cathMap.set(header, matchMap);
21+
}
22+
return cath;
23+
}
24+
25+
public static fromMap(map: CommonAccessTokenHeaderMap) {
26+
const cath = new CommonAccessTokenHeader();
27+
cath.cathMap = map;
28+
return cath;
29+
}
30+
31+
toDict() {
32+
const result: { [key: string]: any } = {};
33+
this.cathMap.forEach((matchMap, header) => {
34+
const match: { [key: string]: any } = {};
35+
matchMap.forEach((value, matchType) => {
36+
match[labelsToMatch[matchType]] = value;
37+
});
38+
result[header] = match;
39+
});
40+
return result;
41+
}
42+
43+
get payload() {
44+
return this.cathMap;
45+
}
46+
}

src/catif.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { claimsToLabels, labelsToClaim } from './cat';
2+
3+
type CatIfValue = Map<number, [number, { [key: string]: string }]>;
4+
export type CommonAccessTokenIfMap = Map<number, CatIfValue>;
5+
6+
export class CommonAccessTokenIf {
7+
private catIfMap: CommonAccessTokenIfMap = new Map();
8+
9+
public static fromDict(dict: { [key: string]: any }) {
10+
const catif = new CommonAccessTokenIf();
11+
for (const catIfClaim in dict) {
12+
catif.catIfMap.set(claimsToLabels[catIfClaim], dict[catIfClaim]);
13+
}
14+
return catif;
15+
}
16+
17+
public static fromMap(map: CommonAccessTokenIfMap) {
18+
const catif = new CommonAccessTokenIf();
19+
catif.catIfMap = map;
20+
return catif;
21+
}
22+
23+
toDict() {
24+
const result: { [key: string]: any } = {};
25+
this.catIfMap.forEach((catIfValue, claim) => {
26+
result[labelsToClaim[claim]] = catIfValue;
27+
});
28+
return result;
29+
}
30+
31+
get payload() {
32+
return this.catIfMap;
33+
}
34+
}

0 commit comments

Comments
 (0)