This repository has been archived by the owner on May 14, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Display ente authenticator codes (#983)
- Loading branch information
Showing
13 changed files
with
680 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 & 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> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.