Decode secret as base32

This commit is contained in:
Quantum 2025-02-10 13:09:14 -05:00
parent 6181e0c0bc
commit 5243c3aa8b
6 changed files with 36 additions and 15 deletions

7
package-lock.json generated
View file

@ -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",

View file

@ -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",

View file

@ -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 (
<div className="totp-app">
<div className="totp-settings">
<TextInput label="Secret key" value={secret} onChange={setSecret}/>
<TextInput label="Secret key" value={secret} onChange={setSecret}
error={!validSecret && 'Secret must be a valid base32-encoded string'}/>
{advanced ?
<ActionLink onClick={hideAdvanced}>Hide advanced options</ActionLink> :
<ActionLink onClick={showAdvanced}>Show advanced options</ActionLink>}
@ -108,7 +118,7 @@ function App() {
<button type="button" className="btn btn-secondary" onClick={onReset}>Reset</button>
</Collapsible>
</div>
{valid && <OTPOutput secret={secret} offset={offset} algorithm={algorithm} digits={digits}/>}
{valid && <OTPOutput secret={decoded} offset={offset} algorithm={algorithm} digits={digits}/>}
</div>
);
}

View file

@ -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 <div className={classNames('totp-code', {

9
src/algorithms.tsx Normal file
View file

@ -0,0 +1,9 @@
import {HashAlgorithms} from '@otplib/core';
export const ALGORITHMS = {
sha1: HashAlgorithms.SHA1,
sha256: HashAlgorithms.SHA256,
sha512: HashAlgorithms.SHA512,
};
export type HashAlgorithm = keyof typeof ALGORITHMS;

View file

@ -1,4 +1,5 @@
import {ALGORITHMS, HashAlgorithm} from './OTPOutput';
import {ALGORITHMS, HashAlgorithm} from './algorithms.tsx';
export type State = {
secret: string;