From a4267f4caf281353532928a88c1dfae7ea7f6e54 Mon Sep 17 00:00:00 2001 From: rusconn Date: Fri, 1 Apr 2022 15:47:57 +0900 Subject: [PATCH] feat: add jwt decoder --- README.md | 2 +- package.json | 1 + .../encoders-decoders/jwt/CodeEditor.tsx | 19 ++++++ .../jwt/CodeEditorLoading.tsx | 8 +++ .../pages/encoders-decoders/jwt/Content.tsx | 62 +++++++++++++++++++ .../pages/encoders-decoders/jwt/index.ts | 3 + src/data/tools.tsx | 4 +- src/libs/$path.ts | 6 ++ src/libs/jwt.ts | 22 +++++++ src/pages/encoders-decoders/jwt.tsx | 7 +++ yarn.lock | 5 ++ 11 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 src/components/pages/encoders-decoders/jwt/CodeEditor.tsx create mode 100644 src/components/pages/encoders-decoders/jwt/CodeEditorLoading.tsx create mode 100644 src/components/pages/encoders-decoders/jwt/Content.tsx create mode 100644 src/components/pages/encoders-decoders/jwt/index.ts create mode 100644 src/libs/jwt.ts create mode 100644 src/pages/encoders-decoders/jwt.tsx diff --git a/README.md b/README.md index c6eecd8..7365912 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ A web clone of [DevToys](https://github.com/veler/DevToys) - [x] Add all tools page mock - [ ] Implement tools - [x] Converters - - [ ] Encoders / Decoders + - [x] Encoders / Decoders - [ ] Formatters - [ ] Generators - [ ] Text diff --git a/package.json b/package.json index 0e4c23d..b76f605 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "fuse.js": "^6.5.3", "html-entities": "^2.3.3", "js-base64": "^3.7.2", + "jwt-decode": "^3.1.2", "next": "12.1.0", "react": "17.0.2", "react-ace": "^9.5.0", diff --git a/src/components/pages/encoders-decoders/jwt/CodeEditor.tsx b/src/components/pages/encoders-decoders/jwt/CodeEditor.tsx new file mode 100644 index 0000000..2081d54 --- /dev/null +++ b/src/components/pages/encoders-decoders/jwt/CodeEditor.tsx @@ -0,0 +1,19 @@ +import { ComponentPropsWithoutRef, memo } from "react"; + +import CodeEditor from "@/components/common/CodeEditor"; + +type Props = Omit, "height" | "width">; + +const StyledComponent = (props: Props) => ( + +); + +export const Component = memo(StyledComponent); + +export default Component; diff --git a/src/components/pages/encoders-decoders/jwt/CodeEditorLoading.tsx b/src/components/pages/encoders-decoders/jwt/CodeEditorLoading.tsx new file mode 100644 index 0000000..2d630d3 --- /dev/null +++ b/src/components/pages/encoders-decoders/jwt/CodeEditorLoading.tsx @@ -0,0 +1,8 @@ +import { Skeleton } from "@mui/material"; +import { memo } from "react"; + +const StyledComponent = () => ; + +export const Component = memo(StyledComponent); + +export default Component; diff --git a/src/components/pages/encoders-decoders/jwt/Content.tsx b/src/components/pages/encoders-decoders/jwt/Content.tsx new file mode 100644 index 0000000..c6020f1 --- /dev/null +++ b/src/components/pages/encoders-decoders/jwt/Content.tsx @@ -0,0 +1,62 @@ +import dynamic from "next/dynamic"; +import { ComponentPropsWithoutRef, memo, useCallback, useState } from "react"; + +import { Main, MainItem, TextField } from "@/components/common"; +import { decode } from "@/libs/jwt"; + +import CodeEditorLoading from "./CodeEditorLoading"; + +// https://github.com/securingsincity/react-ace/issues/27 +const CodeEditor = dynamic( + async () => { + const ace = await import("./CodeEditor"); + await import("ace-builds/src-noconflict/mode-json"); + return ace; + }, + { ssr: false, loading: CodeEditorLoading } +); + +type TextFieldValue = ComponentPropsWithoutRef["value"]; +type CodeValue = NonNullable["value"]>; +type OnTextFieldChange = NonNullable["onChange"]>; + +type Props = { + jwt: TextFieldValue; + header: CodeValue; + payload: CodeValue; + onJwtChange: OnTextFieldChange; +}; + +const StyledComponent = ({ jwt, header, payload, onJwtChange }: Props) => ( +
+ + + + + + + + + +
+); + +export const Component = memo(StyledComponent); + +const Container = () => { + const [jwt, setJwt] = useState( + "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" + ); + + const onJwtChange: Props["onJwtChange"] = useCallback(({ currentTarget: { value } }) => { + setJwt(value); + }, []); + + const { headerObj, payloadObj } = decode(jwt); + const header = JSON.stringify(headerObj, null, 2) ?? ""; + const payload = JSON.stringify(payloadObj, null, 2) ?? ""; + + return ; +}; + +export default Container; diff --git a/src/components/pages/encoders-decoders/jwt/index.ts b/src/components/pages/encoders-decoders/jwt/index.ts new file mode 100644 index 0000000..ae1b8af --- /dev/null +++ b/src/components/pages/encoders-decoders/jwt/index.ts @@ -0,0 +1,3 @@ +import Content from "./Content"; + +export { Content }; diff --git a/src/data/tools.tsx b/src/data/tools.tsx index fc1fdd7..67f0345 100644 --- a/src/data/tools.tsx +++ b/src/data/tools.tsx @@ -82,8 +82,8 @@ const toolGroups = [ longTitle: "JWT Decoder", description: "Decode a JWT header, payload and signature", keywords: "jwt json web token decocder", - href: pagesPath.$url(), - disabled: true, + href: pagesPath.encoders_decoders.jwt.$url(), + disabled: false, }, ], }, diff --git a/src/libs/$path.ts b/src/libs/$path.ts index d61c174..911d6c6 100644 --- a/src/libs/$path.ts +++ b/src/libs/$path.ts @@ -26,6 +26,12 @@ export const pagesPath = { hash: url?.hash, }), }, + jwt: { + $url: (url?: { hash?: string }) => ({ + pathname: "/encoders-decoders/jwt" as const, + hash: url?.hash, + }), + }, url: { $url: (url?: { hash?: string }) => ({ pathname: "/encoders-decoders/url" as const, diff --git a/src/libs/jwt.ts b/src/libs/jwt.ts new file mode 100644 index 0000000..e10aa70 --- /dev/null +++ b/src/libs/jwt.ts @@ -0,0 +1,22 @@ +import jwt_decode from "jwt-decode"; + +export const decode = (token: string) => { + let headerObj; + let payloadObj; + + if (token.split(".").length === 3) { + /* eslint-disable no-empty */ + + try { + headerObj = jwt_decode(token, { header: true }); + } catch {} + + try { + payloadObj = jwt_decode(token, { header: false }); + } catch {} + + /* eslint-enable no-empty */ + } + + return { headerObj, payloadObj }; +}; diff --git a/src/pages/encoders-decoders/jwt.tsx b/src/pages/encoders-decoders/jwt.tsx new file mode 100644 index 0000000..f654299 --- /dev/null +++ b/src/pages/encoders-decoders/jwt.tsx @@ -0,0 +1,7 @@ +import type { NextPage } from "next"; + +import { Content } from "@/components/pages/encoders-decoders/jwt"; + +const Page: NextPage = Content; + +export default Page; diff --git a/yarn.lock b/yarn.lock index 43c236f..92113cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1677,6 +1677,11 @@ json5@^1.0.1: array-includes "^3.1.3" object.assign "^4.1.2" +jwt-decode@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-3.1.2.tgz#3fb319f3675a2df0c2895c8f5e9fa4b67b04ed59" + integrity sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A== + language-subtag-registry@~0.3.2: version "0.3.21" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"