Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Integrate VeraId in account creation process #43

Merged
merged 27 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
055db87
integrate ASN.1
gnarea Sep 12, 2023
4f1cfd5
format
gnarea Sep 12, 2023
36e0d20
format
gnarea Sep 12, 2023
5b974be
Implement account request message serialisation
gnarea Sep 15, 2023
78c11a7
Merge branch 'main' into production-server-migration
gnarea Sep 15, 2023
5e80775
Replace connection params .der file so we can connect to letro.app
gnarea Sep 15, 2023
2e56ee8
Merge branch 'main' into production-server-migration
gnarea Sep 16, 2023
a86eeab
integrate ASN.1
gnarea Sep 12, 2023
1dc48b5
make signature compatible with nodejs
gnarea Sep 16, 2023
39b8927
fix merge
gnarea Sep 16, 2023
3725bc5
Implement account creation message deserialisation
gnarea Sep 18, 2023
8c63eda
Merge branch 'main' into production-server-migration
gnarea Sep 19, 2023
82ad3d2
Merge branch 'main' into production-server-migration
gnarea Sep 19, 2023
1c81290
Implement `AccountCreation.validate()`
gnarea Sep 19, 2023
a47c1bd
Integrate new account request message
gnarea Sep 21, 2023
a0d80b3
Merge branch 'main' into production-server-migration
gnarea Sep 24, 2023
43183e1
fix merge
gnarea Sep 24, 2023
6ead903
Integrate new account creation message (deserialisation).
gnarea Sep 24, 2023
4a82171
Merge branch 'main' into production-server-migration
gnarea Sep 24, 2023
b21c477
add missing `@Throws`
gnarea Sep 24, 2023
809e234
`String.parseLocale()`: Add test for empty string
gnarea Sep 25, 2023
3ad81c7
Use lazy locale in `RegistrationDomainProvider`
gnarea Sep 25, 2023
31043a7
AccountRepository: Rename updateAccount and pass Account instance
gnarea Sep 25, 2023
cd089cd
AccountRequest: Remove duplicated code
gnarea Sep 25, 2023
9cc2162
Make `parseLocale()` a standalone function
gnarea Sep 25, 2023
7177bdc
Rename `Account.locale` to `normalisedLocale`
gnarea Sep 25, 2023
ccf2513
Merge branch 'main' into production-server-migration
gnarea Sep 25, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ android {
lintConfig file('lint.xml')
warningsAsErrors true
}
testOptions {
unitTests.all {
useJUnitPlatform()
}
}
}

dependencies {
Expand All @@ -65,8 +70,14 @@ dependencies {
implementation 'androidx.core:core-ktx:1.10.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1'

// Awala
// Awala-powered messaging
implementation 'com.github.relaycorp:awala-endpoint-android:1.13.23'
def bouncy_castle_version = "1.70"
implementation "org.bouncycastle:bcprov-jdk15on:$bouncy_castle_version"
implementation "org.bouncycastle:bcpkix-jdk15on:$bouncy_castle_version"

// VeraId
implementation 'tech.relaycorp:veraid:1.10.0'

// Compose
implementation platform('androidx.compose:compose-bom:2023.09.00')
Expand Down Expand Up @@ -99,13 +110,19 @@ dependencies {


// Testing
testImplementation 'junit:junit:4.13.2'
def junitVersion = "5.8.2"
def kotlinCoroutinesVersion = "1.7.3"
testImplementation "org.junit.jupiter:junit-jupiter:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation platform('androidx.compose:compose-bom:2023.06.01')
androidTestImplementation 'androidx.compose.ui:ui-test-junit4'
debugImplementation 'androidx.compose.ui:ui-tooling'
debugImplementation 'androidx.compose.ui:ui-test-manifest'
testImplementation 'io.kotest:kotest-assertions-core:5.7.2'
testImplementation "io.mockk:mockk:1.13.7"
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutinesVersion")
}

// Allow references to generated code
Expand Down
44 changes: 34 additions & 10 deletions app/schemas/tech.relaycorp.letro.storage.LetroDatabase/1.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
"formatVersion": 1,
"database": {
"version": 1,
"identityHash": "d80c574c6882c1c80c0f103b6a6ec580",
"identityHash": "dc43f5c1432dfa4a804e8ab2f5182994",
"entities": [
{
"tableName": "account",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `veraId` TEXT NOT NULL, `isCurrent` INTEGER NOT NULL, `isCreated` INTEGER NOT NULL)",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `veraidId` TEXT NOT NULL, `requestedUserName` TEXT NOT NULL, `locale` TEXT NOT NULL, `isCurrent` INTEGER NOT NULL, `veraidPrivateKey` BLOB NOT NULL, `veraidMemberBundle` BLOB, `isCreated` INTEGER NOT NULL)",
"fields": [
{
"fieldPath": "id",
Expand All @@ -15,8 +15,20 @@
"notNull": true
},
{
"fieldPath": "veraId",
"columnName": "veraId",
"fieldPath": "veraidId",
"columnName": "veraidId",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "requestedUserName",
"columnName": "requestedUserName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "locale",
"columnName": "locale",
"affinity": "TEXT",
"notNull": true
},
Expand All @@ -26,6 +38,18 @@
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "veraidPrivateKey",
"columnName": "veraidPrivateKey",
"affinity": "BLOB",
"notNull": true
},
{
"fieldPath": "veraidMemberBundle",
"columnName": "veraidMemberBundle",
"affinity": "BLOB",
"notNull": false
},
{
"fieldPath": "isCreated",
"columnName": "isCreated",
Expand All @@ -41,20 +65,20 @@
},
"indices": [
{
"name": "index_account_veraId",
"name": "index_account_veraidId",
"unique": true,
"columnNames": [
"veraId"
"veraidId"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_account_veraId` ON `${TABLE_NAME}` (`veraId`)"
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_account_veraidId` ON `${TABLE_NAME}` (`veraidId`)"
}
],
"foreignKeys": []
},
{
"tableName": "contacts",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `alias` TEXT, `contactEndpointId` TEXT, `status` INTEGER NOT NULL, FOREIGN KEY(`ownerVeraId`) REFERENCES `account`(`veraId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `ownerVeraId` TEXT NOT NULL, `contactVeraId` TEXT NOT NULL, `alias` TEXT, `contactEndpointId` TEXT, `status` INTEGER NOT NULL, FOREIGN KEY(`ownerVeraId`) REFERENCES `account`(`veraidId`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "id",
Expand Down Expand Up @@ -119,7 +143,7 @@
"ownerVeraId"
],
"referencedColumns": [
"veraId"
"veraidId"
]
}
]
Expand Down Expand Up @@ -262,7 +286,7 @@
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd80c574c6882c1c80c0f103b6a6ec580')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dc43f5c1432dfa4a804e8ab2f5182994')"
]
}
}
44 changes: 41 additions & 3 deletions app/src/main/java/tech/relaycorp/letro/account/model/Account.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,50 @@ const val TABLE_NAME_ACCOUNT = "account"

@Entity(
tableName = TABLE_NAME_ACCOUNT,
indices = [Index("veraId", unique = true)],
indices = [Index("veraidId", unique = true)],
)
data class Account(
@PrimaryKey(autoGenerate = true)
val id: Long = 0L,
val veraId: String,
val veraidId: String,
gnarea marked this conversation as resolved.
Show resolved Hide resolved
val requestedUserName: String,
val locale: String,
gnarea marked this conversation as resolved.
Show resolved Hide resolved
val isCurrent: Boolean,
// TODO: Encrypt key when integrating VeraId (https://relaycorp.atlassian.net/browse/LTR-55)
val veraidPrivateKey: ByteArray,
val veraidMemberBundle: ByteArray? = null,
val isCreated: Boolean = false,
)
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as Account

if (id != other.id) return false
if (veraidId != other.veraidId) return false
if (requestedUserName != other.requestedUserName) return false
if (locale != other.locale) return false
if (isCurrent != other.isCurrent) return false
if (!veraidPrivateKey.contentEquals(other.veraidPrivateKey)) return false
if (veraidMemberBundle != null) {
if (other.veraidMemberBundle == null) return false
if (!veraidMemberBundle.contentEquals(other.veraidMemberBundle)) return false
} else if (other.veraidMemberBundle != null) return false
if (isCreated != other.isCreated) return false

return true
}

override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + veraidId.hashCode()
result = 31 * result + requestedUserName.hashCode()
result = 31 * result + locale.hashCode()
result = 31 * result + isCurrent.hashCode()
result = 31 * result + veraidPrivateKey.contentHashCode()
result = 31 * result + (veraidMemberBundle?.contentHashCode() ?: 0)
result = 31 * result + isCreated.hashCode()
return result
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ interface AccountDao {
@Update
suspend fun update(entity: Account): Int

@Query("SELECT * FROM $TABLE_NAME_ACCOUNT WHERE veraId=:veraId")
suspend fun getByVeraId(veraId: String): Account?
@Query("SELECT * FROM $TABLE_NAME_ACCOUNT WHERE id=:id")
suspend fun getById(id: Long): Account?

@Query("SELECT * FROM $TABLE_NAME_ACCOUNT WHERE requestedUserName=:requestedUserName AND locale=:locale")
suspend fun getByRequestParams(requestedUserName: String, locale: String): Account?
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,26 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import tech.relaycorp.letro.account.model.Account
import tech.relaycorp.letro.main.MainViewModel
import tech.relaycorp.letro.utils.i18n.normaliseString
import java.security.PrivateKey
import java.util.Locale
import javax.inject.Inject

interface AccountRepository {
val currentAccount: Flow<Account?>
suspend fun createAccount(id: String)
suspend fun createAccount(
requestedUserName: String,
domainName: String,
locale: Locale,
veraidPrivateKey: PrivateKey,
)

suspend fun updateAccountId(id: String, newId: String)
suspend fun getByRequest(
requestedUserName: String,
locale: Locale,
): Account?

suspend fun completeRegistration(id: Long, veraidId: String, veraidBundle: ByteArray)
gnarea marked this conversation as resolved.
Show resolved Hide resolved
}

class AccountRepositoryImpl @Inject constructor(
Expand Down Expand Up @@ -44,20 +57,35 @@ class AccountRepositoryImpl @Inject constructor(
}
}

override suspend fun createAccount(id: String) {
override suspend fun createAccount(
requestedUserName: String,
domainName: String,
locale: Locale,
veraidPrivateKey: PrivateKey,
) {
accountDao.insert(
Account(
veraId = id,
veraidId = "$requestedUserName@$domainName",
requestedUserName = requestedUserName,
locale = locale.normaliseString(),
veraidPrivateKey = veraidPrivateKey.encoded,
isCurrent = true,
),
)
}

override suspend fun updateAccountId(id: String, newId: String) {
accountDao.getByVeraId(id)?.let {
override suspend fun getByRequest(requestedUserName: String, locale: Locale): Account? =
accountDao.getByRequestParams(
requestedUserName = requestedUserName,
locale = locale.normaliseString(),
)

override suspend fun completeRegistration(id: Long, veraidId: String, veraidBundle: ByteArray) {
gnarea marked this conversation as resolved.
Show resolved Hide resolved
accountDao.getById(id)?.let {
accountDao.update(
it.copy(
veraId = newId,
veraidId = veraidId,
veraidMemberBundle = veraidBundle,
isCreated = true,
),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import android.util.Log
import tech.relaycorp.letro.awala.AwalaManagerImpl

sealed class MessageType(val value: String) {
object AccountCreationRequest : MessageType("application/vnd.relaycorp.letro.account-creation-request")
object AccountCreationCompleted : MessageType("application/vnd.relaycorp.letro.account-creation-completed-tmp")
object AccountCreationRequest : MessageType("application/vnd.relaycorp.letro.account-request")
object AccountCreation : MessageType("application/vnd.relaycorp.letro.account-creation")
object AuthorizeReceivingFromServer : MessageType("application/vnd+relaycorp.awala.pda-path")
object ContactPairingRequest : MessageType("application/vnd.relaycorp.letro.pairing-request-tmp")
object ContactPairingMatch : MessageType("application/vnd.relaycorp.letro.pairing-match-tmp")
Expand All @@ -18,7 +18,7 @@ sealed class MessageType(val value: String) {
fun from(type: String): MessageType {
return when (type) {
AccountCreationRequest.value -> AccountCreationRequest
AccountCreationCompleted.value -> AccountCreationCompleted
AccountCreation.value -> AccountCreation
AuthorizeReceivingFromServer.value -> AuthorizeReceivingFromServer
ContactPairingRequest.value -> ContactPairingRequest
ContactPairingMatch.value -> ContactPairingMatch
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ class ContactsViewModel @Inject constructor(
contactsCollectionJob = null
if (account != null) {
contactsCollectionJob = viewModelScope.launch {
contactsRepository.getContacts(account.veraId).collect {
contactsRepository.getContacts(account.veraidId).collect {
_contacts.emit(it.filter { it.status == ContactPairingStatus.COMPLETED })
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ class ManageContactViewModel @Inject constructor(
editingContact = contactToEdit
_uiState.update {
it.copy(
veraId = contactToEdit.contactVeraId,
veraidId = contactToEdit.contactVeraId,
alias = contactToEdit.alias,
isVeraIdInputEnabled = false,
)
Expand Down Expand Up @@ -107,7 +107,7 @@ class ManageContactViewModel @Inject constructor(
val trimmedId = id.trim()
_uiState.update {
it.copy(
veraId = trimmedId,
veraidId = trimmedId,
isSentRequestAgainHintVisible = contacts.any { it.contactVeraId == trimmedId && it.status == ContactPairingStatus.REQUEST_SENT },
)
}
Expand Down Expand Up @@ -140,7 +140,7 @@ class ManageContactViewModel @Inject constructor(
EDIT_CONTACT -> {
updateContact()
viewModelScope.launch {
_onEditContactCompleted.emit(uiState.value.veraId)
_onEditContactCompleted.emit(uiState.value.veraidId)
}
}
else -> throw IllegalStateException("Unknown screen type: $screenType")
Expand Down Expand Up @@ -195,7 +195,7 @@ class ManageContactViewModel @Inject constructor(
contactsRepository.addNewContact(
contact = Contact(
ownerVeraId = currentAccountId,
contactVeraId = uiState.value.veraId,
contactVeraId = uiState.value.veraidId,
alias = uiState.value.alias?.nullIfBlankOrEmpty(),
status = ContactPairingStatus.REQUEST_SENT,
),
Expand Down Expand Up @@ -230,7 +230,7 @@ class ManageContactViewModel @Inject constructor(

data class PairWithOthersUiState(
val manageContactTexts: ManageContactTexts,
val veraId: String = "",
val veraidId: String = "",
val alias: String? = null,
val isActionButtonEnabled: Boolean = false,
val isSentRequestAgainHintVisible: Boolean = false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const val TABLE_NAME_CONTACTS = "contacts"
foreignKeys = [
ForeignKey(
entity = Account::class,
parentColumns = ["veraId"],
parentColumns = ["veraidId"],
childColumns = ["ownerVeraId"],
onDelete = ForeignKey.CASCADE,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class ContactsRepositoryImpl @Inject constructor(
override fun saveRequestWasOnceSent() {
val currentAccount = currentAccount ?: return
scope.launch {
preferences.putBoolean(getContactRequestHasEverBeenSentKey(currentAccount.veraId), true)
preferences.putBoolean(getContactRequestHasEverBeenSentKey(currentAccount.veraidId), true)
updateContactsState(currentAccount)
}
}
Expand All @@ -137,9 +137,9 @@ class ContactsRepositoryImpl @Inject constructor(
val isPairedContactExist = contacts
.value
.any {
it.ownerVeraId == account.veraId && it.status == ContactPairingStatus.COMPLETED
it.ownerVeraId == account.veraidId && it.status == ContactPairingStatus.COMPLETED
}
val isPairRequestWasEverSent = preferences.getBoolean(getContactRequestHasEverBeenSentKey(account.veraId), false)
val isPairRequestWasEverSent = preferences.getBoolean(getContactRequestHasEverBeenSentKey(account.veraidId), false)
_contactsState.emit(
ContactsState(
isPairedContactExist = isPairedContactExist,
Expand All @@ -149,8 +149,8 @@ class ContactsRepositoryImpl @Inject constructor(
}

private fun getContactRequestHasEverBeenSentKey(
veraId: String,
) = "${KEY_CONTACT_REQUEST_HAS_EVER_BEEN_SENT_PREFIX}$veraId"
veraidId: String,
) = "${KEY_CONTACT_REQUEST_HAS_EVER_BEEN_SENT_PREFIX}$veraidId"

private companion object {
private const val TAG = "ContactsRepository"
Expand Down
Loading
Loading