diff --git a/package-lock.json b/package-lock.json index 3c85777..d24cfad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@otplib/core": "^12.0.1", "@otplib/plugin-crypto-js": "^12.0.1", "@popperjs/core": "^2.11.8", + "base32-decode": "^1.0.0", "bootstrap": "^5.3.3", "classnames": "^2.5.1", "react": "^18.2.0", @@ -1930,6 +1931,12 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base32-decode": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/base32-decode/-/base32-decode-1.0.0.tgz", + "integrity": "sha512-KNWUX/R7wKenwE/G/qFMzGScOgVntOmbE27vvc6GrniDGYb6a5+qWcuoXl8WIOQL7q0TpK7nZDm1Y04Yi3Yn5g==", + "license": "MIT" + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", diff --git a/package.json b/package.json index a908006..fea854f 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@otplib/core": "^12.0.1", "@otplib/plugin-crypto-js": "^12.0.1", "@popperjs/core": "^2.11.8", + "base32-decode": "^1.0.0", "bootstrap": "^5.3.3", "classnames": "^2.5.1", "react": "^18.2.0", diff --git a/src/App.tsx b/src/App.tsx index 0af46ad..9512da5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,10 +1,12 @@ import React, {useState} from 'react'; +import base32Decode from 'base32-decode'; import {NumberInput, TextInput} from './Input'; -import OTPOutput, {HashAlgorithm} from './OTPOutput'; +import OTPOutput from './OTPOutput'; import Select from './Select'; import Collapsible from './Collapsible'; import ActionLink from './ActionLink'; import {type State, defaults, serializeState, deserializeState} from './state'; +import {HashAlgorithm} from './algorithms.tsx'; function parseState() { if (window.location.hash.startsWith('#!')) { @@ -20,9 +22,17 @@ function App() { const {secret, step, digits, algorithm} = state; const [offset, setOffset] = React.useState(0); + const [validSecret, decoded] = React.useMemo(() => { + try { + return [true, base32Decode(secret.toUpperCase(), 'RFC4648')]; + } catch (e) { + return [false, undefined]; + } + }, [secret]); + const validStep = step > 0; const validDigits = digits > 0 && digits <= 10; - const valid = validStep && validDigits && !!secret; + const valid = validSecret && validStep && validDigits && !!secret; React.useEffect(() => { if (!validStep) return; @@ -74,7 +84,6 @@ function App() { React.useEffect(() => { const value = serializeState(state); - console.log(value); history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`)); }, [state]); @@ -91,7 +100,8 @@ function App() { return (
- + {advanced ? Hide advanced options : Show advanced options} @@ -108,7 +118,7 @@ function App() {
- {valid && } + {valid && }
); } diff --git a/src/OTPOutput.tsx b/src/OTPOutput.tsx index 1739433..9206a2a 100644 --- a/src/OTPOutput.tsx +++ b/src/OTPOutput.tsx @@ -1,16 +1,9 @@ import React from 'react'; -import {HashAlgorithms, HOTP, HOTPOptions} from '@otplib/core'; +import {HOTP, HOTPOptions} from '@otplib/core'; import {createDigest} from '@otplib/plugin-crypto-js'; import classNames from 'classnames'; import CopyButton from './CopyButton.tsx'; - -export const ALGORITHMS = { - sha1: HashAlgorithms.SHA1, - sha256: HashAlgorithms.SHA256, - sha512: HashAlgorithms.SHA512, -}; - -export type HashAlgorithm = keyof typeof ALGORITHMS; +import {ALGORITHMS, HashAlgorithm} from './algorithms.tsx'; function OTPCode({code, delta}: { code: string; delta: number }) { return