diff --git a/components/editors/base-editor.tsx b/components/editors/base-editor.tsx index a45924a4..7319756d 100644 --- a/components/editors/base-editor.tsx +++ b/components/editors/base-editor.tsx @@ -20,6 +20,7 @@ const BaseEditor: FunctionComponent<{ value: string; lang: string; showValidMsg?: boolean; + showErrors?: boolean; hideGoToLine?: boolean; editorExtensions: Extension[]; onErrorChange?: (value: string, view?: ViewUpdate) => parseError; @@ -28,6 +29,7 @@ const BaseEditor: FunctionComponent<{ value, lang, showValidMsg, + showErrors, editorExtensions, hideGoToLine, onErrorChange, @@ -35,6 +37,7 @@ const BaseEditor: FunctionComponent<{ }) { const [error, setError] = useState(null); const editor = useRef(); + showErrors = showErrors ?? true; const scrollDocToView = (error: parseError) => { if (!editor?.current?.state?.doc) { @@ -53,7 +56,7 @@ const BaseEditor: FunctionComponent<{ }; return ( -
+
- {error && ( + {error && showErrors && (
void; }> = function (props) { return ( diff --git a/components/editors/text-editor.tsx b/components/editors/text-editor.tsx index 29247c5f..cc96b9b8 100644 --- a/components/editors/text-editor.tsx +++ b/components/editors/text-editor.tsx @@ -5,6 +5,11 @@ const TextEditor: FunctionComponent<{ value: string; showValidMsg?: boolean; onValueChange?: (value: string) => void; + onErrorChange?: ( + currentValue: string, + viewUpdate: any + ) => { message: string }; + hideGoToLine?: boolean; }> = function (props) { return ; }; diff --git a/pages/tools/base64-encode-decode.tsx b/pages/tools/base64-encode-decode.tsx index d0e46474..001fc7fc 100644 --- a/pages/tools/base64-encode-decode.tsx +++ b/pages/tools/base64-encode-decode.tsx @@ -25,27 +25,31 @@ const Base64EncodeDecode: FunctionComponent = function () {
- { - try { - setBase64(bytesToBase64(new TextEncoder().encode(value))); - } catch (error) {} - }} - /> +
+ { + try { + setBase64(bytesToBase64(new TextEncoder().encode(value))); + } catch (error) {} + }} + /> +
- { - try { - setText(new TextDecoder().decode(base64ToBytes(value))); - } catch (error) {} - }} - /> +
+ { + try { + setText(new TextDecoder().decode(base64ToBytes(value))); + } catch (error) {} + }} + /> +
diff --git a/pages/tools/csv-parser-json-converter.tsx b/pages/tools/csv-parser-json-converter.tsx index 8e3816c5..6a018487 100644 --- a/pages/tools/csv-parser-json-converter.tsx +++ b/pages/tools/csv-parser-json-converter.tsx @@ -100,54 +100,56 @@ Alice,35,Chicago,Data Scientist,Amazon,Seattle,345-678-9012`;
- { - let lines = []; - let headers = []; - - csv.split('\n').forEach((line, index) => { - if (!line.trim()) { - return; - } - - if (index === 0) { - headers = line.split(','); - - return; - } else { - const items = line.split(','); - - lines.push(items); - } - }); - - setParsedHeaders(headers); - setParsedLines(lines); - - // Convert to JSON - const json = []; - lines.forEach((line) => { - const obj = {}; - - headers.forEach((header, index) => { - obj[header] = line[index]; +
+ { + let lines = []; + let headers = []; + + csv.split('\n').forEach((line, index) => { + if (!line.trim()) { + return; + } + + if (index === 0) { + headers = line.split(','); + + return; + } else { + const items = line.split(','); + + lines.push(items); + } }); - json.push(obj); - }); + setParsedHeaders(headers); + setParsedLines(lines); - console.log(json); + // Convert to JSON + const json = []; + lines.forEach((line) => { + const obj = {}; - setJsonContent(JSON.stringify(json, null, 2)); - }} - /> + headers.forEach((header, index) => { + obj[header] = line[index]; + }); + + json.push(obj); + }); + + setJsonContent(JSON.stringify(json, null, 2)); + }} + /> +
- +
+ +
diff --git a/pages/tools/http-headers-analyzer.tsx b/pages/tools/http-headers-analyzer.tsx index fb502777..b920c64e 100644 --- a/pages/tools/http-headers-analyzer.tsx +++ b/pages/tools/http-headers-analyzer.tsx @@ -593,25 +593,27 @@ Accept-Language: en-US`; />
- { - setHeaders( - value - .split('\n') - .map((header) => { - if (!header) { - return null; - } +
+ { + setHeaders( + value + .split('\n') + .map((header) => { + if (!header) { + return null; + } - const [name] = header.split(':'); + const [name] = header.split(':'); - return name.trim(); - }) - .filter(Boolean) - ); - }} - /> + return name.trim(); + }) + .filter(Boolean) + ); + }} + /> +
diff --git a/pages/tools/index.tsx b/pages/tools/index.tsx index c78d502f..b185219c 100644 --- a/pages/tools/index.tsx +++ b/pages/tools/index.tsx @@ -106,6 +106,18 @@ const tools = [ } ], imageSrc: '/images/illustrations/csv-parser-json-converter.svg' + }, + { + title: 'Online JWT decoder', + description: + 'Decode your JWT tokens online and extract the header and payload data', + links: [ + { + src: '/tools/jwt-decode', + text: 'Decode' + } + ], + imageSrc: '/images/illustrations/jwt-decode.svg' } ]; diff --git a/pages/tools/json-formatter-beautifier.tsx b/pages/tools/json-formatter-beautifier.tsx index 46a1bbda..f056e1f1 100644 --- a/pages/tools/json-formatter-beautifier.tsx +++ b/pages/tools/json-formatter-beautifier.tsx @@ -83,22 +83,26 @@ const JsonFormatterBeautifier: FunctionComponent = function () {
- { - try { - setFormattedJsonContent( - format(value, spacesOrTabs, numberOfSpaces) - ); - } catch (error) {} - }} - /> +
+ { + try { + setFormattedJsonContent( + format(value, spacesOrTabs, numberOfSpaces) + ); + } catch (error) {} + }} + /> +
- +
+ +
-
diff --git a/pages/tools/json-to-yaml.tsx b/pages/tools/json-to-yaml.tsx index 7ad6f1b5..328868d1 100644 --- a/pages/tools/json-to-yaml.tsx +++ b/pages/tools/json-to-yaml.tsx @@ -67,28 +67,32 @@ const JsonToYaml: FunctionComponent = function () {
- { - try { - const obj = JSON.parse(value); - setYamlContent(jsyaml.dump(obj)); - } catch (error) {} - }} - /> +
+ { + try { + const obj = JSON.parse(value); + setYamlContent(jsyaml.dump(obj)); + } catch (error) {} + }} + /> +
- { - try { - setJsonContent(JSON.stringify(jsyaml.load(value), null, 2)); - } catch (error) {} - }} - /> +
+ { + try { + setJsonContent(JSON.stringify(jsyaml.load(value), null, 2)); + } catch (error) {} + }} + /> +
diff --git a/pages/tools/json-validator.tsx b/pages/tools/json-validator.tsx index 3590f0d2..d087a729 100644 --- a/pages/tools/json-validator.tsx +++ b/pages/tools/json-validator.tsx @@ -17,10 +17,12 @@ const JsonValidator: FunctionComponent = function () { />
- +
+ +
diff --git a/pages/tools/jwt-decode.tsx b/pages/tools/jwt-decode.tsx new file mode 100644 index 00000000..5285b8e3 --- /dev/null +++ b/pages/tools/jwt-decode.tsx @@ -0,0 +1,213 @@ +import { FunctionComponent, useState } from 'react'; +import JsonEditor from '../../components/editors/json-editor'; +import TextEditor from '../../components/editors/text-editor'; +import Hero from '../../components/hero'; +import Meta from '../../components/meta'; +import Layout from '../../layout/layout'; + +const decodeJwt = (token: string) => { + const parts = token.split('.'); + const header = parts[0]; + const payload = parts[1]; + + if (parts.length < 2 || !header || !payload) { + throw new Error('Invalid JWT token'); + } + + return { + header: JSON.parse(atob(header)), + payload: JSON.parse(atob(payload)) + }; +}; + +const JwtEncodeDecode: FunctionComponent = function () { + const [textContent] = useState( + `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0IiwibmFtZSI6Ik1vY2sgT29uIiwiaWF0IjoxNzI0NTcwMzkxfQ.VB0POLK_cNMijO0tDinXN3Z9C2Zy3l0MPtBqs-af_48` + ); + const [headerContent, setHeaderContent] = useState(`{ + "alg": "HS256", + "typ": "JWT" +}`); + const [payloadContent, setPayloadContent] = useState(`{ + "sub": "1234", + "name": "Mock Oon", + "iat": 1724570391 +}`); + + return ( + + + +
+
+
+
+ { + try { + const { header, payload } = decodeJwt(value); + + setHeaderContent(JSON.stringify(header, null, 2)); + setPayloadContent(JSON.stringify(payload, null, 2)); + } catch (error) {} + }} + onErrorChange={(currentValue: string, viewUpdate) => { + try { + decodeJwt(currentValue); + + return null; + } catch (error) { + return { + message: 'Invalid JWT token' + }; + } + }} + /> +
+ +
+ +
+ +
+
+
+

Header:

+
+
+ { + console.log(value); + }} + showValidMsg={false} + showErrors={false} + /> +
+
+
+
+

Payload:

+
+
+ +
+
+
+
+
+
+ +
+
+
+
+

About this tool

+

+ This tool allows you to decode your{' '} + JSON Web Tokens (JWT) and extract the{' '} + header and payload data. Simply paste your JWT + token in the input field on the left and the tool will + automatically decode it and display the header and payload data + in the right-hand side JSON editors. +

+ +

About JSON Web Tokens

+

+ JSON Web Tokens (JWT) are an open standard that defines a + compact and self-contained way for securely transmitting + information between parties as a JSON object. +

+

+ The token is composed of three parts: the{' '} + header, the payload, and the{' '} + signature. The content of a JWT is encoded + using Base64 encoding, which makes it readable to humans but + still secure. The information in the header and payload is not + encrypted, but it is signed using a secret key or a + public/private key pair. This allows the recipient to verify + that the token has not been tampered with. +

+

+ JWTs are commonly used for authentication and{' '} + authorization in web applications, APIs, and + microservices. They are often used as a replacement for + traditional session-based authentication systems because they + are stateless, scalable, and secure. +

+

+ The header typically consists of two parts: the type of the + token, which is JWT, and the signing algorithm being used, such + as HMAC SHA256 or RSA.
+ Example: {"{ 'alg': 'HS256', 'typ': 'JWT' }"} +

+

+ The payload contains the claims, which are statements about an + entity (typically, the user) and additional data. +
+ Example:{' '} + + {"{ 'sub': '1234567890', 'name': 'John Doe', 'admin': true }"} + +

+

+ Usual claims in the payload are: +

+
    +
  • + iss (issuer): The issuer of the token, + usually a URL or the name of the service that issued the + token. +
  • +
  • + sub (subject): The subject of the token, + usually the user ID or a unique identifier for the user. +
  • +
  • + aud (audience): The audience of the token, + identifying the recipients for which the token is intended. +
  • +
  • + exp (expiration time): The expiration time of + the token, after which it is no longer valid. +
  • +
  • + nbf (not before): The time before which the + token is not valid. +
  • +
  • + iat (issued at): The time the token was + issued. +
  • +
  • + jti (JWT ID): A unique identifier for the + token. +
  • +
+ +

+ Aside from these standard claims, you can also include custom + claims in the payload to store additional information about the + user or the token itself. +

+
+
+
+
+
+ ); +}; + +export default JwtEncodeDecode; diff --git a/pages/tools/string-length-word-counter.tsx b/pages/tools/string-length-word-counter.tsx index 2a1814cd..1707878c 100644 --- a/pages/tools/string-length-word-counter.tsx +++ b/pages/tools/string-length-word-counter.tsx @@ -39,16 +39,18 @@ const StringLengthCounter: FunctionComponent = function () {

{stats.lines}

- { - setStats({ - length: value.length, - words: value.trim().split(/\s+/).length, - lines: value.split(/\n|\r\n/).length - }); - }} - /> +
+ { + setStats({ + length: value.length, + words: value.trim().split(/\s+/).length, + lines: value.split(/\n|\r\n/).length + }); + }} + /> +
diff --git a/pages/tools/xml-to-json.tsx b/pages/tools/xml-to-json.tsx index c6faaaf5..e8f26b11 100644 --- a/pages/tools/xml-to-json.tsx +++ b/pages/tools/xml-to-json.tsx @@ -36,37 +36,41 @@ const XmlToJson: FunctionComponent = function () {
- { - try { - setJsonContent( - xml2json(value, { - compact: true, - spaces: 2 - }) - ); - } catch (error) {} - }} - /> +
+ { + try { + setJsonContent( + xml2json(value, { + compact: true, + spaces: 2 + }) + ); + } catch (error) {} + }} + /> +
- { - try { - setXmlContent( - json2xml(value, { - compact: true, - spaces: 2 - }) - ); - } catch (error) {} - }} - /> +
+ { + try { + setXmlContent( + json2xml(value, { + compact: true, + spaces: 2 + }) + ); + } catch (error) {} + }} + /> +
diff --git a/pages/tools/xml-validator.tsx b/pages/tools/xml-validator.tsx index e38b4f83..4a1ff86c 100644 --- a/pages/tools/xml-validator.tsx +++ b/pages/tools/xml-validator.tsx @@ -18,10 +18,12 @@ const XmlValidator: FunctionComponent = function () {
- \n \n Paste your XML here\n`} - showValidMsg - /> +
+ \n \n Paste your XML here\n`} + showValidMsg + /> +
diff --git a/pages/tools/yaml-validator.tsx b/pages/tools/yaml-validator.tsx index b4ce8dc1..1881abce 100644 --- a/pages/tools/yaml-validator.tsx +++ b/pages/tools/yaml-validator.tsx @@ -18,8 +18,9 @@ const YamlValidator: FunctionComponent = function () {
- + + showValidMsg + /> +
diff --git a/public/images/illustrations/jwt-decode.svg b/public/images/illustrations/jwt-decode.svg new file mode 100644 index 00000000..91a2bc66 --- /dev/null +++ b/public/images/illustrations/jwt-decode.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/styles/_user.scss b/styles/_user.scss index 0af13b00..e42aecb5 100644 --- a/styles/_user.scss +++ b/styles/_user.scss @@ -52,10 +52,17 @@ pre .code { height: 80vh; } +.code-editor-container-half { + height: 40vh; +} + @media screen and (min-width: 768px) { .code-editor-container { height: 60vh; } + .code-editor-container-half { + height: 30vh; + } .code-editor-layout-dual { display: grid;