Skip to content

Commit 53dadff

Browse files
authored
Implement E2EE for locations & journeys
Implement E2EE for locations & journeys
2 parents 87cf70d + fc6784b commit 53dadff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+2502
-490
lines changed

app/build.gradle.kts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
import org.jetbrains.kotlin.konan.properties.hasProperty
23
import java.util.Properties
34

@@ -14,6 +15,7 @@ plugins {
1415
var versionMajor = 1
1516
var versionMinor = 0
1617
var versionBuild = 0
18+
val targetSdkVersion: Int = 34
1719

1820
android {
1921
namespace = "com.canopas.yourspace"
@@ -30,7 +32,8 @@ android {
3032
defaultConfig {
3133
applicationId = "com.canopas.yourspace"
3234
minSdk = 24
33-
targetSdk = 34
35+
targetSdk = targetSdkVersion
36+
3437
versionCode = versionMajor * 1000000 + versionMinor * 10000 + versionBuild
3538
versionName = "$versionMajor.$versionMinor.$versionBuild"
3639
setProperty("archivesBaseName", "GroupTrack-$versionName-$versionCode")
@@ -60,7 +63,6 @@ android {
6063
buildConfigField("String", "PLACE_API_KEY", "\"${p.getProperty("PLACE_API_KEY")}\"")
6164
}
6265
}
63-
6466
signingConfigs {
6567
if (System.getenv("APKSIGN_KEYSTORE") != null) {
6668
create("release") {
@@ -103,12 +105,12 @@ android {
103105
}
104106

105107
compileOptions {
106-
sourceCompatibility = JavaVersion.VERSION_1_8
107-
targetCompatibility = JavaVersion.VERSION_1_8
108+
sourceCompatibility = JavaVersion.VERSION_17
109+
targetCompatibility = JavaVersion.VERSION_17
108110
isCoreLibraryDesugaringEnabled = true
109111
}
110112
kotlinOptions {
111-
jvmTarget = "1.8"
113+
jvmTarget = "17"
112114
}
113115
buildFeatures {
114116
compose = true
@@ -208,10 +210,14 @@ dependencies {
208210
implementation("androidx.core:core-splashscreen:1.0.1")
209211

210212
// Desugaring
211-
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.3")
213+
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
212214

213215
// Gson
214216
implementation("com.google.code.gson:gson:2.10.1")
215217

218+
// Signal Protocol
219+
implementation("org.signal:libsignal-client:0.64.1")
220+
implementation("org.signal:libsignal-android:0.64.1")
221+
216222
implementation(project(":data"))
217223
}

app/src/main/java/com/canopas/yourspace/ui/MainActivity.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ import com.canopas.yourspace.ui.flow.messages.chat.MessagesScreen
4747
import com.canopas.yourspace.ui.flow.messages.thread.ThreadsScreen
4848
import com.canopas.yourspace.ui.flow.onboard.OnboardScreen
4949
import com.canopas.yourspace.ui.flow.permission.EnablePermissionsScreen
50+
import com.canopas.yourspace.ui.flow.pin.enterpin.EnterPinScreen
51+
import com.canopas.yourspace.ui.flow.pin.setpin.SetPinScreen
5052
import com.canopas.yourspace.ui.flow.settings.SettingsScreen
5153
import com.canopas.yourspace.ui.flow.settings.profile.EditProfileScreen
5254
import com.canopas.yourspace.ui.flow.settings.space.SpaceProfileScreen
@@ -124,6 +126,12 @@ fun MainApp(viewModel: MainViewModel) {
124126
slideComposable(AppDestinations.signIn.path) {
125127
SignInMethodsScreen()
126128
}
129+
slideComposable(AppDestinations.setPin.path) {
130+
SetPinScreen()
131+
}
132+
slideComposable(AppDestinations.enterPin.path) {
133+
EnterPinScreen()
134+
}
127135

128136
slideComposable(AppDestinations.home.path) {
129137
navController.currentBackStackEntry

app/src/main/java/com/canopas/yourspace/ui/MainViewModel.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,18 @@ class MainViewModel @Inject constructor(
4242

4343
init {
4444
viewModelScope.launch {
45+
val currentUser = authService.getUser()
46+
val isExistingUser = currentUser != null
47+
val identityKeysMatch = currentUser?.let {
48+
it.identity_key_public?.toBytes().contentEquals(it.identity_key_private?.toBytes())
49+
} ?: false
50+
val showSetPinScreen = isExistingUser && identityKeysMatch
51+
val showEnterPinScreen = showSetPinScreen && userPreferences.getPasskey().isNullOrEmpty()
4552
val initialRoute = when {
4653
!userPreferences.isIntroShown() -> AppDestinations.intro.path
4754
userPreferences.currentUser == null -> AppDestinations.signIn.path
55+
showEnterPinScreen -> AppDestinations.enterPin.path
56+
showSetPinScreen -> AppDestinations.setPin.path
4857
!userPreferences.isOnboardShown() -> AppDestinations.onboard.path
4958
else -> AppDestinations.home.path
5059
}

app/src/main/java/com/canopas/yourspace/ui/component/OtpTextField.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ fun OtpInputField(
3737
pinText: String,
3838
onPinTextChange: (String) -> Unit,
3939
textStyle: TextStyle = AppTheme.appTypography.header2,
40-
digitCount: Int = 6
40+
digitCount: Int = 6,
41+
keyboardType: KeyboardType = KeyboardType.Text
4142
) {
4243
val focusRequester = remember { FocusRequester() }
4344
BoxWithConstraints(
@@ -55,7 +56,7 @@ fun OtpInputField(
5556
onPinTextChange(it)
5657
}
5758
},
58-
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text),
59+
keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
5960
modifier = Modifier.focusRequester(focusRequester),
6061
decorationBox = {
6162
Row(
@@ -67,7 +68,7 @@ fun OtpInputField(
6768
repeat(digitCount) { index ->
6869
OTPDigit(index, pinText, textStyle, focusRequester, width = width)
6970

70-
if (index == 2) {
71+
if (index == 2 && digitCount > 4) {
7172
HorizontalDivider(
7273
modifier = Modifier
7374
.width(16.dp)

app/src/main/java/com/canopas/yourspace/ui/flow/auth/SignInMethodViewModel.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -42,12 +42,12 @@ class SignInMethodViewModel @Inject constructor(
4242
_state.emit(_state.value.copy(showGoogleLoading = true))
4343
try {
4444
val firebaseToken = firebaseAuth.signInWithGoogleAuthCredential(account.idToken)
45-
val isNewUser = authService.verifiedGoogleLogin(
45+
authService.verifiedGoogleLogin(
4646
firebaseAuth.currentUserUid,
4747
firebaseToken,
4848
account
4949
)
50-
onSignUp(isNewUser)
50+
onSignUp()
5151
_state.emit(_state.value.copy(showGoogleLoading = false))
5252
} catch (e: Exception) {
5353
Timber.e(e, "Failed to sign in with google")
@@ -65,7 +65,7 @@ class SignInMethodViewModel @Inject constructor(
6565
_state.emit(_state.value.copy(showAppleLoading = true))
6666
try {
6767
val firebaseToken = authResult.user?.getIdToken(true)?.await()
68-
val isNewUser = authService.verifiedAppleLogin(
68+
authService.verifiedAppleLogin(
6969
firebaseAuth.currentUserUid,
7070
firebaseToken?.token ?: "",
7171
authResult.user ?: run {
@@ -78,7 +78,7 @@ class SignInMethodViewModel @Inject constructor(
7878
return@launch
7979
}
8080
)
81-
onSignUp(isNewUser)
81+
onSignUp()
8282
_state.emit(_state.value.copy(showAppleLoading = false))
8383
} catch (e: Exception) {
8484
Timber.e(e, "Failed to sign in with Apple")
@@ -95,17 +95,22 @@ class SignInMethodViewModel @Inject constructor(
9595
_state.value = _state.value.copy(error = null)
9696
}
9797

98-
private fun onSignUp(isNewUser: Boolean) = viewModelScope.launch(appDispatcher.MAIN) {
99-
if (isNewUser) {
98+
private fun onSignUp() = viewModelScope.launch(appDispatcher.MAIN) {
99+
val currentUser = authService.currentUser ?: return@launch
100+
val showSetPinScreen = currentUser.identity_key_public?.toBytes()
101+
.contentEquals(currentUser.identity_key_private?.toBytes())
102+
val showEnterPinScreen = !showSetPinScreen && userPreferences.getPasskey()
103+
.isNullOrEmpty()
104+
105+
if (showSetPinScreen) {
100106
navigator.navigateTo(
101-
AppDestinations.onboard.path,
107+
AppDestinations.setPin.path,
102108
popUpToRoute = AppDestinations.signIn.path,
103109
inclusive = true
104110
)
105-
} else {
106-
userPreferences.setOnboardShown(true)
111+
} else if (showEnterPinScreen) {
107112
navigator.navigateTo(
108-
AppDestinations.home.path,
113+
AppDestinations.enterPin.path,
109114
popUpToRoute = AppDestinations.signIn.path,
110115
inclusive = true
111116
)

app/src/main/java/com/canopas/yourspace/ui/flow/home/map/component/SelectedUserDetail.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ private fun MemberInfoView(
159159
val state by viewModel.state.collectAsState()
160160

161161
var address by remember { mutableStateOf("") }
162-
val time = timeAgo(location?.created_at ?: 0)
162+
val time = location?.created_at?.let { timeAgo(it) } ?: ""
163163
val userStateText = if (user.noNetwork) {
164164
stringResource(R.string.map_selected_user_item_no_network_state)
165165
} else if (user.locationPermissionDenied) {

app/src/main/java/com/canopas/yourspace/ui/flow/journey/components/LocationHistory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ fun JourneyLocationItem(
113113
.padding(start = 16.dp)
114114
.weight(1f)
115115
) {
116-
val time = getFormattedJourneyTime(location.created_at ?: 0, location.update_at ?: 0)
116+
val time = getFormattedJourneyTime(location.created_at ?: 0, location.updated_at ?: 0)
117117
val distance = getDistanceString(location.route_distance ?: 0.0)
118118

119119
PlaceInfo(title, "$time - $distance")

app/src/main/java/com/canopas/yourspace/ui/flow/journey/detail/UserJourneyDetailScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ private fun JourneyInfo(journey: LocationJourney) {
174174
.padding(start = 16.dp)
175175
.weight(1f)
176176
) {
177-
journey.update_at?.let { getFormattedLocationTime(it) }
177+
journey.updated_at?.let { getFormattedLocationTime(it) }
178178
?.let { PlaceInfo(toAddressStr, it) }
179179
}
180180
}

app/src/main/java/com/canopas/yourspace/ui/flow/journey/detail/UserJourneyDetailViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ class UserJourneyDetailViewModel @Inject constructor(
6161
private fun fetchJourney() = viewModelScope.launch(appDispatcher.IO) {
6262
try {
6363
_state.value = _state.value.copy(isLoading = true)
64-
val journey = journeyService.getLocationJourneyFromId(userId, journeyId)
64+
val journey = journeyService.getLocationJourneyFromId(journeyId)
6565
if (journey == null) {
6666
_state.value = _state.value.copy(
6767
isLoading = false,

app/src/main/java/com/canopas/yourspace/ui/flow/journey/timeline/JourneyTimelineViewModel.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class JourneyTimelineViewModel @Inject constructor(
9696
try {
9797
val from = _state.value.selectedTimeFrom
9898
val to = _state.value.selectedTimeTo
99-
val lastJourneyTime = allJourneys.minOfOrNull { it.update_at!! }
99+
val lastJourneyTime = allJourneys.minOfOrNull { it.updated_at }
100100

101101
val locations = if (loadMore) {
102102
journeyService.getMoreJourneyHistory(userId, lastJourneyTime)
@@ -105,8 +105,8 @@ class JourneyTimelineViewModel @Inject constructor(
105105
}
106106

107107
val filteredLocations = locations.filter {
108-
(it.created_at?.let { created -> created in from..to } ?: false) ||
109-
(it.update_at?.let { updated -> updated in from..to } ?: false)
108+
it.created_at in from..to ||
109+
it.updated_at in from..to
110110
}
111111

112112
val locationJourneys = (allJourneys + filteredLocations).groupByDate()
@@ -158,12 +158,12 @@ class JourneyTimelineViewModel @Inject constructor(
158158

159159
private fun List<LocationJourney>.groupByDate(): Map<Long, List<LocationJourney>> {
160160
val journeys = this.distinctBy { it.id }
161-
.sortedByDescending { it.update_at!! }
161+
.sortedByDescending { it.updated_at }
162162

163163
val groupedItems = mutableMapOf<Long, MutableList<LocationJourney>>()
164164

165165
for (journey in journeys) {
166-
val date = getDayStartTimestamp(journey.created_at!!)
166+
val date = getDayStartTimestamp(journey.created_at)
167167

168168
if (!groupedItems.containsKey(date)) {
169169
groupedItems[date] = mutableListOf()

0 commit comments

Comments
 (0)