mirror of
https://github.com/quantum5/totp.git
synced 2025-04-24 05:31:59 -04:00
Decode secret as base32
This commit is contained in:
parent
6181e0c0bc
commit
5243c3aa8b
7
package-lock.json
generated
7
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
||||||
"@otplib/core": "^12.0.1",
|
"@otplib/core": "^12.0.1",
|
||||||
"@otplib/plugin-crypto-js": "^12.0.1",
|
"@otplib/plugin-crypto-js": "^12.0.1",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"base32-decode": "^1.0.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
@ -1930,6 +1931,12 @@
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/base64-js": {
|
||||||
"version": "1.5.1",
|
"version": "1.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@otplib/core": "^12.0.1",
|
"@otplib/core": "^12.0.1",
|
||||||
"@otplib/plugin-crypto-js": "^12.0.1",
|
"@otplib/plugin-crypto-js": "^12.0.1",
|
||||||
"@popperjs/core": "^2.11.8",
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"base32-decode": "^1.0.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
|
20
src/App.tsx
20
src/App.tsx
|
@ -1,10 +1,12 @@
|
||||||
import React, {useState} from 'react';
|
import React, {useState} from 'react';
|
||||||
|
import base32Decode from 'base32-decode';
|
||||||
import {NumberInput, TextInput} from './Input';
|
import {NumberInput, TextInput} from './Input';
|
||||||
import OTPOutput, {HashAlgorithm} from './OTPOutput';
|
import OTPOutput from './OTPOutput';
|
||||||
import Select from './Select';
|
import Select from './Select';
|
||||||
import Collapsible from './Collapsible';
|
import Collapsible from './Collapsible';
|
||||||
import ActionLink from './ActionLink';
|
import ActionLink from './ActionLink';
|
||||||
import {type State, defaults, serializeState, deserializeState} from './state';
|
import {type State, defaults, serializeState, deserializeState} from './state';
|
||||||
|
import {HashAlgorithm} from './algorithms.tsx';
|
||||||
|
|
||||||
function parseState() {
|
function parseState() {
|
||||||
if (window.location.hash.startsWith('#!')) {
|
if (window.location.hash.startsWith('#!')) {
|
||||||
|
@ -20,9 +22,17 @@ function App() {
|
||||||
const {secret, step, digits, algorithm} = state;
|
const {secret, step, digits, algorithm} = state;
|
||||||
const [offset, setOffset] = React.useState(0);
|
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 validStep = step > 0;
|
||||||
const validDigits = digits > 0 && digits <= 10;
|
const validDigits = digits > 0 && digits <= 10;
|
||||||
const valid = validStep && validDigits && !!secret;
|
const valid = validSecret && validStep && validDigits && !!secret;
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!validStep) return;
|
if (!validStep) return;
|
||||||
|
@ -74,7 +84,6 @@ function App() {
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const value = serializeState(state);
|
const value = serializeState(state);
|
||||||
console.log(value);
|
|
||||||
history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`));
|
history.replaceState(null, '', window.location.pathname + window.location.search + (value && `#!${value}`));
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
|
@ -91,7 +100,8 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<div className="totp-app">
|
<div className="totp-app">
|
||||||
<div className="totp-settings">
|
<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 ?
|
{advanced ?
|
||||||
<ActionLink onClick={hideAdvanced}>Hide advanced options</ActionLink> :
|
<ActionLink onClick={hideAdvanced}>Hide advanced options</ActionLink> :
|
||||||
<ActionLink onClick={showAdvanced}>Show 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>
|
<button type="button" className="btn btn-secondary" onClick={onReset}>Reset</button>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
{valid && <OTPOutput secret={secret} offset={offset} algorithm={algorithm} digits={digits}/>}
|
{valid && <OTPOutput secret={decoded} offset={offset} algorithm={algorithm} digits={digits}/>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import React from 'react';
|
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 {createDigest} from '@otplib/plugin-crypto-js';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import CopyButton from './CopyButton.tsx';
|
import CopyButton from './CopyButton.tsx';
|
||||||
|
import {ALGORITHMS, HashAlgorithm} from './algorithms.tsx';
|
||||||
export const ALGORITHMS = {
|
|
||||||
sha1: HashAlgorithms.SHA1,
|
|
||||||
sha256: HashAlgorithms.SHA256,
|
|
||||||
sha512: HashAlgorithms.SHA512,
|
|
||||||
};
|
|
||||||
|
|
||||||
export type HashAlgorithm = keyof typeof ALGORITHMS;
|
|
||||||
|
|
||||||
function OTPCode({code, delta}: { code: string; delta: number }) {
|
function OTPCode({code, delta}: { code: string; delta: number }) {
|
||||||
return <div className={classNames('totp-code', {
|
return <div className={classNames('totp-code', {
|
||||||
|
|
9
src/algorithms.tsx
Normal file
9
src/algorithms.tsx
Normal 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;
|
|
@ -1,4 +1,5 @@
|
||||||
import {ALGORITHMS, HashAlgorithm} from './OTPOutput';
|
|
||||||
|
import {ALGORITHMS, HashAlgorithm} from './algorithms.tsx';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
secret: string;
|
secret: string;
|
||||||
|
|
Loading…
Reference in a new issue