Integrate Android biometrics with the shared Rust lease (#98)
This commit is contained in:
1 parent
5c933a99a3
commit
647dc1bb4b
21 files changed
+1473
-118
No files matched your search
@@ -0,0 +1,231 @@
|
||||
package de.rfc1437.ironstorage
|
||||
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import de.rfc1437.ironstorage.core.AndroidKeystore
|
||||
import de.rfc1437.ironstorage.core.AuthenticationFailureKind
|
||||
import de.rfc1437.ironstorage.core.KeystoreAuthorization
|
||||
import de.rfc1437.ironstorage.core.KeystoreCiphertext
|
||||
import de.rfc1437.ironstorage.core.KeystoreKeySpecification
|
||||
import de.rfc1437.ironstorage.core.NativeAuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.NativeFixtureShellRepository
|
||||
import de.rfc1437.ironstorage.core.NativeOnboardingRepository
|
||||
import de.rfc1437.ironstorage.core.NativePrivateSession
|
||||
import de.rfc1437.ironstorage.core.PrivateDirectories
|
||||
import de.rfc1437.ironstorage.features.AuthenticationControls
|
||||
import de.rfc1437.ironstorage.platform.AndroidKeystoreAdapter
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Assume.assumeTrue
|
||||
import org.junit.Test
|
||||
|
||||
/** Real OS/Rust integration using public fixtures and only production Kotlin facades. */
|
||||
class AuthenticationLeaseTest {
|
||||
private val instrumentation = InstrumentationRegistry.getInstrumentation()
|
||||
private val context = instrumentation.targetContext
|
||||
|
||||
@Test fun strongBiometricLeaseCancellationBackgroundAndManualRecovery() = runBlocking {
|
||||
assumeTrue(InstrumentationRegistry.getArguments().getString("biometricLease") == "true")
|
||||
val os = AndroidKeystoreAdapter(context)
|
||||
assertTrue(
|
||||
"Enroll a synthetic strong fingerprint for this opt-in test",
|
||||
os.deviceState().strongBiometric
|
||||
)
|
||||
val id = "auth-${UUID.randomUUID()}"
|
||||
val roots = listOf(context.filesDir, context.noBackupFilesDir, context.cacheDir).map {
|
||||
File(it, id).apply { mkdirs() }.canonicalPath
|
||||
}
|
||||
val isolated = FixtureKeystore(os, id)
|
||||
val session =
|
||||
NativePrivateSession(context, {
|
||||
PrivateDirectories(roots[0], roots[1], roots[2])
|
||||
}, isolated)
|
||||
// Select the isolated Rust context before any activity creates its default runtime.
|
||||
withContext(Dispatchers.IO) {
|
||||
session.initialize()
|
||||
}
|
||||
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
val shell = NativeFixtureShellRepository(session, scope)
|
||||
val setup = NativeOnboardingRepository(session, scope, shell)
|
||||
val authentication = NativeAuthenticationRepository(session, scope, Dispatchers.IO)
|
||||
val password = "public-test-passphrase"
|
||||
suspend fun settled() = withTimeout(180000) {
|
||||
authentication.state.first { !it.busy }
|
||||
}
|
||||
suspend fun request(action: () -> Unit) {
|
||||
withContext(Dispatchers.Main.immediate) { action() }
|
||||
}
|
||||
val stage = MutableStateFlow("Authentication fixture")
|
||||
val step = AtomicReference<CountDownLatch?>()
|
||||
fun show(value: String) {
|
||||
android.util.Log.i("IronStorage98", value)
|
||||
stage.value = value
|
||||
}
|
||||
fun awaitStep(value: String): Boolean {
|
||||
val latch = CountDownLatch(1)
|
||||
step.set(latch)
|
||||
show(value)
|
||||
return try {
|
||||
latch.await(180, TimeUnit.SECONDS)
|
||||
} finally {
|
||||
step.set(null)
|
||||
}
|
||||
}
|
||||
ActivityScenario.launch(MainActivity::class.java).use { scenario ->
|
||||
lateinit var host: MainActivity
|
||||
scenario.onActivity {
|
||||
host = it
|
||||
os.attach(it)
|
||||
it.setContent {
|
||||
MaterialTheme {
|
||||
Column {
|
||||
Text(stage.collectAsState().value)
|
||||
TextButton(onClick = {
|
||||
step.get()?.countDown()
|
||||
}) { Text("Continue fixture") }
|
||||
AuthenticationControls(authentication)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
try {
|
||||
withTimeout(20000) { setup.setup.first { !it.busy } }
|
||||
request { setup.generate("Authentication fixture", password) }
|
||||
val generated = withTimeout(20000) {
|
||||
setup.setup.first {
|
||||
!it.busy &&
|
||||
it.selectedKey.isNotEmpty()
|
||||
}
|
||||
}
|
||||
request { setup.createLocal(generated.selectedKey, password) }
|
||||
withTimeout(20000) { setup.setup.first { !it.busy && it.configured } }
|
||||
show("98:enroll")
|
||||
request { authentication.enroll(password) }
|
||||
assertTrue(settled().biometricEnabled)
|
||||
request { authentication.lock() }
|
||||
assertFalse(authentication.state.value.unlocked)
|
||||
show("98:unlock")
|
||||
request { authentication.unlock() }
|
||||
assertTrue(settled().unlocked)
|
||||
request { authentication.reload() }
|
||||
assertTrue(settled().unlocked)
|
||||
request { authentication.lock() }
|
||||
show("98:cancel")
|
||||
request { authentication.unlock() }
|
||||
assertEquals(AuthenticationFailureKind.Cancelled, settled().failure?.kind)
|
||||
assertFalse(authentication.state.value.unlocked)
|
||||
assertTrue(awaitStep("98:background-start"))
|
||||
request { authentication.unlock() }
|
||||
instrumentation.waitForIdleSync()
|
||||
scenario.moveToState(Lifecycle.State.CREATED)
|
||||
request { authentication.lock() }
|
||||
assertFalse(settled().unlocked)
|
||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||
request { authentication.disable() }
|
||||
assertFalse(settled().biometricEnabled)
|
||||
show("98:reenroll")
|
||||
request { authentication.enroll(password) }
|
||||
assertTrue(settled().unlocked)
|
||||
request { authentication.lock() }
|
||||
assertTrue(awaitStep("98:invalidate-now"))
|
||||
request { authentication.unlock() }
|
||||
val invalid = settled()
|
||||
assertFalse(invalid.unlocked)
|
||||
assertTrue(
|
||||
invalid.failure?.kind == AuthenticationFailureKind.PassphraseRequired ||
|
||||
invalid.failure?.kind == AuthenticationFailureKind.BiometryUnavailable
|
||||
)
|
||||
request { authentication.disable() }
|
||||
assertFalse(settled().biometricEnabled)
|
||||
show("98:recover")
|
||||
request { authentication.enroll(password) }
|
||||
assertTrue(settled().unlocked)
|
||||
request { authentication.lock() }
|
||||
assertTrue(awaitStep("98:remove-lock"))
|
||||
request { authentication.unlock() }
|
||||
val removed = settled()
|
||||
assertFalse(removed.unlocked)
|
||||
assertTrue(
|
||||
removed.failure?.kind == AuthenticationFailureKind.PassphraseRequired ||
|
||||
removed.failure?.kind == AuthenticationFailureKind.BiometryUnavailable
|
||||
)
|
||||
request { authentication.disable() }
|
||||
assertFalse(settled().biometricEnabled)
|
||||
request { authentication.enroll(password) }
|
||||
val unsupported = settled()
|
||||
assertFalse(unsupported.unlocked)
|
||||
assertFalse(unsupported.biometricEnabled)
|
||||
assertEquals(
|
||||
AuthenticationFailureKind.BiometryUnavailable,
|
||||
unsupported.failure?.kind
|
||||
)
|
||||
assertTrue(awaitStep("98:restore-lock"))
|
||||
show("98:recover-lock")
|
||||
request { authentication.enroll(password) }
|
||||
assertTrue(settled().unlocked)
|
||||
request { authentication.disable() }
|
||||
assertFalse(settled().unlocked)
|
||||
show("98:complete")
|
||||
} finally {
|
||||
authentication.lock()
|
||||
os.cancelPending()
|
||||
os.detach(host)
|
||||
scope.cancel()
|
||||
isolated.aliases().forEach { isolated.deleteKey(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Only OS aliases are namespaced so orphan cleanup cannot touch the production store. */
|
||||
private class FixtureKeystore(private val os: AndroidKeystore, id: String) :
|
||||
AndroidKeystore by os {
|
||||
private val prefix = "ironstorage.issue98.fixture.$id."
|
||||
override fun createKey(specification: KeystoreKeySpecification) = os.createKey(
|
||||
KeystoreKeySpecification(
|
||||
prefix + specification.alias,
|
||||
specification.authorization,
|
||||
specification.unlockedDevice,
|
||||
specification.invalidateOnEnrollment,
|
||||
specification.strongbox
|
||||
)
|
||||
)
|
||||
override fun encrypt(
|
||||
alias: String,
|
||||
authorization: KeystoreAuthorization,
|
||||
aad: ByteArray,
|
||||
value: ByteArray
|
||||
): KeystoreCiphertext = os.encrypt(prefix + alias, authorization, aad, value)
|
||||
override fun decrypt(
|
||||
alias: String,
|
||||
authorization: KeystoreAuthorization,
|
||||
aad: ByteArray,
|
||||
value: KeystoreCiphertext
|
||||
): ByteArray = os.decrypt(prefix + alias, authorization, aad, value)
|
||||
override fun deleteKey(alias: String) = os.deleteKey(prefix + alias)
|
||||
override fun aliases(): List<String> = os.aliases().filter {
|
||||
it.startsWith(prefix)
|
||||
}.map { it.removePrefix(prefix) }
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
package de.rfc1437.ironstorage
|
||||
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.ui.test.assertIsDisplayed
|
||||
import androidx.compose.ui.test.assertIsNotEnabled
|
||||
import androidx.compose.ui.test.junit4.v2.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import de.rfc1437.ironstorage.core.AuthenticationFailure
|
||||
import de.rfc1437.ironstorage.core.AuthenticationFailureKind
|
||||
import de.rfc1437.ironstorage.core.AuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.AuthenticationState
|
||||
import de.rfc1437.ironstorage.features.AuthenticationControls
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AuthenticationScreenTest {
|
||||
@get:Rule val compose = createAndroidComposeRule<MainActivity>()
|
||||
|
||||
@Test fun enrollmentRequiresExplicitSubmissionAndTypedSuccessBeforeDisplayingEnabled() {
|
||||
val repository = Fixture()
|
||||
compose.activity.runOnUiThread {
|
||||
compose.activity.setContent { MaterialTheme { AuthenticationControls(repository) } }
|
||||
}
|
||||
compose.onNodeWithText("Biometric unlock disabled").assertIsDisplayed()
|
||||
compose.onNodeWithText("Enable biometric unlock").performClick()
|
||||
compose.onNodeWithText("Continue").assertIsNotEnabled()
|
||||
compose.onNodeWithText("GPG passphrase").performTextInput("public-test-passphrase")
|
||||
compose.onNodeWithText("Continue").performClick()
|
||||
assertEquals("public-test-passphrase", repository.submitted)
|
||||
compose.onNodeWithText("Biometric unlock disabled").assertIsDisplayed()
|
||||
compose.runOnIdle {
|
||||
repository.state.value = AuthenticationState(
|
||||
failure = AuthenticationFailure(
|
||||
AuthenticationFailureKind.Cancelled,
|
||||
"Authentication cancelled",
|
||||
"No protected content was unlocked."
|
||||
)
|
||||
)
|
||||
}
|
||||
compose.onNodeWithText("Biometric unlock disabled").assertIsDisplayed()
|
||||
compose.runOnIdle { repository.state.value = AuthenticationState(true, true, 30u) }
|
||||
compose.onNodeWithText("Biometric unlock enabled").assertIsDisplayed()
|
||||
compose.onNodeWithText("Lock").performClick()
|
||||
assertTrue(repository.locked)
|
||||
compose.onNodeWithText("Unlock").performClick()
|
||||
assertEquals(1, repository.unlocks)
|
||||
compose.onNodeWithText("Disable biometric unlock").performClick()
|
||||
assertEquals(1, repository.disabled)
|
||||
compose.onNodeWithText("Biometric unlock disabled").assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Test fun cancellationBackgroundAndRemountNeverRestoreThePassphrase() {
|
||||
val repository = Fixture()
|
||||
fun mount() = compose.activity.runOnUiThread {
|
||||
compose.activity.setContent { MaterialTheme { AuthenticationControls(repository) } }
|
||||
}
|
||||
mount()
|
||||
compose.onNodeWithText("Enable biometric unlock").performClick()
|
||||
compose.onNodeWithText("GPG passphrase").performTextInput("public-test-passphrase")
|
||||
compose.onNodeWithText("Cancel").performClick()
|
||||
compose.onNodeWithText("Enable biometric unlock").performClick()
|
||||
compose.onNodeWithText("Continue").assertIsNotEnabled()
|
||||
compose.onNodeWithText("GPG passphrase").performTextInput("another-public-fixture")
|
||||
compose.activityRule.scenario.moveToState(Lifecycle.State.CREATED)
|
||||
compose.activityRule.scenario.moveToState(Lifecycle.State.RESUMED)
|
||||
compose.onNodeWithText("GPG passphrase").assertDoesNotExist()
|
||||
compose.onNodeWithText("Enable biometric unlock").performClick()
|
||||
compose.onNodeWithText("Continue").assertIsNotEnabled()
|
||||
compose.activity.runOnUiThread { compose.activity.setContent { MaterialTheme { } } }
|
||||
mount()
|
||||
compose.onNodeWithText("Enable biometric unlock").performClick()
|
||||
compose.onNodeWithText("Continue").assertIsNotEnabled()
|
||||
assertEquals(null, repository.submitted)
|
||||
assertTrue(repository.cancelled > 0)
|
||||
}
|
||||
|
||||
private class Fixture : AuthenticationRepository {
|
||||
override val state = MutableStateFlow(AuthenticationState())
|
||||
var submitted: String? = null
|
||||
var locked = false
|
||||
var unlocks = 0
|
||||
var disabled = 0
|
||||
var cancelled = 0
|
||||
override fun reload() = Unit
|
||||
override fun enroll(passphrase: String) {
|
||||
submitted = passphrase
|
||||
}
|
||||
override fun unlock() {
|
||||
unlocks++
|
||||
}
|
||||
override fun disable() {
|
||||
disabled++
|
||||
state.value = AuthenticationState()
|
||||
}
|
||||
override fun lock() {
|
||||
locked = true
|
||||
state.value = state.value.copy(unlocked = false)
|
||||
}
|
||||
override fun cancel() {
|
||||
cancelled++
|
||||
state.value = state.value.copy(unlocked = false)
|
||||
}
|
||||
override fun touch() = Unit
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,10 @@ import androidx.compose.ui.test.performScrollTo
|
||||
import androidx.compose.ui.test.performSemanticsAction
|
||||
import androidx.compose.ui.test.performTextInput
|
||||
import androidx.compose.ui.unit.Density
|
||||
import de.rfc1437.ironstorage.core.AuthenticationFailure
|
||||
import de.rfc1437.ironstorage.core.AuthenticationFailureKind
|
||||
import de.rfc1437.ironstorage.core.AuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.AuthenticationState
|
||||
import de.rfc1437.ironstorage.core.PasswordField
|
||||
import de.rfc1437.ironstorage.core.PasswordKind
|
||||
import de.rfc1437.ironstorage.core.PasswordRepository
|
||||
@@ -54,7 +58,7 @@ class PasswordScreenTest {
|
||||
compose.activity.runOnUiThread {
|
||||
compose.activity.setContent {
|
||||
inputMode = LocalInputModeManager.current
|
||||
MaterialTheme { PasswordScreen(repository) }
|
||||
MaterialTheme { PasswordScreen(repository, repository.authentication) }
|
||||
}
|
||||
}
|
||||
compose.runOnIdle { assertTrue(inputMode.requestInputMode(InputMode.Keyboard)) }
|
||||
@@ -84,12 +88,19 @@ class PasswordScreenTest {
|
||||
compose.onAllNodesWithText("Actions")[0].performClick()
|
||||
compose.onNodeWithText("Open").performClick()
|
||||
compose.onNodeWithText("Unlock").assertIsDisplayed()
|
||||
compose.onNodeWithText("Unlock").performClick()
|
||||
compose.onNodeWithText("GPG passphrase").performTextInput("incorrect")
|
||||
compose.onNodeWithText("Unlock").performClick()
|
||||
compose.onNodeWithText("Continue").performClick()
|
||||
compose.onNodeWithText("Incorrect passphrase. Retry unlocking.").assertIsDisplayed()
|
||||
compose.onNodeWithText("Use GPG passphrase").performClick()
|
||||
compose.onNodeWithText("GPG passphrase").performTextInput("public-test-passphrase")
|
||||
compose.onNodeWithText("Unlock").performClick()
|
||||
compose.onNodeWithText("Continue").performClick()
|
||||
compose.onNodeWithText("Fixture account").assertIsDisplayed()
|
||||
// Revocation must mask even a page result that has not been cleared yet.
|
||||
compose.runOnIdle { repository.lease.value = AuthenticationState() }
|
||||
compose.onNodeWithText("Fixture account").assertDoesNotExist()
|
||||
compose.onNodeWithText("Unlock").assertIsDisplayed()
|
||||
compose.runOnIdle { repository.lease.value = AuthenticationState(unlocked = true) }
|
||||
compose.onNodeWithText("Lock").performClick()
|
||||
compose.onNodeWithText("Unlock").assertIsDisplayed()
|
||||
repository.replaceOnRefresh = true
|
||||
@@ -117,7 +128,7 @@ class PasswordScreenTest {
|
||||
compose.activity.setContent {
|
||||
val density = LocalDensity.current
|
||||
CompositionLocalProvider(LocalDensity provides Density(density.density, 2f)) {
|
||||
MaterialTheme { PasswordScreen(repository) }
|
||||
MaterialTheme { PasswordScreen(repository, repository.authentication) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,6 +162,19 @@ class PasswordScreenTest {
|
||||
)
|
||||
override val state =
|
||||
MutableStateFlow(PasswordState(revision = "root", rows = listOf(folder)))
|
||||
val lease = MutableStateFlow(AuthenticationState())
|
||||
val authentication = object : AuthenticationRepository {
|
||||
override val state = lease
|
||||
override fun reload() = Unit
|
||||
override fun unlock() = error("Unexpected direct unlock")
|
||||
override fun enroll(passphrase: String) = error("Unexpected enrollment")
|
||||
override fun disable() = error("Unexpected disable")
|
||||
override fun lock() {
|
||||
state.value = state.value.copy(unlocked = false)
|
||||
}
|
||||
override fun cancel() = lock()
|
||||
override fun touch() = Unit
|
||||
}
|
||||
var lastId: String? = null
|
||||
var replaceOnRefresh = false
|
||||
override fun restore(path: String, selected: String?, revision: String?) = Unit
|
||||
@@ -190,10 +214,21 @@ class PasswordScreenTest {
|
||||
}
|
||||
}
|
||||
override fun unlock(passphrase: String) {
|
||||
if (passphrase.isEmpty()) {
|
||||
lease.value = AuthenticationState(
|
||||
failure = AuthenticationFailure(
|
||||
AuthenticationFailureKind.PassphraseRequired,
|
||||
"Passphrase Required",
|
||||
"Enter the passphrase."
|
||||
)
|
||||
)
|
||||
return
|
||||
}
|
||||
if (passphrase != "public-test-passphrase") {
|
||||
state.value = state.value.copy(error = "Incorrect passphrase. Retry unlocking.")
|
||||
return
|
||||
}
|
||||
lease.value = AuthenticationState(unlocked = true)
|
||||
assertEquals("public-test-passphrase", passphrase)
|
||||
state.value =
|
||||
state.value.copy(
|
||||
|
||||
@@ -7,6 +7,8 @@ import androidx.activity.viewModels
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import de.rfc1437.ironstorage.core.AuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.PasswordRepository
|
||||
import de.rfc1437.ironstorage.core.ShellRepository
|
||||
import de.rfc1437.ironstorage.features.SetupFormViewModel
|
||||
import de.rfc1437.ironstorage.features.ShellViewModel
|
||||
@@ -19,10 +21,24 @@ class MainActivity : FragmentActivity() {
|
||||
|
||||
@Inject lateinit var repository: ShellRepository
|
||||
|
||||
@Inject lateinit var authentication: AuthenticationRepository
|
||||
|
||||
@Inject lateinit var passwords: PasswordRepository
|
||||
|
||||
private val setupForm: SetupFormViewModel by viewModels()
|
||||
|
||||
private val viewModel: ShellViewModel by viewModels()
|
||||
|
||||
override fun onStop() {
|
||||
passwords.lock()
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
override fun onUserInteraction() {
|
||||
authentication.touch()
|
||||
super.onUserInteraction()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
keystore.detach(this)
|
||||
super.onDestroy()
|
||||
|
||||
@@ -6,7 +6,9 @@ import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import de.rfc1437.ironstorage.core.AuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.HomeRepository
|
||||
import de.rfc1437.ironstorage.core.NativeAuthenticationRepository
|
||||
import de.rfc1437.ironstorage.core.NativeFixtureShellRepository
|
||||
import de.rfc1437.ironstorage.core.NativeHomeRepository
|
||||
import de.rfc1437.ironstorage.core.NativeOnboardingRepository
|
||||
@@ -39,12 +41,29 @@ object ShellModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun passwordRepository(session: NativePrivateSession): PasswordRepository =
|
||||
NativePasswordRepository(
|
||||
fun passwordRepository(
|
||||
session: NativePrivateSession,
|
||||
authentication: NativeAuthenticationRepository
|
||||
): PasswordRepository = NativePasswordRepository(
|
||||
session,
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate),
|
||||
authentication,
|
||||
Dispatchers.IO
|
||||
)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun nativeAuthentication(session: NativePrivateSession): NativeAuthenticationRepository =
|
||||
NativeAuthenticationRepository(
|
||||
session,
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
|
||||
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate),
|
||||
Dispatchers.IO
|
||||
)
|
||||
|
||||
@Provides
|
||||
fun authentication(repository: NativeAuthenticationRepository): AuthenticationRepository =
|
||||
repository
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun privateSession(
|
||||
|
||||
Reference in new issue
Block a user