├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── proton-authenticator-mobile ├── Cargo.toml ├── android │ ├── .gitignore │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ ├── libs.versions.toml │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── lib │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ └── AndroidManifest.xml │ ├── libTest │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── test │ │ │ └── kotlin │ │ │ └── proton │ │ │ └── android │ │ │ └── authenticator │ │ │ ├── AuthenticatorCryptoTest.kt │ │ │ ├── AuthenticatorLoggerTest.kt │ │ │ ├── MobileAuthenticatorClientTest.kt │ │ │ ├── SyncOperationCheckerTest.kt │ │ │ ├── TestUtils.kt │ │ │ └── TotpGeneratorTest.kt │ ├── libTestApp │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── ic_launcher-playstore.png │ │ │ ├── java │ │ │ └── proton │ │ │ │ └── authenticator │ │ │ │ └── android │ │ │ │ └── benchmark │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ ├── drawable │ │ │ ├── ic_launcher_background.xml │ │ │ └── ic_launcher_foreground.xml │ │ │ ├── layout │ │ │ └── main_activity.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ │ └── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ └── settings.gradle.kts ├── bindgen.rs ├── build.rs ├── iOS │ └── AuthenticatorRustCore │ │ ├── .gitignore │ │ ├── .swiftpm │ │ └── xcode │ │ │ └── package.xcworkspace │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Package.swift │ │ └── README.md ├── src │ ├── authenticator.rs │ ├── authenticator.udl │ ├── benchmark.rs │ ├── benchmark.udl │ ├── crypto.rs │ ├── crypto.udl │ ├── entry.rs │ ├── generator.rs │ ├── generator.udl │ ├── import.rs │ ├── import.udl │ ├── issuer_mapper.rs │ ├── issuer_mapper.udl │ ├── lib.rs │ ├── log.rs │ ├── log.udl │ ├── namespace.udl │ ├── operations.rs │ ├── operations.udl │ ├── ordering.rs │ └── ordering.udl └── uniffi.toml ├── proton-authenticator-web ├── Cargo.toml ├── asm.js ├── package.json ├── src │ ├── common.rs │ ├── entry.rs │ ├── lib.rs │ ├── log.rs │ └── worker │ │ ├── client.rs │ │ ├── crypto.rs │ │ ├── generator.rs │ │ ├── import.rs │ │ ├── issuer.rs │ │ ├── mod.rs │ │ ├── operations.rs │ │ └── ordering.rs └── test │ ├── .gitignore │ ├── README.md │ ├── bun.lockb │ ├── package.json │ ├── proton-authenticator-web-worker-generator.spec.ts │ ├── proton-authenticator-web-worker-importer.spec.ts │ ├── proton-authenticator-web-worker-operations.spec.ts │ ├── proton-authenticator-web-worker.spec.ts │ └── tsconfig.json ├── proton-authenticator ├── Cargo.toml ├── build.rs ├── proto │ ├── authenticator_entry.proto │ └── google_authenticator.proto ├── resources │ ├── issuerInfos.txt │ └── issuerManualOverrides.txt ├── src │ ├── client.rs │ ├── crypto.rs │ ├── entry │ │ ├── create.rs │ │ ├── exporter.rs │ │ ├── gen │ │ │ ├── authenticator_entry.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── serializer.rs │ │ └── update.rs │ ├── generator.rs │ ├── issuer_mapper.rs │ ├── lib.rs │ ├── log.rs │ ├── operations.rs │ ├── ordering.rs │ ├── parser │ │ ├── aegis │ │ │ ├── db.rs │ │ │ ├── encrypted.rs │ │ │ ├── json.rs │ │ │ ├── mod.rs │ │ │ └── txt.rs │ │ ├── bitwarden │ │ │ ├── csv.rs │ │ │ ├── json.rs │ │ │ └── mod.rs │ │ ├── ente │ │ │ ├── mod.rs │ │ │ └── txt.rs │ │ ├── google │ │ │ ├── gen │ │ │ │ ├── google_authenticator.rs │ │ │ │ └── mod.rs │ │ │ └── mod.rs │ │ ├── lastpass │ │ │ ├── json.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── proton_authenticator │ │ │ └── mod.rs │ │ └── twofas │ │ │ ├── mod.rs │ │ │ └── parser.rs │ ├── steam │ │ └── mod.rs │ ├── test_utils.rs │ └── utils.rs └── test_data │ └── authenticator │ ├── 2fas │ ├── decrypted.2fas │ ├── decrypted_ios.2fas │ ├── encrypted.2fas │ └── password │ ├── aegis │ ├── aegis-json-encrypted-test.json │ ├── aegis-json-unencrypted.json │ ├── aegis-txt.txt │ └── password │ ├── bitwarden │ ├── bitwarden.csv │ ├── bitwarden.json │ └── bitwarden_sample.csv │ ├── ente │ └── plain.txt │ └── lastpass │ ├── lastpass.json │ └── lastpass_ios_export.json ├── proton-pass-common ├── 2faDomains.txt ├── Cargo.toml ├── benches │ ├── card_detector.rs │ └── password_scorer.rs ├── build.rs ├── eff_large_wordlist.txt ├── passwords.txt ├── src │ ├── alias_prefix.rs │ ├── creditcard │ │ ├── detector.rs │ │ └── mod.rs │ ├── domain.rs │ ├── email.rs │ ├── file │ │ ├── associations.rs │ │ ├── mod.rs │ │ └── sanitize_filename.rs │ ├── host.rs │ ├── invite.rs │ ├── lib.rs │ ├── login.rs │ ├── passkey │ │ ├── generate.rs │ │ ├── mod.rs │ │ ├── parser │ │ │ ├── cvs.rs │ │ │ ├── ebay.rs │ │ │ ├── equal_sign.rs │ │ │ ├── mod.rs │ │ │ ├── paypal.rs │ │ │ ├── sanitize.rs │ │ │ └── swissid.rs │ │ ├── passkey_handling.rs │ │ ├── protonpasskey.rs │ │ ├── protonpasskeydeserializer.rs │ │ ├── protonpasskeyserializer.rs │ │ └── resolve.rs │ ├── password │ │ ├── analyzer.rs │ │ ├── mod.rs │ │ ├── password_generator.rs │ │ └── scorer.rs │ ├── qr.rs │ ├── twofa.rs │ └── wifi.rs ├── test_data │ └── file_format │ │ ├── pgpkey.private │ │ ├── pgpkey.pub │ │ ├── sample-unclosed.svg │ │ ├── sample.avi │ │ ├── sample.docx │ │ ├── sample.garbage │ │ ├── sample.ics │ │ ├── sample.jpg │ │ ├── sample.mp3 │ │ ├── sample.mp4 │ │ ├── sample.pages │ │ ├── sample.pdf │ │ ├── sample.png │ │ ├── sample.rar │ │ ├── sample.svg │ │ ├── sample.txt │ │ ├── sample.wav │ │ ├── sample.xlsx │ │ └── sample.zip └── tests │ ├── alias_prefix_valid.rs │ ├── credit_card_detector.rs │ ├── domain.rs │ ├── email_valid.rs │ ├── file.rs │ ├── passkey.rs │ ├── password_scorer.rs │ └── twofa_valid.rs ├── proton-pass-derive ├── Cargo.toml └── src │ └── lib.rs ├── proton-pass-mobile ├── Cargo.toml ├── android │ ├── .gitignore │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── lib │ │ ├── .gitignore │ │ ├── build.gradle.kts │ │ ├── proguard-rules.pro │ │ └── src │ │ │ └── main │ │ │ └── AndroidManifest.xml │ └── settings.gradle.kts ├── bindgen.rs ├── build.rs ├── iOS │ └── PassRustCore │ │ ├── .gitignore │ │ ├── .swiftpm │ │ └── xcode │ │ │ └── package.xcworkspace │ │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ │ ├── Package.swift │ │ └── README.md ├── src │ ├── alias.rs │ ├── alias.udl │ ├── creditcard.rs │ ├── creditcard.udl │ ├── domain.rs │ ├── domain.udl │ ├── email.rs │ ├── email.udl │ ├── file.rs │ ├── file.udl │ ├── host.rs │ ├── host.udl │ ├── invite.rs │ ├── invite.udl │ ├── lib.rs │ ├── login.rs │ ├── login.udl │ ├── namespace.udl │ ├── passkey.rs │ ├── passkey.udl │ ├── password.rs │ ├── password.udl │ ├── qr.rs │ ├── qr.udl │ ├── totp.rs │ ├── totp.udl │ ├── twofa.rs │ ├── twofa.udl │ ├── wifi.rs │ └── wifi.udl └── uniffi.toml ├── proton-pass-totp ├── Cargo.toml ├── benches │ └── totp_generation.rs ├── src │ ├── algorithm.rs │ ├── error.rs │ ├── lib.rs │ ├── queries.rs │ ├── sanitizer.rs │ └── totp.rs └── tests │ ├── edent.rs │ └── totp.rs ├── proton-pass-web ├── Cargo.toml ├── package.json ├── src │ ├── common.rs │ ├── lib.rs │ ├── password │ │ ├── mod.rs │ │ └── password.rs │ ├── ui │ │ ├── creditcard.rs │ │ ├── file.rs │ │ ├── login.rs │ │ ├── mod.rs │ │ └── wifi.rs │ └── worker │ │ ├── mod.rs │ │ ├── passkey.rs │ │ └── totp.rs └── test │ ├── .gitignore │ ├── README.md │ ├── bun.lockb │ ├── package.json │ ├── proton-pass-web-password.spec.ts │ ├── proton-pass-web-ui.spec.ts │ ├── proton-pass-web-worker.spec.ts │ └── tsconfig.json ├── release.toml ├── rustfmt.toml └── tools ├── custom2faDomains.txt ├── excluded2faDomains.txt ├── generate2FADomains.py ├── generateCommonPasswordList.py └── icon_fetcher ├── Cargo.toml ├── config.toml └── src └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | .idea/ 3 | /target 4 | .cargo/ 5 | 6 | # Pass mobile 7 | proton-pass-mobile/android/lib/**/*.kt 8 | proton-pass-mobile/iOS/headers 9 | proton-pass-mobile/iOS/frameworks 10 | proton-pass-mobile/iOS/PassRustCore/*.xcframework 11 | proton-pass-mobile/src/proton 12 | proton-pass-mobile/src/common.udl 13 | proton-pass-mobile/src/*.h 14 | proton-pass-mobile/src/*.swift 15 | proton-pass-mobile/src/*.modulemap 16 | proton-pass-mobile/iOS/**/*.swift 17 | proton-pass-mobile/pkg 18 | 19 | proton-pass-web/**/pkg 20 | proton-pass-web/dist 21 | 22 | # Authenticator 23 | proton-authenticator-mobile/android/lib/**/*.kt 24 | proton-authenticator-mobile/iOS/headers 25 | proton-authenticator-mobile/iOS/frameworks 26 | proton-authenticator-mobile/iOS/AuthenticatorRustCore/*.xcframework 27 | proton-authenticator-mobile/src/proton 28 | proton-authenticator-mobile/src/common.udl 29 | proton-authenticator-mobile/src/*.h 30 | proton-authenticator-mobile/src/*.swift 31 | proton-authenticator-mobile/src/*.modulemap 32 | proton-authenticator-mobile/iOS/**/*.swift 33 | proton-authenticator-mobile/pkg 34 | 35 | proton-authenticator-web/**/pkg 36 | proton-authenticator-web/dist 37 | 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "1" 3 | members = [ 4 | "proton-authenticator", 5 | "proton-authenticator-mobile", 6 | "proton-authenticator-web", 7 | "proton-pass-common", 8 | "proton-pass-mobile", 9 | "proton-pass-web", 10 | "proton-pass-derive", 11 | "proton-pass-totp", 12 | "tools/icon_fetcher" 13 | ] 14 | 15 | [workspace.dependencies] 16 | criterion = { version = "0.5.1", features = ["html_reports"] } 17 | js-sys = "0.3.77" 18 | serde = { version = "1.0", features = ["derive"] } 19 | serde_json = "1.0" 20 | tokio = { version = "1.44.1", features = ["full"] } 21 | tsify-next = { version = "0.5.5", features = ["wasm-bindgen", "js", "json"] } 22 | wasm-bindgen = "0.2.100" 23 | wasm-bindgen-futures = "0.4.50" 24 | 25 | [profile.release] 26 | opt-level = "s" # Tell `rustc` to optimize for small code size. 27 | strip = true 28 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "proton-authenticator-mobile" 3 | version = "0.21.1" 4 | edition = "2021" 5 | 6 | # Flags for cargo-release 7 | [package.metadata.release] 8 | pre-release-replacements = [ 9 | # Yes, we are relying on the number of whitespaces 10 | {file="android/lib/build.gradle.kts", search=" version = .*", replace=" version = \"{{version}}\""} 11 | ] 12 | 13 | 14 | [lib] 15 | doctest = false 16 | crate-type = ["cdylib", "staticlib"] 17 | name = "proton_authenticator_common_mobile" 18 | 19 | [dependencies] 20 | proton-authenticator = { path = "../proton-authenticator" } 21 | proton-pass-derive = { path = "../proton-pass-derive" } 22 | 23 | tokio.workspace = true 24 | 25 | chrono = "0.4.40" 26 | uniffi = { version = "0.29.2" } 27 | 28 | [build-dependencies] 29 | uniffi = { version = "0.29.2", features = [ "build" ] } 30 | 31 | [[bin]] 32 | name = "uniffi-bindgen" 33 | path = "./bindgen.rs" 34 | required-features = [ "uniffi/cli" ] 35 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | *.so 17 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.gradlePlugin.application) apply false 4 | alias(libs.plugins.gradlePlugin.kotlin.android) apply false 5 | alias(libs.plugins.gradlePlugin.kotlin.jvm) apply false 6 | alias(libs.plugins.gradlePlugin.kotlin.serialization) apply false 7 | alias(libs.plugins.gradlePlugin.kotlinx.atomicfu) apply false 8 | alias(libs.plugins.gradlePlugin.library) apply false 9 | alias(libs.plugins.gradlePlugin.maven.publish) apply false 10 | } 11 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 2 | android.useAndroidX=true 3 | kotlin.code.style=official 4 | android.nonTransitiveRClass=true 5 | 6 | # --- MAVEN PUBLISHING CONFIG 7 | RELEASE_SIGNING_ENABLED=true 8 | 9 | # or when publishing to https://s01.oss.sonatype.org 10 | SONATYPE_HOST=S01 11 | 12 | POM_NAME=Proton Authenticator Common 13 | POM_DESCRIPTION=A shared library for Proton Authenticator clients that implements common operations. 14 | POM_URL=https://github.com/ProtonPass 15 | POM_INCEPTION_YEAR=2023 16 | POM_LICENSE_NAME=GNU GENERAL PUBLIC LICENSE, Version 3.0 17 | POM_LICENSE_URL=https://www.gnu.org/licenses/gpl-3.0.en.html 18 | POM_LICENSE_DIST=repo 19 | POM_DEVELOPER_ID=opensource@proton.me 20 | POM_DEVELOPER_NAME=Open Source Proton 21 | POM_DEVELOPER_URL=opensource@proton.me 22 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protonpass/proton-pass-common/8d5de37e2defe2a2a5e037df2ef555db83e82496/proton-authenticator-mobile/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Sep 20 13:19:37 CEST 2023 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/lib/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/lib/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/lib/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | jniLibs/ 3 | src/main/kotlin/**/*.kt -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.gradlePlugin.kotlin.jvm) 3 | alias(libs.plugins.gradlePlugin.kotlinx.atomicfu) 4 | alias(libs.plugins.gradlePlugin.kotlin.serialization) 5 | } 6 | 7 | kotlin { 8 | jvmToolchain(17) 9 | } 10 | 11 | sourceSets { 12 | main { 13 | // Make sure jniLibs is treated as resources, so it is on the classpath 14 | resources { 15 | srcDir("src/main/jniLibs") 16 | } 17 | } 18 | } 19 | 20 | tasks.test { 21 | systemProperty("jna.library.path", file("src/main/jniLibs").absolutePath) 22 | 23 | testLogging { 24 | events("PASSED", "SKIPPED", "FAILED") // which events to display 25 | showStandardStreams = true // show standard out/err for each test 26 | exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation(libs.kotlinx.serialization.json) 32 | implementation(libs.kotlinx.coroutines.core) 33 | implementation(libs.jna) 34 | implementation(libs.okio) 35 | 36 | testImplementation(libs.coroutines.test) 37 | testImplementation(libs.kotlinx.coroutines.core) 38 | testImplementation(libs.kotlinx.datetime) 39 | testImplementation(libs.kotlinTest) 40 | testImplementation(libs.junit) 41 | testImplementation(libs.truth) 42 | testImplementation(libs.turbine) 43 | } -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/src/test/kotlin/proton/android/authenticator/AuthenticatorCryptoTest.kt: -------------------------------------------------------------------------------- 1 | package proton.android.authenticator 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import uniffi.proton_authenticator_common_mobile.AuthenticatorCrypto 6 | import uniffi.proton_authenticator_common_mobile.AuthenticatorMobileClient 7 | 8 | class AuthenticatorCryptoTest { 9 | 10 | private val crypto = AuthenticatorCrypto() 11 | 12 | @Test 13 | fun `can generate encryption key`() { 14 | val key = crypto.generateKey() 15 | assertThat(key.size).isEqualTo(32) 16 | } 17 | 18 | @Test 19 | fun `can encrypt and decrypt`() { 20 | val client = AuthenticatorMobileClient() 21 | val label1 = "MYLABEL1" 22 | val label2 = "MYLABEL2" 23 | 24 | val entry1 = client.entryFromUri(entryUriWithLabel(label1)) 25 | val entry2 = client.entryFromUri(entryUriWithLabel(label2)) 26 | 27 | val key = crypto.generateKey() 28 | val encrypted = crypto.encryptManyEntries(listOf(entry1, entry2), key) 29 | 30 | assertThat(encrypted.size).isEqualTo(2) 31 | 32 | val decrypted = crypto.decryptManyEntries(encrypted, key) 33 | assertThat(decrypted.size).isEqualTo(2) 34 | 35 | assertThat(decrypted[0].name).isEqualTo(label1) 36 | assertThat(decrypted[1].name).isEqualTo(label2) 37 | } 38 | 39 | @Test 40 | fun `generateKey generates different keys`() { 41 | val key1 = crypto.generateKey() 42 | val key2 = crypto.generateKey() 43 | 44 | assertThat(key1).isNotEqualTo(key2) 45 | } 46 | 47 | private fun entryUriWithLabel(label: String): String = 48 | "otpauth://totp/$label?secret=MYSECRET&issuer=MYISSUER&algorithm=SHA256&digits=8&period=15" 49 | } -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/src/test/kotlin/proton/android/authenticator/AuthenticatorLoggerTest.kt: -------------------------------------------------------------------------------- 1 | package proton.android.authenticator 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import uniffi.proton_authenticator_common_mobile.AuthenticatorLogLevel 6 | import uniffi.proton_authenticator_common_mobile.AuthenticatorLogger 7 | import uniffi.proton_authenticator_common_mobile.emitLog 8 | import uniffi.proton_authenticator_common_mobile.registerAuthenticatorLogger 9 | 10 | class AuthenticatorLoggerTest { 11 | 12 | @Test 13 | fun `can register a logger`() { 14 | val loggedMessages = mutableListOf>() 15 | registerAuthenticatorLogger(object : AuthenticatorLogger { 16 | override fun log(level: AuthenticatorLogLevel, message: String) { 17 | loggedMessages.add(level to message) 18 | } 19 | }) 20 | 21 | val messages = listOf( 22 | "trace message", 23 | "debug message", 24 | "info message", 25 | "warn message", 26 | "error message" 27 | ) 28 | 29 | val levels = listOf( 30 | AuthenticatorLogLevel.TRACE, 31 | AuthenticatorLogLevel.DEBUG, 32 | AuthenticatorLogLevel.INFO, 33 | AuthenticatorLogLevel.WARN, 34 | AuthenticatorLogLevel.ERROR, 35 | ) 36 | 37 | for ((message, level) in messages.zip(levels)) { 38 | emitLog(level, message) 39 | } 40 | 41 | assertThat(loggedMessages.size).isEqualTo(messages.size) 42 | 43 | for (i in messages.indices) { 44 | val loggedMessage = loggedMessages[i] 45 | assertThat(loggedMessage.first).isEqualTo(levels[i]) 46 | assertThat(loggedMessage.second).isEqualTo(messages[i]) 47 | } 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/src/test/kotlin/proton/android/authenticator/SyncOperationCheckerTest.kt: -------------------------------------------------------------------------------- 1 | package proton.android.authenticator 2 | 3 | import com.google.common.truth.Truth.assertThat 4 | import org.junit.Test 5 | import uniffi.proton_authenticator_common_mobile.LocalEntry 6 | import uniffi.proton_authenticator_common_mobile.LocalEntryState 7 | import uniffi.proton_authenticator_common_mobile.OperationType 8 | import uniffi.proton_authenticator_common_mobile.RemoteEntry 9 | import uniffi.proton_authenticator_common_mobile.SyncOperationChecker 10 | 11 | class SyncOperationCheckerTest { 12 | 13 | private val instance = SyncOperationChecker() 14 | 15 | @Test 16 | fun `can handle empty lists`() { 17 | val res = instance.calculateOperations(emptyList(), emptyList()) 18 | assertThat(res).isEmpty() 19 | } 20 | 21 | @Test 22 | fun `does not return anything in case no differences`() { 23 | val entry = TestUtils.getEntry1() 24 | val remote = listOf(RemoteEntry(remoteId = "REMOTE_ID", entry = entry, modifyTime = NOW)) 25 | val local = listOf(LocalEntry( 26 | entry = entry, 27 | state = LocalEntryState.SYNCED, 28 | modifyTime = NOW, 29 | localModifyTime = null 30 | )) 31 | val res = instance.calculateOperations(remote, local) 32 | assertThat(res).isEmpty() 33 | } 34 | 35 | @Test 36 | fun `remote entry not present in local returns upsert`() { 37 | val entry = TestUtils.getEntry1() 38 | val remoteId = "REMOTE_ID" 39 | val remote = listOf(RemoteEntry(remoteId = remoteId, entry = entry, modifyTime = NOW)) 40 | val res = instance.calculateOperations(remote, emptyList()) 41 | 42 | assertThat(res.size).isEqualTo(1) 43 | assertThat(res[0].entry).isEqualTo(entry) 44 | assertThat(res[0].remoteId).isEqualTo(remoteId) 45 | assertThat(res[0].operation).isEqualTo(OperationType.UPSERT) 46 | } 47 | 48 | @Test 49 | fun `local entry pending to be pushed not present in remote returns push`() { 50 | val entry = TestUtils.getEntry1() 51 | val local = listOf(LocalEntry( 52 | entry = entry, 53 | state = LocalEntryState.PENDING_SYNC, 54 | modifyTime = NOW, 55 | localModifyTime = null 56 | )) 57 | val res = instance.calculateOperations(emptyList(), local) 58 | 59 | assertThat(res.size).isEqualTo(1) 60 | assertThat(res[0].entry).isEqualTo(entry) 61 | assertThat(res[0].remoteId).isNull() 62 | assertThat(res[0].operation).isEqualTo(OperationType.PUSH) 63 | } 64 | 65 | companion object { 66 | private val NOW = 1_700_000_000L 67 | } 68 | 69 | } -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTest/src/test/kotlin/proton/android/authenticator/TestUtils.kt: -------------------------------------------------------------------------------- 1 | package proton.android.authenticator 2 | 3 | import uniffi.proton_authenticator_common_mobile.AuthenticatorEntryModel 4 | import uniffi.proton_authenticator_common_mobile.AuthenticatorMobileClient 5 | 6 | object TestUtils { 7 | 8 | fun getEntry1(): AuthenticatorEntryModel { 9 | val client = AuthenticatorMobileClient() 10 | return client.entryFromUri("otpauth://totp/MYLABEL?secret=MYSECRET&issuer=MYISSUER&algorithm=SHA256&digits=8&period=15") 11 | } 12 | 13 | fun getEntry2(): AuthenticatorEntryModel { 14 | val client = AuthenticatorMobileClient() 15 | return client.entryFromUri("otpauth://totp/MYLABEL?secret=MYSECRET123&issuer=MYISSUER&algorithm=SHA256&digits=8&period=15") 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTestApp/.gitignore: -------------------------------------------------------------------------------- 1 | build/ -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTestApp/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.gradlePlugin.application) 3 | alias(libs.plugins.gradlePlugin.kotlin.android) 4 | } 5 | 6 | android { 7 | namespace = "proton.android.authenticator.benchmark" 8 | compileSdk = libs.versions.compileSdk.get().toInt() 9 | 10 | defaultConfig { 11 | minSdk = libs.versions.minSdk.get().toInt() 12 | 13 | proguardFiles( 14 | getDefaultProguardFile("proguard-android-optimize.txt"), 15 | "proguard-rules.pro" 16 | ) 17 | } 18 | 19 | compileOptions { 20 | sourceCompatibility = JavaVersion.VERSION_17 21 | targetCompatibility = JavaVersion.VERSION_17 22 | } 23 | 24 | kotlinOptions { 25 | jvmTarget = JavaVersion.VERSION_17.toString() 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation(projects.lib) 31 | 32 | implementation(libs.kotlinx.coroutines.core) 33 | implementation(libs.kotlinx.datetime) 34 | } 35 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTestApp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTestApp/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/protonpass/proton-pass-common/8d5de37e2defe2a2a5e037df2ef555db83e82496/proton-authenticator-mobile/android/libTestApp/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /proton-authenticator-mobile/android/libTestApp/src/main/java/proton/authenticator/android/benchmark/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package proton.authenticator.android.benchmark 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.widget.Button 6 | import proton.android.authenticator.benchmark.R 7 | import proton.android.authenticator.commonrust.AuthenticatorMobileClient 8 | import proton.android.authenticator.commonrust.MobileTotpBenchmark 9 | 10 | class MainActivity: Activity() { 11 | 12 | override fun onCreate(savedInstanceState: Bundle?) { 13 | super.onCreate(savedInstanceState) 14 | setContentView(R.layout.main_activity) 15 | 16 | findViewById