diff --git a/README.md b/README.md index 25f7017..e958a1d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,17 @@ # TOTPFIT ### Another 2FAuthenticator based on TOTP for Zepp Amazfit GTS 4 -Features: +![alt text](docs/assets/image2.png) + +### Features: - Supports of otpauth links with parameters "issuer", "algorithm", "digits", "period" -- Addition/Edition/Deletion of TOTPs from mobile settings app \ No newline at end of file +- Addition/Edition/Deletion of TOTPs from mobile settings app + +### Google Migration Support: +- Support of google migration links formated: ```otpauth-migration://offline?data=...``` (BETA) + +### Screenshots: + +![alt text](docs/assets/image2.png) + +![alt text](docs/assets/image.png) \ No newline at end of file diff --git a/docs/assets/image.png b/docs/assets/image.png new file mode 100644 index 0000000..69a46ed Binary files /dev/null and b/docs/assets/image.png differ diff --git a/docs/assets/image2.png b/docs/assets/image2.png new file mode 100644 index 0000000..0e82be5 Binary files /dev/null and b/docs/assets/image2.png differ diff --git a/lib/protobuf-decoder/varintUtils.js b/lib/protobuf-decoder/varintUtils.js index fbd69a2..6b9b6b4 100644 --- a/lib/protobuf-decoder/varintUtils.js +++ b/lib/protobuf-decoder/varintUtils.js @@ -9,7 +9,6 @@ export function decodeVarint(buffer, offset) { } byte = buffer[offset++]; - console.log(this) const multiplier = this.BigInt(2) ** this.BigInt(shift); const thisByteValue = this.BigInt(byte & 0x7f) * multiplier; diff --git a/lib/totp-quickjs/base32decoder.js b/lib/totp-quickjs/base32decoder.js index 821b33c..784e2b1 100644 --- a/lib/totp-quickjs/base32decoder.js +++ b/lib/totp-quickjs/base32decoder.js @@ -25,6 +25,32 @@ function leftpad(str, len, pad) { ); } +export function encode(bytes) { + const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; + let bits = 0; + let value = 0; + let output = ''; + + for (let i = 0; i < bytes.length; i++) { + value = (value << 8) | bytes[i]; + bits += 8; + + while (bits >= 5) { + output += alphabet[(value >>> (bits - 5)) & 0x1F]; + bits -= 5; + } + } + + if (bits > 0) { + output += alphabet[(value << (5 - bits)) & 0x1F]; + } + + const paddingLength = (8 - (output.length % 8)) % 8; + output += '='.repeat(paddingLength); + + return output; +} + export function base64decode(base64) { const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; let result = []; diff --git a/page/index.js b/page/index.js index 776ff07..c60a457 100644 --- a/page/index.js +++ b/page/index.js @@ -6,7 +6,6 @@ import { LocalStorage } from "@zos/storage"; const app = getApp(); let waitForFetch = true; -let localStorage = new LocalStorage(); Page( BasePage({ @@ -14,6 +13,8 @@ Page( this.getTOTPData() .then((x) => { app._options.globalData.TOTPS = JSON.parse(x) ?? []; + + let localStorage = new LocalStorage(); localStorage.setItem( "TOTPs", JSON.stringify(app._options.globalData.TOTPS) @@ -22,6 +23,7 @@ Page( }) .catch((x) => { console.log(`Init failed: ${x}`); + let localStorage = new LocalStorage(); app._options.globalData.TOTPS = JSON.parse( localStorage.getItem("TOTPs", null) ?? [] ); diff --git a/setting/index.js b/setting/index.js index e73daf1..03e27a1 100644 --- a/setting/index.js +++ b/setting/index.js @@ -13,12 +13,16 @@ AppSettingsPage({ placeholder: "otpauth://", label: "Add new OTP Link", onChange: (changes) => { - var link = getTOTPByLink(changes); + let link = getTOTPByLink(changes); if (link == null) { console.log("link is invalid"); return; } - storage.push(link); + + if(Array.isArray(link)) + storage.push(...link); + else storage.push(link); + updateStorage(storage); }, labelStyle: { @@ -80,7 +84,11 @@ function GetTOTPList(storage) { label: "Change OTP link", onChange: (changes) => { try { - storage[elementId] = getTOTPByLink(changes); + let link = getTOTPByLink(changes); + if(Array.isArray(link)) + return; + + storage[elementId] = link; updateStorage(storage); } catch (err) { console.log(err); diff --git a/setting/utils/queryParser.js b/setting/utils/queryParser.js index 9bb52f0..1a27e22 100644 --- a/setting/utils/queryParser.js +++ b/setting/utils/queryParser.js @@ -1,6 +1,6 @@ -import { decodeProto } from "../../lib/protobuf-decoder/protobufDecoder"; +import { decodeProto, TYPES } from "../../lib/protobuf-decoder/protobufDecoder"; import { TOTP } from "../../lib/totp-quickjs"; -import { base64decode } from "../../lib/totp-quickjs/base32decoder"; +import { base64decode, encode } from "../../lib/totp-quickjs/base32decoder"; const otpauthScheme = "otpauth:/"; const googleMigrationScheme = "otpauth-migration:/"; @@ -40,7 +40,7 @@ function getByOtpauthScheme(link){ if (secret === undefined) throw new Error("Secret not defined"); if(issuer == client){ - issuer = args[3].split("issuer=")[1]?.split("&")[0] + issuer = args[3].split("issuer=")[1]?.split("&")[0]; } issuer = decodeURIComponent(issuer); @@ -65,9 +65,49 @@ function getByGoogleMigrationScheme(link){ let data = link.split("data=")[1]; //Returns base64 encoded data data = decodeURIComponent(data); - console.log(data) let decode = base64decode(data); - console.log(decode) let proto = decodeProto(decode); - console.log(proto); + + let protoTotps = []; + + proto.parts.forEach(part => { + if(part.type == TYPES.LENDELIM){ + protoTotps.push(decodeProto(part.value)); + } + }); + + 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); + + let name = bytesToString(x.parts.filter(x => x.index == 2)[0].value); + let issuer = bytesToString(x.parts.filter(x => x.index == 3)[0].value); + + totps.push(new TOTP( + secret, + issuer, + name, + 6, + 30, + 0, + "SHA-1" + )); + }); + + return totps; + +} + +function bytesToString(bytes) { + let str = ''; + for (let i = 0; i < bytes.length; i++) { + str += String.fromCharCode(bytes[i]); + } + return str; } \ No newline at end of file