Skip to content

Commit

Permalink
Merge pull request #8 from davidreis97/master
Browse files Browse the repository at this point in the history
0.0.3
  • Loading branch information
davidreis97 authored Jun 14, 2023
2 parents fb2d465 + 2c52e82 commit c6cd0f5
Show file tree
Hide file tree
Showing 11 changed files with 134 additions and 38 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "jwt-editor",
"private": true,
"version": "0.0.2",
"version": "0.0.3",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jwt-editor"
version = "0.0.2"
version = "0.0.3"
description = "JWT Editor"
authors = ["[email protected]"]
license = "MIT"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
},
"package": {
"productName": "JWT Editor",
"version": "0.0.2"
"version": "0.0.3"
},
"tauri": {
"allowlist": {
Expand Down
77 changes: 49 additions & 28 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Flex, Grid, MantineProvider, Textarea } from '@mantine/core';
import { useState } from 'react';
import { Flex, Grid, JsonInput, MantineProvider, Textarea } from '@mantine/core';
import { useEffect, useState } from 'react';
import SignatureStatus from './helpers/signature-status';
import { signJWT, verifyJWT } from './logic/crypto';
import { base64url } from "jose"
import Signature from './components/signature';
import KeyPair from './helpers/key-pair';
import React from 'react';
import { canJsonParse, cleanUpJsonInput, formatJson } from './helpers/json';
import useActiveElement from './helpers/use-active-element';

const textDecoder = new TextDecoder()

Expand All @@ -19,6 +22,22 @@ export default function App() {
status: "info"
})

const focusedElement = useActiveElement();

const [cursor, setCursor] = useState(0);
const headerInputRef = React.useRef<HTMLTextAreaElement>(null);
const payloadInputRef = React.useRef<HTMLTextAreaElement>(null);

useEffect(() => {
const input = headerInputRef.current;
if (input && focusedElement == input) input.setSelectionRange(cursor, cursor)
}, [headerInputRef, cursor, decodedHeader])

useEffect(() => {
const input = payloadInputRef.current;
if (input && focusedElement == input) input.setSelectionRange(cursor, cursor)
}, [payloadInputRef, cursor, decodedPayload])

async function handleJwtChange(newJwt: string){
setJwt(newJwt)
if (!!!newJwt) {
Expand All @@ -30,7 +49,7 @@ export default function App() {

const [newEncodedHeader, newEncodedPayload, _signature] = newJwt.split(".")

const decodedHeader = textDecoder.decode(base64url.decode(newEncodedHeader ?? ""));
const decodedHeader = formatJson(textDecoder.decode(base64url.decode(newEncodedHeader ?? "")));
setDecodedHeader(decodedHeader)

let newAlg = algorithm;
Expand All @@ -41,28 +60,22 @@ export default function App() {
}
} catch(e) {}

setDecodedPayload(textDecoder.decode(base64url.decode(newEncodedPayload ?? "")))
setDecodedPayload(formatJson(textDecoder.decode(base64url.decode(newEncodedPayload ?? ""))))
await verifySignature(newJwt, keyPair.publicKey, newAlg)
}

async function handleHeaderChange(newHeader: string) {
setCursor(headerInputRef.current?.selectionStart ?? 0)
setDecodedHeader(newHeader)

if(keyPair.privateKey){
await generateNewJwt(newHeader, decodedPayload, keyPair.privateKey)
}else{
setJwt("")
}
await generateNewJwt(newHeader, decodedPayload, keyPair.privateKey, algorithm)
}

async function handlePayloadChange(newPayload: string) {
setCursor(payloadInputRef.current?.selectionStart ?? 0)
setDecodedPayload(newPayload)

if(keyPair.privateKey){
await generateNewJwt(decodedHeader, newPayload, keyPair.privateKey)
}else {
setJwt("")
}
await generateNewJwt(decodedHeader, newPayload, keyPair.privateKey, algorithm)
}

async function handlePublicKeyChange(newPublicKey: string) {
Expand All @@ -72,14 +85,13 @@ export default function App() {

async function handlePrivateKeyChange(newPrivateKey: string) {
setKeyPair((kp) => ({...kp, privateKey: newPrivateKey}))
await generateNewJwt(decodedHeader, decodedPayload, newPrivateKey)
await generateNewJwt(decodedHeader, decodedPayload, newPrivateKey, algorithm)
}

async function handleSecretChange(newSecret: string) {
setKeyPair((kp) => ({...kp, publicKey: newSecret, privateKey: newSecret}))

const newJwt = await generateNewJwt(decodedHeader, decodedPayload, newSecret)
await verifySignature(newJwt, newSecret, algorithm)
await generateNewJwt(decodedHeader, decodedPayload, newSecret, algorithm, true)
}

async function handleAlgorithmChange(newAlgorithm: string) {
Expand All @@ -88,12 +100,11 @@ export default function App() {
let newDecodedHeader = ""

try{
newDecodedHeader = JSON.stringify({...JSON.parse(decodedHeader), alg: newAlgorithm})
newDecodedHeader = formatJson(JSON.stringify({...JSON.parse(decodedHeader), alg: newAlgorithm}))
setDecodedHeader(newDecodedHeader)
}catch(e){}

const newJwt = await generateNewJwt(newDecodedHeader, decodedPayload, keyPair.privateKey)
await verifySignature(newJwt, keyPair.publicKey, newAlgorithm)
await generateNewJwt(newDecodedHeader, decodedPayload, keyPair.privateKey, newAlgorithm)
}

async function verifySignature(token: string, publicKey: string, alg: string) {
Expand All @@ -105,6 +116,14 @@ export default function App() {
return
}

if (!!!token) {
setSignatureStatus({
message:"Insert a token to check signature.",
status: "info"
})
return
}

let signatureIsValid = false;

try {
Expand All @@ -125,15 +144,17 @@ export default function App() {
}
}

async function generateNewJwt(decodedHeader: string, decodedPayload: string, privateKey: string){
async function generateNewJwt(decodedHeader: string, decodedPayload: string, privateKey: string, algorithm: string, isSymmetric: boolean = false){
let newJwt = ""

try{
newJwt = await signJWT(JSON.parse(decodedHeader), JSON.parse(decodedPayload), privateKey);
setJwt(newJwt)
} catch (e) {}
if (!!privateKey){
try{
newJwt = await signJWT(JSON.parse(decodedHeader), JSON.parse(decodedPayload), privateKey);
} catch (e) {}
}

return newJwt
await verifySignature(newJwt, isSymmetric ? privateKey : keyPair.publicKey, algorithm)
setJwt(newJwt)
}

return (
Expand All @@ -144,8 +165,8 @@ export default function App() {
</Grid.Col>
<Grid.Col h="100%" span={1}>
<Flex gap="xs" direction="column" h="100%">
<Textarea placeholder='Header' value={decodedHeader} onChange={(evt) => handleHeaderChange(evt.target.value)} sx={{flexGrow:1}} styles={{wrapper:{height: "100%"}, input:{height: "100%"}}} />
<Textarea placeholder='Payload' value={decodedPayload} onChange={(evt) => handlePayloadChange(evt.target.value)} sx={{flexGrow:1}} styles={{wrapper:{height: "100%"}, input:{height: "100%"}}} />
<JsonInput formatOnBlur ref={headerInputRef} error={!canJsonParse(decodedHeader)} placeholder='Header' value={decodedHeader} onChange={(value) => handleHeaderChange(cleanUpJsonInput(value))} sx={{flexGrow:1}} styles={{wrapper:{height: "100%"}, input:{height: "100%"}}} />
<JsonInput formatOnBlur ref={payloadInputRef} error={!canJsonParse(decodedPayload)} placeholder='Payload' value={decodedPayload} onChange={(value) => handlePayloadChange(cleanUpJsonInput(value))} sx={{flexGrow:1}} styles={{wrapper:{height: "100%"}, input:{height: "100%"}}} />
<Signature algorithm={algorithm} keyPair={keyPair} onAlgorithmChange={handleAlgorithmChange} onSecretChange={handleSecretChange} onPrivateKeyChange={handlePrivateKeyChange} onPublicKeyChange={handlePublicKeyChange} signatureStatus={signatureStatus}/>
</Flex>
</Grid.Col>
Expand Down
28 changes: 25 additions & 3 deletions src/components/asymmetric-signature-input.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,42 @@
import { Flex, Text, Textarea } from "@mantine/core"
import KeyPair from "../helpers/key-pair";
import { isValidAsymmetricKey } from "../logic/crypto";
import { useEffect, useState } from "react";

interface AsymmetricSignatureInputProps {
onPublicKeyChange: (pubKey: string) => void
onPrivateKeyChange: (privKey: string) => void
keyPair: KeyPair
keyPair: KeyPair,
algorithm: string,
}

function validateKeyField(key: string, setIsValid: (valid: boolean) => void, algorithm: string) {
useEffect(() => {
async function parseKey() {
setIsValid(await isValidAsymmetricKey(key, algorithm))
}

if (!!!key)
setIsValid(true);
else
parseKey()
}, [key])
}

export default function AsymmetricSignatureInput(props: AsymmetricSignatureInputProps) {
const [validPublicKey, setValidPublicKey] = useState<boolean>(true);
const [validPrivateKey, setValidPrivateKey] = useState<boolean>(true);

validateKeyField(props.keyPair.publicKey, setValidPublicKey, props.algorithm)
validateKeyField(props.keyPair.privateKey, setValidPrivateKey, props.algorithm)

return (
<>
<Flex ml="md" align="end">
<Textarea value={props.keyPair.publicKey} placeholder="Public Key" onChange={(evt) => props.onPublicKeyChange(evt.target.value)} />
<Textarea error={!validPublicKey} value={props.keyPair.publicKey} placeholder="Public Key" onChange={(evt) => props.onPublicKeyChange(evt.target.value)} />
<Text>,</Text>
</Flex>
<Textarea value={props.keyPair.privateKey} mt="0.3rem" w="fit-content" ml="md" placeholder="Private Key" onChange={(evt) => props.onPrivateKeyChange(evt.target.value)} />
<Textarea error={!validPrivateKey} value={props.keyPair.privateKey} mt="0.3rem" w="fit-content" ml="md" placeholder="Private Key" onChange={(evt) => props.onPrivateKeyChange(evt.target.value)} />
</>
);
}
2 changes: 1 addition & 1 deletion src/components/signature.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function Signature(props: SignatureProps) {
{
isSymmetric(props.algorithm) ?
<SymmetricSignatureInput secret={props.keyPair.publicKey} onSecretChange={props.onSecretChange}/> :
<AsymmetricSignatureInput keyPair={props.keyPair} onPrivateKeyChange={props.onPrivateKeyChange} onPublicKeyChange={props.onPublicKeyChange}/>
<AsymmetricSignatureInput algorithm={props.algorithm} keyPair={props.keyPair} onPrivateKeyChange={props.onPrivateKeyChange} onPublicKeyChange={props.onPublicKeyChange}/>
}
<Text>)</Text>
</Box>
Expand Down
26 changes: 26 additions & 0 deletions src/helpers/json.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export function canJsonParse(data: string) {
if (!!!data)
return true;

try {
JSON.parse(data)
return true;
} catch (e) {
return false;
}
}

export function formatJson(data: string) {
try {
const obj = JSON.parse(data);
return JSON.stringify(obj, null, 2)
} catch (e) {
return data
}
}

export function cleanUpJsonInput(data: string) {
return data
.replace(/[\u2018\u2019]/g, "'")
.replace(/[\u201C\u201D]/g, '"')
}
18 changes: 18 additions & 0 deletions src/helpers/use-active-element.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState, useEffect } from "react";

export default function useActiveElement() {
const [active, setActive] = useState(document.activeElement);

const handleFocusIn = () => {
setActive(document.activeElement);
}

useEffect(() => {
document.addEventListener('focusin', handleFocusIn)
return () => {
document.removeEventListener('focusin', handleFocusIn)
};
}, [])

return active;
}
9 changes: 9 additions & 0 deletions src/logic/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ async function parseKey(key: string, alg: string) : Promise<KeyLike | Uint8Array
throw "Could not parse key."
}

export async function isValidAsymmetricKey(key: string, algorithm: string) {
try {
await parseKey(key, algorithm)
return true;
} catch(e) {
return false;
}
}

export async function signJWT(header: JWTHeaderParameters, payload: JWTPayload, privateKey: string) {
return await new SignJWT(payload)
.setProtectedHeader(header)
Expand Down

0 comments on commit c6cd0f5

Please sign in to comment.