Skip to content

Commit

Permalink
🐢 TurtlPass Android
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanamaral committed Jan 15, 2023
0 parents commit 2d1ad99
Show file tree
Hide file tree
Showing 167 changed files with 8,500 additions and 0 deletions.
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.DS_Store
/.idea
/build
/app/build
/app/release
/.gradle
/local.properties
/app/plugins/internal/build/
/app/plugins/internal/.gradle/
google-services.json
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2023 TurtlPass <turtlpass.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<p align="center">
<img src="https://raw.githubusercontent.com/TurtlPass/turtlpass-firmware-arduino/main/assets/icon.png" alt="logo" width=90>
<h3 align="center">TurtlPass Android</h3>
<p align="center">
TurtlPass Android sends a hash of the user's inputs (App Domain, Account ID, and PIN) to the TurtlPass Firmware via USB connection. When the physical button on the device is pressed, the firmware emulates an external keyboard and types the generated password.</p>
<p align="center">
<a href="https://github.com/TurtlPass/turtlpass-android/releases"><img src="https://img.shields.io/badge/Android%20App-v1.0.0-green?logo=android" alt="Releases"/></a></p>
<p align="center">
<a href="https://github.com/TurtlPass/turtlpass-firmware-arduino"><img src="https://img.shields.io/badge/Arduino%20Firmware-v1.0.0-blue?logo=arduino" alt="Firmware Repo"/></a>
<a href="https://github.com/TurtlPass/turtlpass-chrome-extension"><img src="https://img.shields.io/badge/Chrome%20Extension-v1.0.0-blue?logo=googlechrome" alt="Chrome Extension Repo"/></a>
</p>


## ⚡ Features

* Easy app selection: Choose the app you want to generate a password for from the list of installed apps on your device.
* Gravar integration: Load image associated to the user account when the account doesn't have a contact photo already.
* Communication with hardware: Use the TurtlPass firmware device to communicate with the app via USB serial port.
* Secure password generation: The TurtlPass device generates unique, secure passwords using the HKDF algorithm and a seed stored in the flash memory.
* Automatic typing: Don't worry about remembering or typing your passwords - TurtlPass device will do it for you!
* User-friendly design: Enjoy a smooth and intuitive user experience.


## 🔑 How to Use

<img src="how-to.gif" width="300px">

1. Plug the TurtlPass device into your phone's USB port & enable USB connections
2. Choose the App you want a password for
3. Type in your Account ID, typically your email address
4. Click `Unlock` & type in your 6-digit PIN
6. (TurtlPass device generates your password)
7. Now press the button on the TurtlPass device to have it type your password


## 🏛️ Clean Architecture

* [Kotlin](https://kotlinlang.org/)
* [MVVM](https://developer.android.com/topic/libraries/architecture/viewmodel)
* [Use Cases](https://developer.android.com/topic/architecture/domain-layer#use-cases-kotlin)
* [Repositories](https://developer.android.com/topic/architecture#data-layer)
* [Dependency Injection](https://developer.android.com/training/dependency-injection/hilt-android)
* [Coroutines](https://github.com/Kotlin/kotlinx.coroutines)
* [Flow & StateFlow](https://kotlinlang.org/docs/flow.html)
* [Compose UI](https://developer.android.com/jetpack/androidx/releases/compose-ui)
* [Navigation](https://developer.android.com/jetpack/compose/navigation)

**Third-party libraries used in the project:**

[Hilt](https://dagger.dev/hilt/), [Coil](https://github.com/coil-kt/coil), [OkHttp](https://github.com/square/okhttp), [UsbSerial](https://github.com/felHR85/UsbSerial), [Lottie](https://github.com/airbnb/lottie-android), etc.

**Libraries used in the Unit Tests:**

[JUnit](https://junit.org/junit5/), [Mockk](https://github.com/mockk/mockk), [Truth](https://github.com/google/truth) & [Turbine](https://github.com/cashapp/turbine)


## 🔮 Future improvements

* Add support for Browser Apps
* Read NDEF message ID from an external NFC Tag
* Hash user inputs with Argon2 instead of SHA-512


## 📦 Lottie Animations

* [USB Memory Stick Animation](https://lottiefiles.com/20358-usb-memory-stick-animation)
* [Loading/Success/Error Animation](https://lottiefiles.com/627-loading-success-failed)


## 📄 License

TurtlPass Android is released under the [MIT License](https://github.com/TurtlPass/turtlpass-android/blob/master/LICENSE).
150 changes: 150 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
plugins {
id("com.android.application")
id("kotlin-android")
id("kotlin-parcelize")
id("internal")
id("de.mannodermaus.android-junit5")
id("dagger.hilt.android.plugin")
kotlin("plugin.serialization")
id("org.jetbrains.kotlin.android")
kotlin("kapt")
}

android {
compileSdk = internal.Android.compileSdk
buildToolsVersion = internal.Android.buildTools

defaultConfig {
applicationId = "com.turtlpass"
minSdk = internal.Android.minSdk
targetSdk = internal.Android.targetSdk
versionCode = 10000
versionName = "1.0.0"
vectorDrawables { useSupportLibrary = true }
missingDimensionStrategy("device", "anyDevice")
buildConfigField("Long", "TIMEOUT_MILLIS", "5000L")
resValue("bool", "uses_clear_text_traffic", "false")
resValue("string", "gravatar_base_url", "https://s.gravatar.com")
buildConfigField("String", "GRAVATAR_BASE_URL", "\"https://s.gravatar.com\"")
buildConfigField("String", "GRAVATAR_PIN_SET", "\"sha256/sSAE6ZWFQvZ1mQB8kh4utc/VpbMVSPQEuedwea9FrtM=\"")
}
buildTypes {
release {
getByName("release") {
// Enables code shrinking, obfuscation, and optimization
isMinifyEnabled = true
// Enables resource shrinking, performed by the Android Gradle plugin
isShrinkResources = true
// Includes ProGuard rules files that are packaged with Android Gradle plugin
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
buildFeatures {
compose = true
}
composeOptions {
kotlinCompilerExtensionVersion = internal.Versions.compose
}
packagingOptions {
resources {
excludes.add("META-INF/AL2.0")
excludes.add("META-INF/LGPL2.1")
}
}
compileOptions {
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_11
}
testOptions {
unitTests.isReturnDefaultValues = true
}
}

dependencies {
internal.Dependencies.kotlin.apply {
implementation(jetpackCore)
implementation(splashscreen)
implementation(kotlinStdlib)
implementation(coroutinesAndroid)
implementation(serialization)
}
internal.Dependencies.compose.apply {
implementation(material)
implementation(material3)
implementation(ui)
implementation(uiToolingPreview)
debugImplementation(uiTooling)
implementation(activityCompose)
implementation(runtime)
implementation(foundation)
implementation(foundationLayout)
implementation(constraintLayout)
implementation(animation)
implementation(navigationAnimation)
implementation(systemUiController)
implementation(permissions)
implementation(placeholder)
implementation(navigationMaterial)
implementation(navigationCompose)
implementation(coil)
implementation(lottie)
}
internal.Dependencies.di.apply {
implementation(daggerHiltAndroid)
kapt(daggerHiltAndroidCompiler)
implementation(hiltNavigationCompose)
// test
androidTestImplementation(daggerHiltAndroidTesting)
kaptAndroidTest(daggerHiltAndroidCompiler)
}
internal.Dependencies.network.apply {
implementation(okhttp)
implementation(interceptor)
}
internal.Dependencies.security.apply {
implementation(crypto)
}
internal.Dependencies.other.apply {
implementation(appcompat)
implementation(annotation)
implementation(material)
implementation(lifecycleViewModelKtx)
implementation(lifecycleViewModelCompose)
implementation(lifecycleViewModelSavedState)
implementation(lifecycleRuntime)
implementation(datastorePreferences)
implementation(preferenceKtx)
implementation(timber)
implementation(usbSerial)
implementation(guava)
debugImplementation(leakcanary)
debugImplementation(chucker)
releaseImplementation(chuckerNoOp)
}
internal.Dependencies.test.apply {
testImplementation(truth)
testImplementation(turbine)
testImplementation(coreTesting)
testImplementation(coroutines)
testImplementation(mockitoKotlin)
testImplementation(mockk)
testImplementation(mockkAgentJvm)
// androidTestImplementation(mockkAndroid)
// androidTestImplementation(mockkAgentJvm)
testImplementation(junit5JupiterApi)
testRuntimeOnly(junit5JupiterEngine)
testImplementation(junit5JupiterParams)
testRuntimeOnly(junit5VintageEngine)
// androidTestImplementation(junit5Core)
// androidTestRuntimeOnly(junit5Runner)
testImplementation(sharedPreferencesMock)
}
}
// Allow references to generated code (Hilt)
kapt {
correctErrorTypes = true
}
18 changes: 18 additions & 0 deletions app/plugins/internal/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
plugins {
`kotlin-dsl`
`java-gradle-plugin`
}

group = "internal"
version = "2.0.2.3"

repositories {
mavenCentral()
}

gradlePlugin {
plugins.register("internal") {
id = "internal"
implementationClass = "internal.InternalPlugin"
}
}
1 change: 1 addition & 0 deletions app/plugins/internal/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "Internal"
9 changes: 9 additions & 0 deletions app/plugins/internal/src/main/kotlin/internal/Android.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@file:Suppress("unused")
package internal

object Android {
const val buildTools = "33.0.0"
const val minSdk = 23
const val targetSdk = 33
const val compileSdk = 33
}
12 changes: 12 additions & 0 deletions app/plugins/internal/src/main/kotlin/internal/Dependencies.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@file:Suppress("unused")
package internal

object Dependencies {
val kotlin = internal.dependencies.Kotlin
val compose = internal.dependencies.Compose
val di = internal.dependencies.Di
val network = internal.dependencies.Network
val security = internal.dependencies.Security
val other = internal.dependencies.Other
val test = internal.dependencies.Test
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package internal

import org.gradle.api.*


class InternalPlugin : Plugin<Project> {

override fun apply(target: Project) {}
}
40 changes: 40 additions & 0 deletions app/plugins/internal/src/main/kotlin/internal/Versions.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
@file:Suppress("unused")

package internal

object Versions {
const val jetpackCore = "1.9.0" // https://developer.android.com/jetpack/androidx/releases/core
const val ktxCore = "1.7.21" // https://kotlinlang.org/docs/releases.html#release-details
const val compose = "1.4.0-alpha02" // https://developer.android.com/jetpack/androidx/versions/all-channel
//|> 1.4.0-alpha02 compatible with Kotlin 1.7.21
const val ktxCoroutinesAndroid = "1.6.0"
const val hilt = "1.0.0"
const val hiltCore = "2.44" // https://github.com/google/dagger/releases
const val hiltComposeNavigation = "1.0.0-beta01" // https://developer.android.com/jetpack/androidx/releases/hilt
const val accompanist = "0.28.0"
const val material3 = "1.0.0-alpha02" // https://developer.android.com/jetpack/androidx/releases/compose-material3
const val constraint = "1.0.0-alpha08"
const val pagingCompose = "1.0.0-alpha09"
const val lifecycle = "2.6.0-alpha03"
const val paging = "3.0.0"
const val lottie = "4.2.2" // http://airbnb.io/lottie/#/android-compose
const val material = "1.5.0"
const val startup = "1.0.0"
const val room = "2.3.0"
const val okhttp = "4.9.3"
const val retrofit2 = "2.9.0"
const val interceptor = "4.9.1"
const val sandwich = "1.1.0"
const val serialization = "1.2.1"
const val browserCustomTabs = "1.3.0"
const val kotlinxDatetime = "0.2.1"
const val securityCrypto = "1.1.0-alpha04"
const val securityIdentityCredential = "1.0.0-alpha02"
const val timber = "4.7.1"
// test
const val testManifest = "1.1.0-beta04"
const val mockWebServer = "4.9.1"
const val mockitoCore = "3.10.0"
const val junit5 = "5.9.1"
const val mockk = "1.13.3"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@file:Suppress("unused")

package internal.dependencies

import internal.Versions

object Compose {
const val material = "androidx.compose.material:material:1.4.0-alpha03"
const val material3 = "androidx.compose.material3:material3:1.1.0-alpha03"
const val materialIcons = "androidx.compose.material:material-icons-extended:1.4.0-alpha03"
const val ui = "androidx.compose.ui:ui:1.4.0-alpha03"
const val foundation = "androidx.compose.foundation:foundation:1.4.0-alpha03"
const val foundationLayout = "androidx.compose.foundation:foundation-layout:1.4.0-alpha03"
const val uiToolingPreview = "androidx.compose.ui:ui-tooling-preview:1.4.0-alpha03"
const val uiTooling = "androidx.compose.ui:ui-tooling:1.4.0-alpha03"
const val activityCompose = "androidx.activity:activity-compose:1.6.1"
const val runtime = "androidx.compose.runtime:runtime:${Versions.compose}"
const val animation = "androidx.compose.animation:animation:1.4.0-alpha03"
const val constraintLayout = "androidx.constraintlayout:constraintlayout-compose:1.0.1"
const val coil = "io.coil-kt:coil-compose:2.2.2"
const val lottie = "com.airbnb.android:lottie-compose:5.2.0"
const val systemUiController = "com.google.accompanist:accompanist-systemuicontroller:${Versions.accompanist}"
const val navigationAnimation = "com.google.accompanist:accompanist-navigation-animation:${Versions.accompanist}"
const val permissions = "com.google.accompanist:accompanist-permissions:${Versions.accompanist}"
const val placeholder = "com.google.accompanist:accompanist-placeholder:${Versions.accompanist}"
const val navigationMaterial = "com.google.accompanist:accompanist-navigation-material:${Versions.accompanist}"
const val navigationCompose = "androidx.navigation:navigation-compose:2.5.3"
}
15 changes: 15 additions & 0 deletions app/plugins/internal/src/main/kotlin/internal/dependencies/Di.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
@file:Suppress("unused")
package internal.dependencies

import internal.*

/**
* Dagger - Hilt
*/
object Di {
const val daggerHiltAndroid = "com.google.dagger:hilt-android:${Versions.hiltCore}"
const val daggerHiltAndroidCompiler = "com.google.dagger:hilt-android-compiler:${Versions.hiltCore}"
const val hiltCompiler = "androidx.hilt:hilt-compiler:${Versions.hilt}"
const val hiltNavigationCompose = "androidx.hilt:hilt-navigation-compose:1.0.0"
const val daggerHiltAndroidTesting = "com.google.dagger:hilt-android-testing:${Versions.hiltCore}"
}
Loading

0 comments on commit 2d1ad99

Please sign in to comment.