Use
This commit is contained in:
@@ -180,13 +180,13 @@ const CodeDisplay: React.FC<CodeDisplayProps> = ({ code, timeOffset }) => {
|
||||
|
||||
const regen = useCallback(() => {
|
||||
try {
|
||||
const [m, n] = generateOTPs(code);
|
||||
const [m, n] = generateOTPs(code, timeOffset);
|
||||
setOTP(m);
|
||||
setNextOTP(n);
|
||||
} catch (e) {
|
||||
setErrorMessage(e instanceof Error ? e.message : String(e));
|
||||
}
|
||||
}, [code]);
|
||||
}, [code, timeOffset]);
|
||||
|
||||
const copyCode = () =>
|
||||
void navigator.clipboard.writeText(otp).then(() => {
|
||||
@@ -327,7 +327,7 @@ const CodeValidityBar: React.FC<CodeValidityBarProps> = ({
|
||||
code.type == "hotp" ? undefined : setInterval(advance, 10);
|
||||
|
||||
return () => ticker && clearInterval(ticker);
|
||||
}, [code]);
|
||||
}, [code, timeOffset]);
|
||||
|
||||
const progressColor =
|
||||
progress > 0.4
|
||||
|
||||
@@ -256,12 +256,19 @@ const parseCodeDisplay = (url: URL): CodeDisplay | undefined => {
|
||||
*
|
||||
* @param code The parsed code data, including the secret and code type.
|
||||
*
|
||||
* @param timeOffset A millisecond delta that should be applied to Date.now when
|
||||
* deriving the OTP.
|
||||
*
|
||||
* @returns a pair of OTPs, the current one and the next one, using the given
|
||||
* {@link code}.
|
||||
*/
|
||||
export const generateOTPs = (code: Code): [otp: string, nextOTP: string] => {
|
||||
export const generateOTPs = (
|
||||
code: Code,
|
||||
timeOffset: number,
|
||||
): [otp: string, nextOTP: string] => {
|
||||
let otp: string;
|
||||
let nextOTP: string;
|
||||
const timestamp = Date.now() + timeOffset;
|
||||
switch (code.type) {
|
||||
case "totp": {
|
||||
const totp = new TOTP({
|
||||
@@ -270,9 +277,9 @@ export const generateOTPs = (code: Code): [otp: string, nextOTP: string] => {
|
||||
period: code.period,
|
||||
digits: code.length,
|
||||
});
|
||||
otp = totp.generate();
|
||||
otp = totp.generate({ timestamp });
|
||||
nextOTP = totp.generate({
|
||||
timestamp: Date.now() + code.period * 1000,
|
||||
timestamp: timestamp + code.period * 1000,
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -291,9 +298,9 @@ export const generateOTPs = (code: Code): [otp: string, nextOTP: string] => {
|
||||
|
||||
case "steam": {
|
||||
const steam = new Steam({ secret: code.secret });
|
||||
otp = steam.generate();
|
||||
otp = steam.generate({ timestamp });
|
||||
nextOTP = steam.generate({
|
||||
timestamp: Date.now() + code.period * 1000,
|
||||
timestamp: timestamp + code.period * 1000,
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -14,8 +14,8 @@ import { z } from "zod";
|
||||
export interface AuthCodesAndTimeOffset {
|
||||
codes: Code[];
|
||||
/**
|
||||
* An optional and approximate correction which should be applied to the
|
||||
* current client's time when deriving TOTPs.
|
||||
* An optional and approximate correction (milliseconds) which should be
|
||||
* applied to the current client's time when deriving TOTPs.
|
||||
*/
|
||||
timeOffset?: number;
|
||||
}
|
||||
@@ -112,7 +112,14 @@ const RemoteAuthenticatorEntityChange = z.object({
|
||||
});
|
||||
|
||||
const AuthenticatorEntityDiffResponse = z.object({
|
||||
/**
|
||||
* Changes to entities.
|
||||
*/
|
||||
diff: z.array(RemoteAuthenticatorEntityChange),
|
||||
/**
|
||||
* An optional epoch microseconds indicating the remote time when it
|
||||
* generated the response.
|
||||
*/
|
||||
timestamp: z.number().nullish().transform(nullToUndefined),
|
||||
});
|
||||
|
||||
@@ -122,8 +129,8 @@ export interface AuthenticatorEntityDiffResult {
|
||||
*/
|
||||
entities: AuthenticatorEntity[];
|
||||
/**
|
||||
* An optional and approximate offset by which the time on the current client
|
||||
* is out of sync.
|
||||
* An optional and approximate offset (in milliseconds) by which the time on
|
||||
* the current client is out of sync.
|
||||
*
|
||||
* This offset is computed by calculated by comparing the timestamp when the
|
||||
* remote generated the response to the time we received it. As such
|
||||
@@ -174,10 +181,17 @@ export const authenticatorEntityDiff = async (
|
||||
headers: await authenticatedRequestHeaders(),
|
||||
});
|
||||
ensureOk(res);
|
||||
|
||||
const { diff, timestamp } = AuthenticatorEntityDiffResponse.parse(
|
||||
await res.json(),
|
||||
);
|
||||
if (timestamp) timeOffset = Date.now() - timestamp;
|
||||
|
||||
if (timestamp) {
|
||||
// - timestamp is in epoch microseconds.
|
||||
// - Date.now and timeOffset are in epoch milliseconds.
|
||||
timeOffset = Date.now() - Math.floor(timestamp / 1e3);
|
||||
}
|
||||
|
||||
if (diff.length == 0) break;
|
||||
|
||||
for (const change of diff) {
|
||||
|
||||
Reference in New Issue
Block a user