fix, chore, feat: refactor of settings app, fix of Google Auth migration parse, added rename button(testing), added sort feature, added timeout to screen turning off, formated codes with space between 6-digits codes

Co-authored-by: DemiarUA <demiar97@gmail.com>
https://github.com/Lisoveliy/totpfit/pull/1
This commit is contained in:
2025-08-07 16:53:54 +03:00
parent ea75f03fbe
commit 56d5edc37b
20 changed files with 825 additions and 601 deletions

View File

@@ -2,113 +2,168 @@ import { decodeProto, TYPES } from "../../lib/protobuf-decoder/protobufDecoder";
import { TOTP } from "../../lib/totp-quickjs";
import { base64decode, encode } from "../../lib/totp-quickjs/base32decoder";
const otpauthScheme = "otpauth:/";
const googleMigrationScheme = "otpauth-migration:/";
const otpauthScheme = "otpauth://";
const googleMigrationScheme = "otpauth-migration://";
export function getTOTPByLink(link) {
if (link.includes(otpauthScheme))
return getByOtpauthScheme(link)
if (link.includes(googleMigrationScheme))
return getByGoogleMigrationScheme(link)
function _parseSingleMigrationEntry(part) {
const totpProto = decodeProto(part.value);
const otpData = {};
return null;
}
const protoPartHandlers = {
1: (p) => {
otpData.secret = encode(p.value);
},
2: (p) => {
otpData.name = bytesToString(p.value);
},
3: (p) => {
otpData.issuer = bytesToString(p.value);
},
4: (p) => {
otpData.algorithm = p.value;
},
5: (p) => {
otpData.digits = p.value;
},
6: (p) => {
otpData.type = p.value;
},
};
function getHashType(algorithm) {
if (algorithm == "SHA1") return "SHA-1";
if (algorithm == "SHA256") return "SHA-256";
if (algorithm == "SHA512") return "SHA-512";
else return "SHA-1";
}
totpProto.parts.forEach((p) => {
const handler = protoPartHandlers[p.index];
if (handler) {
handler(p);
}
});
function getByOtpauthScheme(link) {
try {
let args = link.split("/", otpauthScheme.length);
let type = args[2]; //Returns 'hotp' or 'totp'
let issuer = args[3].split(":")[0]?.split("?")[0]; //Returns issuer
let client =
args[3].split(":")[1]?.split("?")[0] ??
args[3].split(":")[0]?.split("?")[0]; //Returns client
let secret = args[3].split("secret=")[1]?.split("&")[0]; //Returns secret
let period = args[3].split("period=")[1]?.split("&")[0]; //Returns period
let digits = args[3].split("digit=")[1]?.split("&")[0]; //Returns digits
let algorithm = args[3].split("algorithm=")[1]?.split("&")[0]; //Returns algorithm
let offset = args[3].split("offset=")[1]?.split("&")[0] ?? 0; //Returns offset
if (otpData.type !== "2") {
return null;
}
if (type.toLowerCase() != "totp")
throw new Error("Type is not valid, requires 'TOTP'");
const digitsMap = { 1: 6, 2: 8 };
const algoMap = { 1: "SHA-1", 2: "SHA-256", 3: "SHA-512" };
if (secret === undefined) throw new Error("Secret not defined");
const finalDigits = digitsMap[otpData.digits] || 6;
const finalAlgo = algoMap[otpData.algorithm] || "SHA-1";
const finalIssuer = otpData.issuer || otpData.name;
const finalName = otpData.name;
if (issuer == client) {
issuer = args[3].split("issuer=")[1]?.split("&")[0];
}
if (!otpData.secret || !finalName) {
throw new Error("Skipping record with missing secret or name.");
}
issuer = decodeURIComponent(issuer);
client = decodeURIComponent(client);
return new TOTP(
secret,
issuer,
client,
Number(digits),
Number(period),
Number(offset),
getHashType(algorithm)
);
} catch (err) {
console.log(err)
return null;
}
return new TOTP(
otpData.secret,
finalIssuer,
finalName,
finalDigits,
30,
0,
finalAlgo,
);
}
function getByGoogleMigrationScheme(link) {
try {
const data = link.split("data=")[1];
if (!data) return null;
let data = link.split("data=")[1]; //Returns base64 encoded data
data = decodeURIComponent(data);
let decode = base64decode(data);
let proto = decodeProto(decode);
const decodedData = decodeURIComponent(data);
const buffer = base64decode(decodedData);
const proto = decodeProto(buffer);
let protoTotps = [];
const totps = [];
const otpParameters = proto.parts.filter(
(p) => p.index === 1 && p.type === TYPES.LENDELIM,
);
proto.parts.forEach(part => {
if (part.type == TYPES.LENDELIM) {
protoTotps.push(decodeProto(part.value));
}
});
otpParameters.forEach((part) => {
const totp = _parseSingleMigrationEntry(part);
if (totp) {
totps.push(totp);
}
});
let totps = [];
protoTotps.forEach(x => {
let type = x.parts.filter(x => x.index == 6)[0]; //find type of OTP
if (type.value !== '2') {
console.log("ERR: it's a not TOTP record")
return;
}
let secret = x.parts.filter(x => x.index == 1)[0].value;
secret = encode(secret);
return totps.length > 0 ? totps : null;
} catch (err) {
console.log("Failed to parse Google Migration scheme:", err);
throw new Error(
"Invalid otpauth-migration:// link. Failed to parse migration data.",
);
}
}
let name = bytesToString(x.parts.filter(x => x.index == 2)[0].value);
let issuer = bytesToString(x.parts.filter(x => x.index == 3)[0].value);
export function getTOTPByLink(link) {
if (link.startsWith(googleMigrationScheme)) {
return getByGoogleMigrationScheme(link);
}
if (link.startsWith(otpauthScheme)) {
return getByOtpauthScheme(link);
}
totps.push(new TOTP(
secret,
issuer,
name,
6,
30,
0,
"SHA-1"
));
});
return null;
}
return totps;
function getHashType(algorithm) {
if (algorithm == "SHA1") return "SHA-1";
if (algorithm == "SHA256") return "SHA-256";
if (algorithm == "SHA512") return "SHA-512";
else return "SHA-1";
}
function getByOtpauthScheme(link) {
try {
let args = link.split("?");
let path = args[0];
let params = args[1];
let pathParts = path.split("/");
let type = pathParts[2]; //hotp or totp
let label = decodeURIComponent(pathParts[3]);
let issuerFromLabel = label.includes(":") ? label.split(":")[0] : null;
let client = label.includes(":") ? label.split(":")[1].trim() : label;
let secret = params.match(/secret=([^&]*)/)?.[1];
let issuerFromParams = params.match(/issuer=([^&]*)/)?.[1];
let issuer = issuerFromParams
? decodeURIComponent(issuerFromParams)
: issuerFromLabel;
if (!issuer) issuer = client;
let period = params.match(/period=([^&]*)/)?.[1];
let digits = params.match(/digits=([^&]*)/)?.[1];
let algorithm = params.match(/algorithm=([^&]*)/)?.[1];
let offset = params.match(/offset=([^&]*)/)?.[1] ?? 0;
if (type.toLowerCase() != "totp")
throw new Error("Type is not valid, requires 'TOTP'");
if (!secret) throw new Error("Secret not defined");
return new TOTP(
secret,
issuer,
client,
Number(digits) || 6,
Number(period) || 30,
Number(offset),
getHashType(algorithm),
);
} catch (err) {
console.log("Failed to parse otpauth scheme:", err);
throw new Error(
`Invalid otpauth:// link. Please check the link and try again. ERR: ${err}`,
);
}
}
function bytesToString(bytes) {
let str = '';
for (let i = 0; i < bytes.length; i++) {
str += String.fromCharCode(bytes[i]);
}
return str;
}
let str = "";
for (let i = 0; i < bytes.length; i++) {
str += String.fromCharCode(bytes[i]);
}
return str;
}