= ({
+ code,
+ errorMessage,
+}) => {
const [showRawData, setShowRawData] = useState(false);
return (
-
{codeInfo.title}
-
{codeErr}
+
{code.issuer}
+
{errorMessage}
{showRawData ? (
setShowRawData(false)}>
- {codeInfo.uriString ?? "(no raw data)"}
+ {code.uriString}
) : (
setShowRawData(true)}>Show rawData
@@ -425,9 +398,9 @@ function BadCodeInfo({ codeInfo, codeErr }) {
);
-}
+};
-const AuthFooter: React.FC = () => {
+const Footer: React.FC = () => {
return (
{
- let santizedRawData = uriString
- .replace(/\+/g, "%2B")
- .replace(/:/g, "%3A")
- .replaceAll("\r", "");
- if (santizedRawData.startsWith('"')) {
- santizedRawData = santizedRawData.substring(1);
- }
- if (santizedRawData.endsWith('"')) {
- santizedRawData = santizedRawData.substring(
- 0,
- santizedRawData.length - 1,
- );
- }
+ const santizedRawData = uriString
+ .replaceAll("+", "%2B")
+ .replaceAll(":", "%3A")
+ .replaceAll("\r", "")
+ // trim quotes
+ .replace(/^"|"$/g, "");
const uriParams = {};
const searchParamsString =
@@ -78,12 +77,22 @@ export const codeFromURIString = (id: string, uriString: string): Code => {
issuer: _getIssuer(uriPath, uriParams),
digits: parseDigits(uriParams),
period: parsePeriod(uriParams),
- secret: getSanitizedSecret(uriParams),
+ secret: parseSecret(uriParams),
algorithm: parseAlgorithm(uriParams),
uriString,
};
};
+const _getType = (uriPath: string): Code["type"] => {
+ const oauthType = uriPath.split("/")[0].substring(0);
+ if (oauthType.toLowerCase() === "totp") {
+ return "totp";
+ } else if (oauthType.toLowerCase() === "hotp") {
+ return "hotp";
+ }
+ throw new Error(`Unsupported format with host ${oauthType}`);
+};
+
const _getAccount = (uriPath: string): string => {
try {
const path = decodeURIComponent(uriPath);
@@ -139,16 +148,45 @@ const parseAlgorithm = (uriParams): Code["algorithm"] => {
}
};
-const _getType = (uriPath: string): Code["type"] => {
- const oauthType = uriPath.split("/")[0].substring(0);
- if (oauthType.toLowerCase() === "totp") {
- return "totp";
- } else if (oauthType.toLowerCase() === "hotp") {
- return "hotp";
- }
- throw new Error(`Unsupported format with host ${oauthType}`);
-};
+const parseSecret = (uriParams): string =>
+ uriParams["secret"].replaceAll(" ", "").toUpperCase();
-const getSanitizedSecret = (uriParams): string => {
- return uriParams["secret"].replace(/ /g, "").toUpperCase();
+/**
+ * Generate a pair of OTPs (one time passwords) from the given {@link code}.
+ *
+ * @param code The parsed code data, including the secret and code type.
+ *
+ * @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] => {
+ let otp: string;
+ let nextOTP: string;
+ switch (code.type) {
+ case "totp": {
+ const totp = new TOTP({
+ secret: code.secret,
+ algorithm: code.algorithm,
+ period: code.period,
+ digits: code.digits,
+ });
+ otp = totp.generate();
+ nextOTP = totp.generate({
+ timestamp: new Date().getTime() + code.period * 1000,
+ });
+ break;
+ }
+
+ case "hotp": {
+ const hotp = new HOTP({
+ secret: code.secret,
+ counter: 0,
+ algorithm: code.algorithm,
+ });
+ otp = hotp.generate();
+ nextOTP = hotp.generate({ counter: 1 });
+ break;
+ }
+ }
+ return [otp, nextOTP];
};
diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json
index 8d515c07cd..1541878c51 100644
--- a/web/apps/photos/package.json
+++ b/web/apps/photos/package.json
@@ -28,7 +28,6 @@
"localforage": "^1.9.0",
"memoize-one": "^6.0.0",
"ml-matrix": "^6.11",
- "otpauth": "^9.0.2",
"p-debounce": "^4.0.0",
"p-queue": "^7.1.0",
"photoswipe": "file:./thirdparty/photoswipe",
diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md
index 2ff8e40172..83a2b27990 100644
--- a/web/docs/dependencies.md
+++ b/web/docs/dependencies.md
@@ -193,3 +193,8 @@ some cases.
- [hdbscan](https://github.com/shaileshpandit/hdbscan-js) is used for face
clustering.
+
+## Auth app specific
+
+- [otpauth](https://github.com/hectorm/otpauth) is used for the generation of
+ the actual OTP from the user's TOTP/HOTP secret.
diff --git a/web/yarn.lock b/web/yarn.lock
index bb12308316..894a44dd02 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -3700,10 +3700,10 @@ optionator@^0.9.3:
prelude-ls "^1.2.1"
type-check "^0.4.0"
-otpauth@^9.0.2:
- version "9.2.2"
- resolved "https://registry.yarnpkg.com/otpauth/-/otpauth-9.2.2.tgz#64bda9ea501a5d86e69a964a45062f1f17f740f4"
- integrity sha512-2VcnYRUmq1dNckIfySNYP32ITWp1bvTeAEW0BSCR6G3GBf3a5zb9E+ubY62t3Dma9RjoHlvd7QpmzHfJZRkiNg==
+otpauth@^9:
+ version "9.2.4"
+ resolved "https://registry.yarnpkg.com/otpauth/-/otpauth-9.2.4.tgz#3b7941a689f63c31db43ab2494d3c2d90bc1f150"
+ integrity sha512-t0Nioq2Up2ZaT5AbpXZLTjrsNtLc/g/rVSaEThmKLErAuT9mrnAKJryiPOKc3rCH+3ycWBgKpRHYn+DHqfaPiQ==
dependencies:
jssha "~3.3.1"