feat(new-tool): jwt parser (#262)
* npm install jwt-decode * added base tool structure * added function to decode JWT and display header and payload * use a table to display the data * show human readable values * added switch to toggle display of parsed values * lint * replaced basic package-lock.json with pnpm-lock.json * change the icon of the tool * simplify return * use camelCase * added description of the tool * always parse the values * use camelCase...
This commit is contained in:
		
							parent
							
								
									ebb7301a98
								
							
						
					
					
						commit
						acc7f0a586
					
				| @ -48,6 +48,7 @@ | |||||||
|     "fuse.js": "^6.6.2", |     "fuse.js": "^6.6.2", | ||||||
|     "highlight.js": "^11.6.0", |     "highlight.js": "^11.6.0", | ||||||
|     "json5": "^2.2.1", |     "json5": "^2.2.1", | ||||||
|  |     "jwt-decode": "^3.1.2", | ||||||
|     "lodash": "^4.17.21", |     "lodash": "^4.17.21", | ||||||
|     "mathjs": "^10.6.4", |     "mathjs": "^10.6.4", | ||||||
|     "mime-types": "^2.1.35", |     "mime-types": "^2.1.35", | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										6
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @ -42,6 +42,7 @@ specifiers: | |||||||
|   highlight.js: ^11.6.0 |   highlight.js: ^11.6.0 | ||||||
|   jsdom: ^19.0.0 |   jsdom: ^19.0.0 | ||||||
|   json5: ^2.2.1 |   json5: ^2.2.1 | ||||||
|  |   jwt-decode: ^3.1.2 | ||||||
|   less: ^4.1.3 |   less: ^4.1.3 | ||||||
|   lodash: ^4.17.21 |   lodash: ^4.17.21 | ||||||
|   mathjs: ^10.6.4 |   mathjs: ^10.6.4 | ||||||
| @ -85,6 +86,7 @@ dependencies: | |||||||
|   fuse.js: 6.6.2 |   fuse.js: 6.6.2 | ||||||
|   highlight.js: 11.6.0 |   highlight.js: 11.6.0 | ||||||
|   json5: 2.2.1 |   json5: 2.2.1 | ||||||
|  |   jwt-decode: 3.1.2 | ||||||
|   lodash: 4.17.21 |   lodash: 4.17.21 | ||||||
|   mathjs: 10.6.4 |   mathjs: 10.6.4 | ||||||
|   mime-types: 2.1.35 |   mime-types: 2.1.35 | ||||||
| @ -4838,6 +4840,10 @@ packages: | |||||||
|       promise: 7.3.1 |       promise: 7.3.1 | ||||||
|     dev: true |     dev: true | ||||||
| 
 | 
 | ||||||
|  |   /jwt-decode/3.1.2: | ||||||
|  |     resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} | ||||||
|  |     dev: false | ||||||
|  | 
 | ||||||
|   /kind-of/6.0.3: |   /kind-of/6.0.3: | ||||||
|     resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} |     resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} | ||||||
|     engines: {node: '>=0.10.0'} |     engines: {node: '>=0.10.0'} | ||||||
|  | |||||||
| @ -53,6 +53,7 @@ import { | |||||||
|   NTooltip, |   NTooltip, | ||||||
|   NUpload, |   NUpload, | ||||||
|   NUploadDragger, |   NUploadDragger, | ||||||
|  |   NPopover, | ||||||
|   NCheckbox, |   NCheckbox, | ||||||
| } from 'naive-ui'; | } from 'naive-ui'; | ||||||
| 
 | 
 | ||||||
| @ -111,6 +112,7 @@ const components = [ | |||||||
|   NIcon, |   NIcon, | ||||||
|   NSwitch, |   NSwitch, | ||||||
|   NCollapseTransition, |   NCollapseTransition, | ||||||
|  |   NPopover, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| export const naive = create({ components }); | export const naive = create({ components }); | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { tool as jwtParser } from './jwt-parser'; | ||||||
| import { tool as chmodCalculator } from './chmod-calculator'; | import { tool as chmodCalculator } from './chmod-calculator'; | ||||||
| import { tool as mimeTypes } from './mime-types'; | import { tool as mimeTypes } from './mime-types'; | ||||||
| import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; | import { tool as otpCodeGeneratorAndValidator } from './otp-code-generator-and-validator'; | ||||||
| @ -63,6 +64,7 @@ export const toolsByCategory: ToolCategory[] = [ | |||||||
|       metaTagGenerator, |       metaTagGenerator, | ||||||
|       otpCodeGeneratorAndValidator, |       otpCodeGeneratorAndValidator, | ||||||
|       mimeTypes, |       mimeTypes, | ||||||
|  |       jwtParser, | ||||||
|     ], |     ], | ||||||
|   }, |   }, | ||||||
|   { |   { | ||||||
|  | |||||||
							
								
								
									
										29
									
								
								src/tools/jwt-parser/claim.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/tools/jwt-parser/claim.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | |||||||
|  | <template> | ||||||
|  |   <n-space> | ||||||
|  |     <em>{{ claim }}</em> | ||||||
|  |     <span v-if="label.label !== claim"> | ||||||
|  |       <n-popover placement="right" trigger="hover"> | ||||||
|  |         <template #trigger> | ||||||
|  |           <n-icon :component="InfoCircle" trigger /> | ||||||
|  |         </template> | ||||||
|  |         {{ label.label }} | ||||||
|  |         <template v-if="label.ref !== ''" #footer> {{ label.ref }} </template> | ||||||
|  |       </n-popover> | ||||||
|  |     </span> | ||||||
|  |   </n-space> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | import { InfoCircle } from '@vicons/tabler'; | ||||||
|  | import { getClaimLabel } from './jwt-parser.service'; | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   claim: { | ||||||
|  |     type: String, | ||||||
|  |     default: '', | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const label = computed(() => getClaimLabel(props.claim ? props.claim : '')); | ||||||
|  | </script> | ||||||
							
								
								
									
										11
									
								
								src/tools/jwt-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/jwt-parser/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | import { Key } from '@vicons/tabler'; | ||||||
|  | import { defineTool } from '../tool'; | ||||||
|  | 
 | ||||||
|  | export const tool = defineTool({ | ||||||
|  |   name: 'JWT parser', | ||||||
|  |   path: '/jwt-parser', | ||||||
|  |   description: 'Parse a JWT (JSON Web Token) to display its content.', | ||||||
|  |   keywords: ['jwt', 'parser'], | ||||||
|  |   component: () => import('./jwt-parser.vue'), | ||||||
|  |   icon: Key, | ||||||
|  | }); | ||||||
							
								
								
									
										429
									
								
								src/tools/jwt-parser/jwt-parser.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										429
									
								
								src/tools/jwt-parser/jwt-parser.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,429 @@ | |||||||
|  | import jwt_decode, { InvalidTokenError } from 'jwt-decode'; | ||||||
|  | 
 | ||||||
|  | interface JWT { | ||||||
|  |   header: Map<string, unknown>; | ||||||
|  |   payload: Map<string, unknown>; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function safeJwtDecode(rawJwt: string): JWT { | ||||||
|  |   try { | ||||||
|  |     const header = jwt_decode(rawJwt, { header: true }) as Map<string, unknown>; | ||||||
|  |     const payload = jwt_decode(rawJwt) as Map<string, unknown>; | ||||||
|  |     return { header, payload }; | ||||||
|  |   } catch (e) { | ||||||
|  |     if (e instanceof InvalidTokenError) { | ||||||
|  |       return { header: new Map<string, unknown>(), payload: new Map<string, unknown>() }; | ||||||
|  |     } else { | ||||||
|  |       throw e; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function getClaimLabel(claim: string): { label: string; ref: string } { | ||||||
|  |   const infos = STANDARD_CLAIMS.find((info) => info.name === claim); | ||||||
|  |   if (infos) { | ||||||
|  |     return { label: infos.long_name, ref: infos.ref }; | ||||||
|  |   } | ||||||
|  |   switch (claim) { | ||||||
|  |     case 'typ': | ||||||
|  |       return { label: 'Type', ref: '' }; | ||||||
|  |     case 'alg': | ||||||
|  |       return { label: 'Algorithm', ref: '' }; | ||||||
|  |   } | ||||||
|  |   return { label: claim, ref: '' }; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export function parseClaimValue(claim: string, value: unknown): { value: unknown; extension?: unknown } { | ||||||
|  |   switch (claim) { | ||||||
|  |     case 'exp': | ||||||
|  |     case 'nbf': | ||||||
|  |     case 'iat': { | ||||||
|  |       // Convert to milliseconds, JWT specs says it should be in seconds, JS
 | ||||||
|  |       // works with milliseconds
 | ||||||
|  |       value = typeof value === 'string' ? parseInt(value) : value; | ||||||
|  |       const date = new Date((value as number) * 1000); | ||||||
|  |       return { value: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`, extension: value }; | ||||||
|  |     } | ||||||
|  |     case 'alg': | ||||||
|  |       return { value: AlgorithmKeyDescriptionMapping[value as string], extension: value }; | ||||||
|  |     default: | ||||||
|  |       if (typeof value === 'boolean') { | ||||||
|  |         // Perhaps there's a better way to do this?
 | ||||||
|  |         return { value: value ? 'true' : 'false' }; | ||||||
|  |       } | ||||||
|  |       return { value: value }; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // From https://datatracker.ietf.org/doc/html/rfc7518#section-3.1
 | ||||||
|  | const AlgorithmKeyDescriptionMapping: { [k: string]: string } = { | ||||||
|  |   HS256: 'HMAC using SHA-256', | ||||||
|  |   HS384: 'HMAC using SHA-384', | ||||||
|  |   HS512: 'HMAC using SHA-512', | ||||||
|  |   RS256: 'RSASSA-PKCS1-v1_5 using SHA-256', | ||||||
|  |   RS384: 'RSASSA-PKCS1-v1_5 using SHA-384', | ||||||
|  |   RS512: 'RSASSA-PKCS1-v1_5 using SHA-512', | ||||||
|  |   ES256: 'ECDSA using P-256 and SHA-256', | ||||||
|  |   ES384: 'ECDSA using P-384 and SHA-384', | ||||||
|  |   ES512: 'ECDSA using P-521 and SHA-512', | ||||||
|  |   PS256: 'RSASSA-PSS using SHA-256 and MGF1 with SHA-256', | ||||||
|  |   PS384: 'RSASSA-PSS using SHA-384 and MGF1 with SHA-384', | ||||||
|  |   PS512: 'RSASSA-PSS using SHA-512 and MGF1 with SHA-512', | ||||||
|  |   none: 'No digital signature or MAC performed', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | // List extracted from IANA: https://www.iana.org/assignments/jwt/jwt.xhtml
 | ||||||
|  | const STANDARD_CLAIMS = [ | ||||||
|  |   { | ||||||
|  |     name: 'iss', | ||||||
|  |     long_name: 'Issuer', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sub', | ||||||
|  |     long_name: 'Subject', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'aud', | ||||||
|  |     long_name: 'Audience', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.3]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'exp', | ||||||
|  |     long_name: 'Expiration Time', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.4]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'nbf', | ||||||
|  |     long_name: 'Not Before', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.5]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'iat', | ||||||
|  |     long_name: 'Issued At', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.6]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'jti', | ||||||
|  |     long_name: 'JWT ID', | ||||||
|  |     ref: '[RFC7519 - Section 4.1.7]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'name', | ||||||
|  |     long_name: 'Full name', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'given_name', | ||||||
|  |     long_name: 'Given name(s) or first name(s)', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'family_name', | ||||||
|  |     long_name: 'Surname(s) or last name(s)', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'middle_name', | ||||||
|  |     long_name: 'Middle name(s)', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'nickname', | ||||||
|  |     long_name: 'Casual name', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'preferred_username', | ||||||
|  |     long_name: 'Shorthand name by which the End-User wishes to be referred to', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'profile', | ||||||
|  |     long_name: 'Profile page URL', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'picture', | ||||||
|  |     long_name: 'Profile picture URL', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'website', | ||||||
|  |     long_name: 'Web page or blog URL', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'email', | ||||||
|  |     long_name: 'Preferred e-mail address', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'email_verified', | ||||||
|  |     long_name: 'True if the e-mail address has been verified; otherwise false', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'gender', | ||||||
|  |     long_name: 'Gender', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'birthdate', | ||||||
|  |     long_name: 'Birthday', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'zoneinfo', | ||||||
|  |     long_name: 'Time zone', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'locale', | ||||||
|  |     long_name: 'Locale', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'phone_number', | ||||||
|  |     long_name: 'Preferred telephone number', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'phone_number_verified', | ||||||
|  |     long_name: 'True if the phone number has been verified; otherwise false', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'address', | ||||||
|  |     long_name: 'Preferred postal address', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'updated_at', | ||||||
|  |     long_name: 'Time the information was last updated', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 5.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'azp', | ||||||
|  |     long_name: 'Authorized party - the party to which the ID Token was issued', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'nonce', | ||||||
|  |     long_name: 'Value used to associate a Client session with an ID Token', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'auth_time', | ||||||
|  |     long_name: 'Time when the authentication occurred', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'at_hash', | ||||||
|  |     long_name: 'Access Token hash value', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'c_hash', | ||||||
|  |     long_name: 'Code hash value', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 3.3.2.11]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'acr', | ||||||
|  |     long_name: 'Authentication Context Class Reference', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'amr', | ||||||
|  |     long_name: 'Authentication Methods References', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sub_jwk', | ||||||
|  |     long_name: 'Public key used to check the signature of an ID Token', | ||||||
|  |     ref: '[OpenID Connect Core 1.0 - Section 7.4]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'cnf', | ||||||
|  |     long_name: 'Confirmation', | ||||||
|  |     ref: '[RFC7800 - Section 3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sip_from_tag', | ||||||
|  |     long_name: 'SIP From tag header field parameter value', | ||||||
|  |     ref: '[RFC8055][RFC3261]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sip_date', | ||||||
|  |     long_name: 'SIP Date header field value', | ||||||
|  |     ref: '[RFC8055][RFC3261]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sip_callid', | ||||||
|  |     long_name: 'SIP Call-Id header field value', | ||||||
|  |     ref: '[RFC8055][RFC3261]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sip_cseq_num', | ||||||
|  |     long_name: 'SIP CSeq numeric header field parameter value', | ||||||
|  |     ref: '[RFC8055][RFC3261]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sip_via_branch', | ||||||
|  |     long_name: 'SIP Via branch header field parameter value', | ||||||
|  |     ref: '[RFC8055][RFC3261]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'orig', | ||||||
|  |     long_name: 'Originating Identity String', | ||||||
|  |     ref: '[RFC8225 - Section 5.2.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'dest', | ||||||
|  |     long_name: 'Destination Identity String', | ||||||
|  |     ref: '[RFC8225 - Section 5.2.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'mky', | ||||||
|  |     long_name: 'Media Key Fingerprint String', | ||||||
|  |     ref: '[RFC8225 - Section 5.2.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'events', | ||||||
|  |     long_name: 'Security Events', | ||||||
|  |     ref: '[RFC8417 - Section 2.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'toe', | ||||||
|  |     long_name: 'Time of Event', | ||||||
|  |     ref: '[RFC8417 - Section 2.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'txn', | ||||||
|  |     long_name: 'Transaction Identifier', | ||||||
|  |     ref: '[RFC8417 - Section 2.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'rph', | ||||||
|  |     long_name: 'Resource Priority Header Authorization', | ||||||
|  |     ref: '[RFC8443 - Section 3]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sid', | ||||||
|  |     long_name: 'Session ID', | ||||||
|  |     ref: '[OpenID Connect Front-Channel Logout 1.0 - Section 3]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'vot', | ||||||
|  |     long_name: 'Vector of Trust value', | ||||||
|  |     ref: '[RFC8485]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'vtm', | ||||||
|  |     long_name: 'Vector of Trust trustmark URL', | ||||||
|  |     ref: '[RFC8485]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'attest', | ||||||
|  |     long_name: 'Attestation level as defined in SHAKEN framework', | ||||||
|  |     ref: '[RFC8588]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'origid', | ||||||
|  |     long_name: 'Originating Identifier as defined in SHAKEN framework', | ||||||
|  |     ref: '[RFC8588]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'act', | ||||||
|  |     long_name: 'Actor', | ||||||
|  |     ref: '[RFC8693 - Section 4.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'scope', | ||||||
|  |     long_name: 'Scope Values', | ||||||
|  |     ref: '[RFC8693 - Section 4.2]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'client_id', | ||||||
|  |     long_name: 'Client Identifier', | ||||||
|  |     ref: '[RFC8693 - Section 4.3]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'may_act', | ||||||
|  |     long_name: 'Authorized Actor - the party that is authorized to become the actor', | ||||||
|  |     ref: '[RFC8693 - Section 4.4]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'jcard', | ||||||
|  |     long_name: 'jCard data', | ||||||
|  |     ref: '[RFC8688][RFC7095]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'at_use_nbr', | ||||||
|  |     long_name: 'Number of API requests for which the access token can be used', | ||||||
|  |     ref: '[ETSI GS NFV-SEC 022 V2.7.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'div', | ||||||
|  |     long_name: 'Diverted Target of a Call', | ||||||
|  |     ref: '[RFC8946]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'opt', | ||||||
|  |     long_name: 'Original PASSporT (in Full Form)', | ||||||
|  |     ref: '[RFC8946]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'vc', | ||||||
|  |     long_name: 'Verifiable Credential as specified in the W3C Recommendation', | ||||||
|  |     ref: '[W3C Recommendation Verifiable Credentials Data Model 1.0 - Expressing verifiable information on the Web (19 November 2019) - Section 6.3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'vp', | ||||||
|  |     long_name: 'Verifiable Presentation as specified in the W3C Recommendation', | ||||||
|  |     ref: '[W3C Recommendation Verifiable Credentials Data Model 1.0 - Expressing verifiable information on the Web (19 November 2019) - Section 6.3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'sph', | ||||||
|  |     long_name: 'SIP Priority header field', | ||||||
|  |     ref: '[RFC9027]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'ace_profile', | ||||||
|  |     long_name: 'The ACE profile a token is supposed to be used with.', | ||||||
|  |     ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'cnonce', | ||||||
|  |     long_name: | ||||||
|  |       'client-nonce. A nonce previously provided to the AS by the RS via the client. Used to verify token freshness when the RS cannot synchronize its clock with the AS.', | ||||||
|  |     ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'exi', | ||||||
|  |     long_name: | ||||||
|  |       'Expires in. Lifetime of the token in seconds from the time the RS first sees it. Used to implement a weaker from of token expiration for devices that cannot synchronize their internal clocks.', | ||||||
|  |     ref: '[RFC-ietf-ace-oauth-authz-46 - Section 5.10.3]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'roles', | ||||||
|  |     long_name: 'Roles', | ||||||
|  |     ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'groups', | ||||||
|  |     long_name: 'Groups', | ||||||
|  |     ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'entitlements', | ||||||
|  |     long_name: 'Entitlements', | ||||||
|  |     ref: '[RFC7643 - Section 4.1.2][RFC9068 - Section 2.2.3.1]', | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     name: 'token_introspection', | ||||||
|  |     long_name: 'Token introspection response', | ||||||
|  |     ref: '[RFC-ietf-oauth-jwt-introspection-response-12 - Section 5]', | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
							
								
								
									
										63
									
								
								src/tools/jwt-parser/jwt-parser.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/tools/jwt-parser/jwt-parser.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,63 @@ | |||||||
|  | <template> | ||||||
|  |   <n-card> | ||||||
|  |     <n-form-item label="JWT to decode" :feedback="validation.message" :validation-status="validation.status"> | ||||||
|  |       <n-input v-model:value="rawJwt" type="textarea" placeholder="Put your token here..." rows="5" /> | ||||||
|  |     </n-form-item> | ||||||
|  | 
 | ||||||
|  |     <n-table> | ||||||
|  |       <tbody> | ||||||
|  |         <td colspan="2" class="table-header"><strong>Header</strong></td> | ||||||
|  |         <tr v-for="[key, value] in Object.entries(decodedJWT.header)" :key="key"> | ||||||
|  |           <td class="claims"><claim-vue :claim="key" /></td> | ||||||
|  |           <td> | ||||||
|  |             <value-vue :claim="key" :value="value" /> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |         <td colspan="2" class="table-header"><strong>Payload</strong></td> | ||||||
|  |         <tr v-for="[key, value] in Object.entries(decodedJWT.payload)" :key="key"> | ||||||
|  |           <td class="claims"><claim-vue :claim="key" /></td> | ||||||
|  |           <td> | ||||||
|  |             <value-vue :claim="key" :value="value" /> | ||||||
|  |           </td> | ||||||
|  |         </tr> | ||||||
|  |       </tbody> | ||||||
|  |     </n-table> | ||||||
|  |   </n-card> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { computed, ref } from 'vue'; | ||||||
|  | import jwt_decode from 'jwt-decode'; | ||||||
|  | import { useValidation } from '@/composable/validation'; | ||||||
|  | import { isNotThrowing } from '@/utils/boolean'; | ||||||
|  | import { safeJwtDecode } from './jwt-parser.service'; | ||||||
|  | import claimVue from './claim.vue'; | ||||||
|  | import valueVue from './value.vue'; | ||||||
|  | 
 | ||||||
|  | const rawJwt = ref( | ||||||
|  |   'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', | ||||||
|  | ); | ||||||
|  | 
 | ||||||
|  | const decodedJWT = computed(() => { | ||||||
|  |   return safeJwtDecode(rawJwt.value); | ||||||
|  | }); | ||||||
|  | const validation = useValidation({ | ||||||
|  |   source: rawJwt, | ||||||
|  |   rules: [ | ||||||
|  |     { | ||||||
|  |       validator: (value) => value.length > 0 && isNotThrowing(() => jwt_decode(value, { header: true })), | ||||||
|  |       message: 'Invalid JWT', | ||||||
|  |     }, | ||||||
|  |   ], | ||||||
|  | }); | ||||||
|  | </script> | ||||||
|  | 
 | ||||||
|  | <style lang="less" scoped> | ||||||
|  | .table-header { | ||||||
|  |   text-align: center; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .claims { | ||||||
|  |   width: 20%; | ||||||
|  | } | ||||||
|  | </style> | ||||||
							
								
								
									
										24
									
								
								src/tools/jwt-parser/value.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/tools/jwt-parser/value.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <template> | ||||||
|  |   <n-space> | ||||||
|  |     {{ value.value }} | ||||||
|  |     <em v-if="value.extension">({{ value.extension }})</em> | ||||||
|  |   </n-space> | ||||||
|  | </template> | ||||||
|  | 
 | ||||||
|  | <script setup lang="ts"> | ||||||
|  | import { computed } from 'vue'; | ||||||
|  | import { parseClaimValue } from './jwt-parser.service'; | ||||||
|  | 
 | ||||||
|  | const props = defineProps({ | ||||||
|  |   claim: { | ||||||
|  |     type: String, | ||||||
|  |     default: '', | ||||||
|  |   }, | ||||||
|  |   value: { | ||||||
|  |     type: String, | ||||||
|  |     default: '', | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | const value = computed(() => parseClaimValue(props.claim, props.value)); | ||||||
|  | </script> | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user