diff --git a/web/apps/auth/src/pages/auth.tsx b/web/apps/auth/src/pages/auth.tsx
index 4006cc9e1..526489779 100644
--- a/web/apps/auth/src/pages/auth.tsx
+++ b/web/apps/auth/src/pages/auth.tsx
@@ -46,14 +46,11 @@ const AuthenticatorCodesPage = () => {
appContext.showNavBar(false);
}, []);
+ const lcSearch = searchTerm.toLowerCase();
const filteredCodes = codes.filter(
- (secret) =>
- (secret.issuer ?? "")
- .toLowerCase()
- .includes(searchTerm.toLowerCase()) ||
- (secret.account ?? "")
- .toLowerCase()
- .includes(searchTerm.toLowerCase()),
+ (code) =>
+ code.issuer?.toLowerCase().includes(lcSearch) ||
+ code.account?.toLowerCase().includes(lcSearch),
);
if (!hasFetched) {
@@ -270,7 +267,7 @@ const OTPDisplay: React.FC
= ({ code, otp, nextOTP }) => { color: "grey", }} > - {code.account} + {code.account ?? ""}
{
- const santizedRawData = uriString
- .replaceAll("+", "%2B")
- .replaceAll(":", "%3A")
- .replaceAll("\r", "")
- // trim quotes
- .replace(/^"|"$/g, "");
-
- const uriParams = {};
- const searchParamsString =
- decodeURIComponent(santizedRawData).split("?")[1];
- searchParamsString.split("&").forEach((pair) => {
- const [key, value] = pair.split("=");
- uriParams[key] = value;
- });
-
- const uri = URI.parse(santizedRawData);
- let uriPath = decodeURIComponent(uri.path);
- if (uriPath.startsWith("/otpauth://") || uriPath.startsWith("otpauth://")) {
- uriPath = uriPath.split("otpauth://")[1];
- } else if (uriPath.startsWith("otpauth%3A//")) {
- uriPath = uriPath.split("otpauth%3A//")[1];
+ try {
+ return _codeFromURIString(id, uriString);
+ } catch (e) {
+ // We might have legacy encodings of account names that contain a "#",
+ // which causes the rest of the URL to be treated as a fragment, and
+ // ignored. See if this was potentially such a case, otherwise rethrow.
+ if (uriString.includes("#"))
+ return _codeFromURIString(id, uriString.replaceAll("#", "%23"));
+ throw e;
}
+};
+
+const _codeFromURIString = (id: string, uriString: string): Code => {
+ const url = new URL(uriString);
+
+ // A URL like
+ //
+ // new URL("otpauth://hotp/Test?secret=AAABBBCCCDDDEEEFFF&issuer=Test&counter=0")
+ //
+ // is parsed differently by the browser and Node depending on the scheme.
+ // When the scheme is http(s), then both of them consider "hotp" as the
+ // `host`. However, when the scheme is "otpauth", as is our case, the
+ // browser considers the entire thing as part of the pathname. so we get.
+ //
+ // host: ""
+ // pathname: "//hotp/Test"
+ //
+ // Since this code run on browsers only, we parse as per that behaviour.
+
+ const [type, path] = parsePathname(url);
return {
id,
- type: _getType(uriPath),
- account: _getAccount(uriPath),
- issuer: _getIssuer(uriPath, uriParams),
- digits: parseDigits(uriParams),
- period: parsePeriod(uriParams),
- secret: parseSecret(uriParams),
- algorithm: parseAlgorithm(uriParams),
+ type,
+ account: parseAccount(path),
+ issuer: parseIssuer(url, path),
+ digits: parseDigits(url),
+ period: parsePeriod(url),
+ secret: parseSecret(url),
+ algorithm: parseAlgorithm(url),
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 parsePathname = (url: URL): [type: Code["type"], path: string] => {
+ const p = url.pathname.toLowerCase();
+ if (p.startsWith("//totp")) return ["totp", url.pathname.slice(6)];
+ if (p.startsWith("//hotp")) return ["hotp", url.pathname.slice(6)];
+ throw new Error(`Unsupported code or unparseable path "${url.pathname}"`);
};
-const _getAccount = (uriPath: string): string => {
- try {
- const path = decodeURIComponent(uriPath);
- if (path.includes(":")) {
- return path.split(":")[1];
- } else if (path.includes("/")) {
- return path.split("/")[1];
- }
- } catch (e) {
- return "";
- }
+const parseAccount = (path: string): string | undefined => {
+ // "/ACME:user@example.org" => "user@example.org"
+ let p = decodeURIComponent(path);
+ if (p.startsWith("/")) p = p.slice(1);
+ if (p.includes(":")) p = p.split(":").slice(1).join(":");
+ return p;
};
-const _getIssuer = (uriPath: string, uriParams: { get?: any }): string => {
- try {
- if (uriParams["issuer"] !== undefined) {
- let issuer = uriParams["issuer"];
- // This is to handle bug in the ente auth app
- if (issuer.endsWith("period")) {
- issuer = issuer.substring(0, issuer.length - 6);
- }
- return issuer;
+const parseIssuer = (url: URL, path: string): string => {
+ // If there is a "issuer" search param, use that.
+ let issuer = url.searchParams.get("issuer");
+ if (issuer) {
+ // This is to handle bug in old versions of Ente Auth app.
+ if (issuer.endsWith("period")) {
+ issuer = issuer.substring(0, issuer.length - 6);
}
- let path = decodeURIComponent(uriPath);
- if (path.startsWith("totp/") || path.startsWith("hotp/")) {
- path = path.substring(5);
- }
- if (path.includes(":")) {
- return path.split(":")[0];
- } else if (path.includes("-")) {
- return path.split("-")[0];
- }
- return path;
- } catch (e) {
- return "";
+ return issuer;
}
+
+ // Otherwise use the `prefix:` from the account as the issuer.
+ // "/ACME:user@example.org" => "ACME"
+ let p = decodeURIComponent(path);
+ if (p.startsWith("/")) p = p.slice(1);
+
+ if (p.includes(":")) p = p.split(":")[0];
+ else if (p.includes("-")) p = p.split("-")[0];
+
+ return p;
};
-const parseDigits = (uriParams): number =>
- parseInt(uriParams["digits"] ?? "", 10) || 6;
+const parseDigits = (url: URL): number =>
+ parseInt(url.searchParams.get("digits") ?? "", 10) || 6;
-const parsePeriod = (uriParams): number =>
- parseInt(uriParams["period"] ?? "", 10) || 30;
+const parsePeriod = (url: URL): number =>
+ parseInt(url.searchParams.get("period") ?? "", 10) || 30;
-const parseAlgorithm = (uriParams): Code["algorithm"] => {
- switch (uriParams["algorithm"]?.toLowerCase()) {
+const parseAlgorithm = (url: URL): Code["algorithm"] => {
+ switch (url.searchParams.get("algorithm")?.toLowerCase()) {
case "sha256":
return "sha256";
case "sha512":
@@ -148,8 +147,8 @@ const parseAlgorithm = (uriParams): Code["algorithm"] => {
}
};
-const parseSecret = (uriParams): string =>
- uriParams["secret"].replaceAll(" ", "").toUpperCase();
+const parseSecret = (url: URL): string =>
+ ensure(url.searchParams.get("secret")).replaceAll(" ", "").toUpperCase();
/**
* Generate a pair of OTPs (one time passwords) from the given {@link code}.
diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts
index 07b15d7d7..11d57aa23 100644
--- a/web/apps/auth/src/services/remote.ts
+++ b/web/apps/auth/src/services/remote.ts
@@ -35,7 +35,7 @@ export const getAuthCodes = async (): Promise => {
);
return codeFromURIString(entity.id, decryptedCode);
} catch (e) {
- log.error(`failed to parse codeId = ${entity.id}`);
+ log.error(`Failed to parse codeID ${entity.id}`, e);
return null;
}
}),
diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json
index 1541878c5..0ec924b29 100644
--- a/web/apps/photos/package.json
+++ b/web/apps/photos/package.json
@@ -43,7 +43,6 @@
"similarity-transformation": "^0.0.1",
"transformation-matrix": "^2.16",
"uuid": "^9.0.1",
- "vscode-uri": "^3.0.7",
"xml-js": "^1.6.11",
"zxcvbn": "^4.4.2"
},
diff --git a/web/yarn.lock b/web/yarn.lock
index 894a44dd0..aaa0d517a 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -4804,11 +4804,6 @@ void-elements@3.1.0:
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
integrity sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==
-vscode-uri@^3.0.7:
- version "3.0.8"
- resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.0.8.tgz#1770938d3e72588659a172d0fd4642780083ff9f"
- integrity sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==
-
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"