feat: add jwt decoder

This commit is contained in:
rusconn
2022-04-01 15:47:57 +09:00
parent 2985e90859
commit a4267f4caf
11 changed files with 136 additions and 3 deletions

View File

@@ -8,7 +8,7 @@ A web clone of [DevToys](https://github.com/veler/DevToys)
- [x] Add all tools page mock - [x] Add all tools page mock
- [ ] Implement tools - [ ] Implement tools
- [x] Converters - [x] Converters
- [ ] Encoders / Decoders - [x] Encoders / Decoders
- [ ] Formatters - [ ] Formatters
- [ ] Generators - [ ] Generators
- [ ] Text - [ ] Text

View File

@@ -29,6 +29,7 @@
"fuse.js": "^6.5.3", "fuse.js": "^6.5.3",
"html-entities": "^2.3.3", "html-entities": "^2.3.3",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"jwt-decode": "^3.1.2",
"next": "12.1.0", "next": "12.1.0",
"react": "17.0.2", "react": "17.0.2",
"react-ace": "^9.5.0", "react-ace": "^9.5.0",

View File

@@ -0,0 +1,19 @@
import { ComponentPropsWithoutRef, memo } from "react";
import CodeEditor from "@/components/common/CodeEditor";
type Props = Omit<ComponentPropsWithoutRef<typeof CodeEditor>, "height" | "width">;
const StyledComponent = (props: Props) => (
<CodeEditor
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
readOnly
height="160px"
width="100%"
/>
);
export const Component = memo(StyledComponent);
export default Component;

View File

@@ -0,0 +1,8 @@
import { Skeleton } from "@mui/material";
import { memo } from "react";
const StyledComponent = () => <Skeleton variant="rectangular" width="100%" height="160px" />;
export const Component = memo(StyledComponent);
export default Component;

View File

@@ -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<typeof TextField>["value"];
type CodeValue = NonNullable<ComponentPropsWithoutRef<typeof CodeEditor>["value"]>;
type OnTextFieldChange = NonNullable<ComponentPropsWithoutRef<typeof TextField>["onChange"]>;
type Props = {
jwt: TextFieldValue;
header: CodeValue;
payload: CodeValue;
onJwtChange: OnTextFieldChange;
};
const StyledComponent = ({ jwt, header, payload, onJwtChange }: Props) => (
<Main title="JWT Decoder">
<MainItem title="Jwt Token">
<TextField multiline rows={5} value={jwt} onChange={onJwtChange} />
</MainItem>
<MainItem title="Header">
<CodeEditor name="header" mode="json" value={header} />
</MainItem>
<MainItem title="Payload">
<CodeEditor name="payload" mode="json" value={payload} />
</MainItem>
</Main>
);
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 <Component {...{ jwt, header, payload, onJwtChange }} />;
};
export default Container;

View File

@@ -0,0 +1,3 @@
import Content from "./Content";
export { Content };

View File

@@ -82,8 +82,8 @@ const toolGroups = [
longTitle: "JWT Decoder", longTitle: "JWT Decoder",
description: "Decode a JWT header, payload and signature", description: "Decode a JWT header, payload and signature",
keywords: "jwt json web token decocder", keywords: "jwt json web token decocder",
href: pagesPath.$url(), href: pagesPath.encoders_decoders.jwt.$url(),
disabled: true, disabled: false,
}, },
], ],
}, },

View File

@@ -26,6 +26,12 @@ export const pagesPath = {
hash: url?.hash, hash: url?.hash,
}), }),
}, },
jwt: {
$url: (url?: { hash?: string }) => ({
pathname: "/encoders-decoders/jwt" as const,
hash: url?.hash,
}),
},
url: { url: {
$url: (url?: { hash?: string }) => ({ $url: (url?: { hash?: string }) => ({
pathname: "/encoders-decoders/url" as const, pathname: "/encoders-decoders/url" as const,

22
src/libs/jwt.ts Normal file
View File

@@ -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 };
};

View File

@@ -0,0 +1,7 @@
import type { NextPage } from "next";
import { Content } from "@/components/pages/encoders-decoders/jwt";
const Page: NextPage = Content;
export default Page;

View File

@@ -1677,6 +1677,11 @@ json5@^1.0.1:
array-includes "^3.1.3" array-includes "^3.1.3"
object.assign "^4.1.2" 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: language-subtag-registry@~0.3.2:
version "0.3.21" version "0.3.21"
resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.21.tgz#04ac218bea46f04cb039084602c6da9e788dd45a"