浏览代码

Merge branch 'main' into migrate_files_db_to_sqlite_async

ashilkn 1 年之前
父节点
当前提交
ed830dc387
共有 2 个文件被更改,包括 36 次插入15 次删除
  1. 10 9
      web/apps/auth/src/pages/auth.tsx
  2. 26 6
      web/apps/auth/src/services/code.ts

+ 10 - 9
web/apps/auth/src/pages/auth.tsx

@@ -233,7 +233,7 @@ const OTPDisplay: React.FC<OTPDisplayProps> = ({ code, otp, nextOTP }) => {
                 overflow: "hidden",
                 overflow: "hidden",
             }}
             }}
         >
         >
-            <TimerProgress period={code.period} />
+            <CodeValidityBar code={code} />
             <div
             <div
                 style={{
                 style={{
                     padding: "12px 20px 0px 20px",
                     padding: "12px 20px 0px 20px",
@@ -329,24 +329,25 @@ const OTPDisplay: React.FC<OTPDisplayProps> = ({ code, otp, nextOTP }) => {
     );
     );
 };
 };
 
 
-interface TimerProgressProps {
-    period: number;
+interface CodeValidityBarProps {
+    code: Code;
 }
 }
 
 
-const TimerProgress: React.FC<TimerProgressProps> = ({ period }) => {
-    const [progress, setProgress] = useState(0);
-    const us = period * 1e6;
+const CodeValidityBar: React.FC<CodeValidityBarProps> = ({ code }) => {
+    const [progress, setProgress] = useState(code.type == "hotp" ? 1 : 0);
 
 
     useEffect(() => {
     useEffect(() => {
         const advance = () => {
         const advance = () => {
+            const us = code.period * 1e6;
             const timeRemaining = us - ((Date.now() * 1000) % us);
             const timeRemaining = us - ((Date.now() * 1000) % us);
             setProgress(timeRemaining / us);
             setProgress(timeRemaining / us);
         };
         };
 
 
-        const ticker = setInterval(advance, 10);
+        const ticker =
+            code.type == "hotp" ? undefined : setInterval(advance, 10);
 
 
-        return () => clearInterval(ticker);
-    }, []);
+        return () => ticker && clearInterval(ticker);
+    }, [code]);
 
 
     const color = progress > 0.4 ? "green" : "orange";
     const color = progress > 0.4 ? "green" : "orange";
 
 

+ 26 - 6
web/apps/auth/src/services/code.ts

@@ -29,6 +29,15 @@ export interface Code {
      * code remains valid.
      * code remains valid.
      */
      */
     period: number;
     period: number;
+    /** The (HMAC) algorithm used by the OTP generator. */
+    algorithm: "sha1" | "sha256" | "sha512";
+    /**
+     * HOTP counter.
+     *
+     * Only valid for HOTP codes. It might be even missing for HOTP codes, in
+     * which case we should start from 0.
+     */
+    counter?: number;
     /**
     /**
      * The secret that is used to drive the OTP generator.
      * The secret that is used to drive the OTP generator.
      *
      *
@@ -36,8 +45,6 @@ export interface Code {
      * {@link type}-specific manner).
      * {@link type}-specific manner).
      */
      */
     secret: string;
     secret: string;
-    /** The (HMAC) algorithm used by the OTP generator. */
-    algorithm: "sha1" | "sha256" | "sha512";
     /** The original string from which this code was generated. */
     /** The original string from which this code was generated. */
     uriString: string;
     uriString: string;
 }
 }
@@ -53,6 +60,12 @@ export interface Code {
  * - (TOTP)
  * - (TOTP)
  *   otpauth://totp/ACME:user@example.org?algorithm=SHA1&digits=6&issuer=acme&period=30&secret=ALPHANUM
  *   otpauth://totp/ACME:user@example.org?algorithm=SHA1&digits=6&issuer=acme&period=30&secret=ALPHANUM
  *
  *
+ * - (HOTP)
+ *   otpauth://hotp/Test?secret=AAABBBCCCDDDEEEFFF&issuer=Test&counter=0
+ *
+ * - (Steam)
+ *   otpauth://steam/Steam:SteamAccount?algorithm=SHA1&digits=5&issuer=Steam&period=30&secret=AAABBBCCCDDDEEEFFF
+ *
  * See also `auth/test/models/code_test.dart`.
  * See also `auth/test/models/code_test.dart`.
  */
  */
 export const codeFromURIString = (id: string, uriString: string): Code => {
 export const codeFromURIString = (id: string, uriString: string): Code => {
@@ -94,8 +107,9 @@ const _codeFromURIString = (id: string, uriString: string): Code => {
         issuer: parseIssuer(url, path),
         issuer: parseIssuer(url, path),
         length: parseLength(url, type),
         length: parseLength(url, type),
         period: parsePeriod(url),
         period: parsePeriod(url),
-        secret: parseSecret(url),
         algorithm: parseAlgorithm(url),
         algorithm: parseAlgorithm(url),
+        counter: parseCounter(url),
+        secret: parseSecret(url),
         uriString,
         uriString,
     };
     };
 };
 };
@@ -164,6 +178,11 @@ const parseAlgorithm = (url: URL): Code["algorithm"] => {
     }
     }
 };
 };
 
 
+const parseCounter = (url: URL): number | undefined => {
+    const c = url.searchParams.get("counter");
+    return c ? parseInt(c, 10) : undefined;
+};
+
 const parseSecret = (url: URL): string =>
 const parseSecret = (url: URL): string =>
     ensure(url.searchParams.get("secret")).replaceAll(" ", "").toUpperCase();
     ensure(url.searchParams.get("secret")).replaceAll(" ", "").toUpperCase();
 
 
@@ -194,13 +213,14 @@ export const generateOTPs = (code: Code): [otp: string, nextOTP: string] => {
         }
         }
 
 
         case "hotp": {
         case "hotp": {
+            const counter = code.counter || 0;
             const hotp = new HOTP({
             const hotp = new HOTP({
                 secret: code.secret,
                 secret: code.secret,
-                counter: 0,
+                counter: counter,
                 algorithm: code.algorithm,
                 algorithm: code.algorithm,
             });
             });
-            otp = hotp.generate();
-            nextOTP = hotp.generate({ counter: 1 });
+            otp = hotp.generate({ counter });
+            nextOTP = hotp.generate({ counter: counter + 1 });
             break;
             break;
         }
         }