Skip to content
This repository has been archived by the owner on May 14, 2024. It is now read-only.

Commit

Permalink
Display ente authenticator codes (#983)
Browse files Browse the repository at this point in the history
  • Loading branch information
ua741 committed Apr 3, 2023
2 parents dc7024a + f9b12ac commit a5c7c31
Show file tree
Hide file tree
Showing 13 changed files with 680 additions and 1 deletion.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"ml-matrix": "^6.8.2",
"next": "^13.1.2",
"next-transpile-modules": "^10.0.0",
"otpauth": "^9.0.2",
"p-queue": "^7.1.0",
"photoswipe": "file:./thirdparty/photoswipe",
"piexifjs": "^1.0.6",
Expand All @@ -77,6 +78,7 @@
"similarity-transformation": "^0.0.1",
"styled-components": "^5.3.5",
"transformation-matrix": "^2.10.0",
"vscode-uri": "^3.0.7",
"workbox-precaching": "^6.1.5",
"workbox-recipes": "^6.1.5",
"workbox-routing": "^6.1.5",
Expand Down
1 change: 1 addition & 0 deletions public/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,7 @@
"UPLOAD_DIRS": "Folder",
"UPLOAD_GOOGLE_TAKEOUT": "Google takeout",
"DEDUPLICATE_FILES": "Deduplicate files",
"AUTHENTICATOR_SECTION": "Authenticator",
"NO_DUPLICATES_FOUND": "You've no duplicate files that can be cleared",
"CLUB_BY_CAPTURE_TIME": "Club by capture time",
"FILES": "Files",
Expand Down
25 changes: 25 additions & 0 deletions src/components/Authenicator/AuthFooder.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Button } from '@mui/material';

export const AuthFooter = () => {
return (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
}}>
<p>Download our mobile app to add &amp; manage your secrets.</p>
<a href="https://github.com/ente-io/auth#-download" download>
<Button
style={{
backgroundColor: 'green',
padding: '12px 18px',
color: 'white',
}}>
Download
</Button>
</a>
</div>
);
};
190 changes: 190 additions & 0 deletions src/components/Authenicator/OTPDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import React, { useState, useEffect } from 'react';
import { TOTP, HOTP } from 'otpauth';
import { Code } from 'types/authenticator/code';
import TimerProgress from './TimerProgress';

const TOTPDisplay = ({ issuer, account, code, nextCode }) => {
return (
<div
style={{
padding: '4px 16px',
display: 'flex',
alignItems: 'flex-start',
minWidth: '320px',
borderRadius: '4px',
backgroundColor: 'rgba(40, 40, 40, 0.6)',
justifyContent: 'space-between',
}}>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
minWidth: '200px',
}}>
<p
style={{
fontWeight: 'bold',
marginBottom: '0px',
fontSize: '14px',
textAlign: 'left',
}}>
{issuer}
</p>
<p
style={{
marginBottom: '8px',
textAlign: 'left',
fontSize: '12px',
}}>
{account}
</p>
<p
style={{
fontSize: '24px',
fontWeight: 'bold',
textAlign: 'left',
}}>
{code}
</p>
</div>
<div style={{ flex: 1 }} />
<div
style={{
display: 'flex',
flexDirection: 'column',
marginTop: '32px',
alignItems: 'flex-end',
minWidth: '120px',
textAlign: 'right',
}}>
<p
style={{
fontWeight: 'bold',
marginBottom: '0px',
fontSize: '10px',
marginTop: 'auto',
textAlign: 'right',
}}>
next
</p>
<p
style={{
fontSize: '14px',
fontWeight: 'bold',
marginBottom: '0px',
marginTop: 'auto',
textAlign: 'right',
}}>
{nextCode}
</p>
</div>
</div>
);
};

function BadCodeInfo({ codeInfo, codeErr }) {
const [showRawData, setShowRawData] = useState(false);

return (
<div className="code-info">
<div>{codeInfo.title}</div>
<div>{codeErr}</div>
<div>
{showRawData ? (
<div onClick={() => setShowRawData(false)}>
{codeInfo.rawData ?? 'no raw data'}
</div>
) : (
<div onClick={() => setShowRawData(true)}>Show rawData</div>
)}
</div>
</div>
);
}

interface OTPDisplayProps {
codeInfo: Code;
}

const OTPDisplay = (props: OTPDisplayProps) => {
const { codeInfo } = props;
const [code, setCode] = useState('');
const [nextCode, setNextCode] = useState('');
const [codeErr, setCodeErr] = useState('');

const generateCodes = () => {
try {
const currentTime = new Date().getTime();
if (codeInfo.type.toLowerCase() === 'totp') {
const totp = new TOTP({
secret: codeInfo.secret,
algorithm: codeInfo.algorithm ?? Code.defaultAlgo,
period: codeInfo.period ?? Code.defaultPeriod,
digits: codeInfo.digits ?? Code.defaultDigits,
});
setCode(totp.generate());
setNextCode(
totp.generate({
timestamp: currentTime + codeInfo.period * 1000,
})
);
} else if (codeInfo.type.toLowerCase() === 'hotp') {
const hotp = new HOTP({
secret: codeInfo.secret,
counter: 0,
algorithm: codeInfo.algorithm,
});
setCode(hotp.generate());
setNextCode(hotp.generate({ counter: 1 }));
}
} catch (err) {
setCodeErr(err.message);
}
};

useEffect(() => {
// this is to set the initial code and nextCode on component mount
generateCodes();
const codeType = codeInfo.type;
const codePeriodInMs = codeInfo.period * 1000;
const timeToNextCode =
codePeriodInMs - (new Date().getTime() % codePeriodInMs);
const intervalId = null;
// wait until we are at the start of the next code period,
// and then start the interval loop
setTimeout(() => {
// we need to call generateCodes() once before the interval loop
// to set the initial code and nextCode
generateCodes();
codeType.toLowerCase() === 'totp' ||
codeType.toLowerCase() === 'hotp'
? setInterval(() => {
generateCodes();
}, codePeriodInMs)
: null;
}, timeToNextCode);

return () => {
if (intervalId) clearInterval(intervalId);
};
}, [codeInfo]);

return (
<div style={{ padding: '8px' }}>
<TimerProgress period={codeInfo.period ?? Code.defaultPeriod} />
{codeErr === '' ? (
<TOTPDisplay
issuer={codeInfo.issuer}
account={codeInfo.account}
code={code}
nextCode={nextCode}
/>
) : (
<BadCodeInfo codeInfo={codeInfo} codeErr={codeErr} />
)}
</div>
);
};

export default OTPDisplay;
46 changes: 46 additions & 0 deletions src/components/Authenicator/TimerProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import React, { useState, useEffect } from 'react';

const TimerProgress = ({ period }) => {
const [progress, setProgress] = useState(0);
const [ticker, setTicker] = useState(null);
const microSecondsInPeriod = period * 1000000;

const startTicker = () => {
const ticker = setInterval(() => {
updateTimeRemaining();
}, 10);
setTicker(ticker);
};

const updateTimeRemaining = () => {
const timeRemaining =
microSecondsInPeriod -
((new Date().getTime() * 1000) % microSecondsInPeriod);
setProgress(timeRemaining / microSecondsInPeriod);
};

useEffect(() => {
startTicker();
return () => clearInterval(ticker);
}, []);

const color = progress > 0.4 ? 'green' : 'orange';

return (
<div
style={{
width: '100%',
height: '3px',
backgroundColor: 'transparent',
}}>
<div
style={{
width: `${progress * 100}%`,
height: '100%',
backgroundColor: color,
}}></div>
</div>
);
};

export default TimerProgress;
7 changes: 7 additions & 0 deletions src/components/Sidebar/UtilitySection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ export default function UtilitySection({ closeSidebar }) {

const redirectToDeduplicatePage = () => router.push(PAGES.DEDUPLICATE);

const redirectToAuthenticatorPage = () => router.push(PAGES.AUTH);

const somethingWentWrong = () =>
setDialogMessage({
title: t('ERROR'),
Expand Down Expand Up @@ -98,6 +100,11 @@ export default function UtilitySection({ closeSidebar }) {
<SidebarButton onClick={redirectToDeduplicatePage}>
{t('DEDUPLICATE_FILES')}
</SidebarButton>
{isInternalUser() && (
<SidebarButton onClick={redirectToAuthenticatorPage}>
{t('AUTHENTICATOR_SECTION')}
</SidebarButton>
)}
<SidebarButton onClick={openPreferencesOptions}>
{t('PREFERENCES')}
</SidebarButton>
Expand Down
2 changes: 2 additions & 0 deletions src/constants/pages/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@ export enum PAGES {
SHARED_ALBUMS = '/shared-albums',
// ML_DEBUG = '/ml-debug',
DEDUPLICATE = '/deduplicate',
// AUTH page is used to show (auth)enticator codes
AUTH = '/auth',
}
Loading

0 comments on commit a5c7c31

Please sign in to comment.