Add {otp} placeholder for generating TOTP codes

This commit is contained in:
Artem Chepurnoy 2024-01-07 23:08:38 +02:00
parent d06292c071
commit 458fbcdcc4
No known key found for this signature in database
GPG Key ID: FAC37D0CF674043E
12 changed files with 227 additions and 35 deletions

View File

@ -1,6 +1,5 @@
package com.artemchep.keyguard.common.service.placeholder package com.artemchep.keyguard.common.service.placeholder
import arrow.core.Option
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
// See: // See:
@ -9,6 +8,15 @@ interface Placeholder {
operator fun get(key: String): IO<String?>? operator fun get(key: String): IO<String?>?
interface Factory { interface Factory {
fun createOrNull(
scope: PlaceholderScope,
): Placeholder?
} }
} }
fun List<Placeholder.Factory>.create(
scope: PlaceholderScope,
): List<Placeholder> = this
.mapNotNull { placeholder ->
placeholder.createOrNull(scope)
}

View File

@ -4,6 +4,7 @@ import com.artemchep.keyguard.common.model.DSecret
import kotlinx.datetime.Instant import kotlinx.datetime.Instant
data class PlaceholderScope( data class PlaceholderScope(
val cipher: DSecret,
val now: Instant, val now: Instant,
val cipher: DSecret,
val url: String? = null,
) )

View File

@ -2,34 +2,49 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.io.io
import com.artemchep.keyguard.common.io.map import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.io.toIO
import com.artemchep.keyguard.common.model.DSecret import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.usecase.GetTotpCode import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import com.artemchep.keyguard.common.service.totp.TotpService
import kotlinx.coroutines.Dispatchers
import kotlinx.datetime.Instant
import org.kodein.di.DirectDI
import org.kodein.di.instance
class CipherPlaceholder( class CipherPlaceholder(
// private val getTotpCode: GetTotpCode, private val totpService: TotpService,
private val cipher: DSecret, private val cipher: DSecret,
private val now: Instant,
) : Placeholder { ) : Placeholder {
override fun get( override fun get(
key: String, key: String,
): IO<String?>? = when { ): IO<String?>? = when {
key.equals("uuid", ignoreCase = true) -> key.equals("uuid", ignoreCase = true) ->
cipher.service.remote?.id.let(::io) cipher.service.remote?.id.let(::io)
key.equals("title", ignoreCase = true) -> key.equals("title", ignoreCase = true) ->
cipher.name.let(::io) cipher.name.let(::io)
key.equals("username", ignoreCase = true) -> key.equals("username", ignoreCase = true) ->
cipher.login?.username.let(::io) cipher.login?.username.let(::io)
key.equals("password", ignoreCase = true) -> key.equals("password", ignoreCase = true) ->
cipher.login?.password.let(::io) cipher.login?.password.let(::io)
// key.equals("otp", ignoreCase = true) -> run {
// val token = cipher.login?.totp?.token key.equals("otp", ignoreCase = true) -> run {
// ?: return@run null.let(::io) val token = cipher.login?.totp?.token
// getTotpCode(token) ?: return@run null.let(::io)
// .toIO() ioEffect(Dispatchers.Default) {
// .map { code -> code.code } totpService
// } .generate(
token = token,
timestamp = now,
)
.code
}
}
key.equals("notes", ignoreCase = true) -> key.equals("notes", ignoreCase = true) ->
cipher.notes.let(::io) cipher.notes.let(::io)
// extras // extras
@ -39,7 +54,21 @@ class CipherPlaceholder(
else -> null else -> null
} }
class Factory { class Factory(
private val totpService: TotpService,
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
totpService = directDI.instance(),
)
override fun createOrNull(
scope: PlaceholderScope,
) = CipherPlaceholder(
totpService = totpService,
cipher = scope.cipher,
now = scope.now,
)
} }
} }

View File

@ -3,6 +3,8 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.io.io
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import org.kodein.di.DirectDI
class CommentPlaceholder( class CommentPlaceholder(
) : Placeholder { ) : Placeholder {
@ -15,4 +17,16 @@ class CommentPlaceholder(
// unknown // unknown
else -> null else -> null
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = CommentPlaceholder()
}
} }

View File

@ -4,6 +4,8 @@ import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.model.DSecret import com.artemchep.keyguard.common.model.DSecret
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import org.kodein.di.DirectDI
class CustomPlaceholder( class CustomPlaceholder(
private val cipher: DSecret, private val cipher: DSecret,
@ -26,4 +28,18 @@ class CustomPlaceholder(
// unknown // unknown
else -> null else -> null
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = CustomPlaceholder(
cipher = scope.cipher,
)
}
} }

View File

@ -1,21 +1,19 @@
package com.artemchep.keyguard.common.service.placeholder.impl package com.artemchep.keyguard.common.service.placeholder.impl
import arrow.core.None
import arrow.core.Option
import arrow.core.some
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.io.io
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import kotlinx.datetime.Clock import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.toJavaLocalDateTime import kotlinx.datetime.toJavaLocalDateTime
import kotlinx.datetime.toLocalDateTime import kotlinx.datetime.toLocalDateTime
import org.kodein.di.DirectDI
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
class DateTimePlaceholder( class DateTimePlaceholder(
private val now: Instant,
) : Placeholder { ) : Placeholder {
private val now = Clock.System.now()
private val localDateTime by lazy { private val localDateTime by lazy {
val tz = TimeZone.currentSystemDefault() val tz = TimeZone.currentSystemDefault()
now.toLocalDateTime(tz) now.toLocalDateTime(tz)
@ -105,4 +103,18 @@ class DateTimePlaceholder(
// unknown // unknown
else -> null else -> null
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = DateTimePlaceholder(
now = scope.now,
)
}
} }

View File

@ -3,6 +3,8 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import org.kodein.di.DirectDI
class EnvironmentPlaceholder( class EnvironmentPlaceholder(
) : Placeholder { ) : Placeholder {
@ -22,4 +24,16 @@ class EnvironmentPlaceholder(
null null
} }
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = EnvironmentPlaceholder()
}
} }

View File

@ -3,7 +3,11 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.ioEffect import com.artemchep.keyguard.common.io.ioEffect
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import com.artemchep.keyguard.common.service.placeholder.util.Parser import com.artemchep.keyguard.common.service.placeholder.util.Parser
import com.artemchep.keyguard.common.usecase.GetTotpCode
import org.kodein.di.DirectDI
import org.kodein.di.instance
class TextReplaceRegexPlaceholder( class TextReplaceRegexPlaceholder(
) : Placeholder { ) : Placeholder {
@ -31,4 +35,16 @@ class TextReplaceRegexPlaceholder(
rg.replace(params.value, replacement) rg.replace(params.value, replacement)
} }
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = TextReplaceRegexPlaceholder()
}
} }

View File

@ -3,8 +3,10 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.io.io
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import com.artemchep.keyguard.common.service.placeholder.util.Parser import com.artemchep.keyguard.common.service.placeholder.util.Parser
import io.ktor.util.* import io.ktor.util.*
import org.kodein.di.DirectDI
import java.net.URLDecoder import java.net.URLDecoder
import java.net.URLEncoder import java.net.URLEncoder
import java.util.Locale import java.util.Locale
@ -85,4 +87,16 @@ class TextTransformPlaceholder(
): String { ): String {
return URLDecoder.decode(value, "UTF-8") return URLDecoder.decode(value, "UTF-8")
} }
class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = TextTransformPlaceholder()
}
} }

View File

@ -3,7 +3,9 @@ package com.artemchep.keyguard.common.service.placeholder.impl
import com.artemchep.keyguard.common.io.IO import com.artemchep.keyguard.common.io.IO
import com.artemchep.keyguard.common.io.io import com.artemchep.keyguard.common.io.io
import com.artemchep.keyguard.common.service.placeholder.Placeholder import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import io.ktor.http.* import io.ktor.http.*
import org.kodein.di.DirectDI
class UrlPlaceholder( class UrlPlaceholder(
private val url: String, private val url: String,
@ -90,7 +92,15 @@ class UrlPlaceholder(
else -> null else -> null
} }
class Factory { class Factory(
) : Placeholder.Factory {
constructor(
directDI: DirectDI,
) : this(
)
override fun createOrNull(
scope: PlaceholderScope,
) = scope.url?.let(::UrlPlaceholder)
} }
} }

View File

@ -80,6 +80,9 @@ import com.artemchep.keyguard.common.service.execute.ExecuteCommand
import com.artemchep.keyguard.common.service.extract.LinkInfoExtractor import com.artemchep.keyguard.common.service.extract.LinkInfoExtractor
import com.artemchep.keyguard.common.service.extract.LinkInfoRegistry import com.artemchep.keyguard.common.service.extract.LinkInfoRegistry
import com.artemchep.keyguard.common.service.passkey.PassKeyService import com.artemchep.keyguard.common.service.passkey.PassKeyService
import com.artemchep.keyguard.common.service.placeholder.Placeholder
import com.artemchep.keyguard.common.service.placeholder.PlaceholderScope
import com.artemchep.keyguard.common.service.placeholder.create
import com.artemchep.keyguard.common.service.placeholder.impl.CipherPlaceholder import com.artemchep.keyguard.common.service.placeholder.impl.CipherPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.CommentPlaceholder import com.artemchep.keyguard.common.service.placeholder.impl.CommentPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.CustomPlaceholder import com.artemchep.keyguard.common.service.placeholder.impl.CustomPlaceholder
@ -266,6 +269,7 @@ fun vaultViewScreenState(
addCipherOpenedHistory = instance(), addCipherOpenedHistory = instance(),
getJustDeleteMeByUrl = instance(), getJustDeleteMeByUrl = instance(),
windowCoroutineScope = instance(), windowCoroutineScope = instance(),
placeholderFactories = allInstances(),
linkInfoExtractors = allInstances(), linkInfoExtractors = allInstances(),
mode = mode, mode = mode,
contentColor = contentColor, contentColor = contentColor,
@ -340,6 +344,7 @@ fun vaultViewScreenState(
addCipherOpenedHistory: AddCipherOpenedHistory, addCipherOpenedHistory: AddCipherOpenedHistory,
getJustDeleteMeByUrl: GetJustDeleteMeByUrl, getJustDeleteMeByUrl: GetJustDeleteMeByUrl,
windowCoroutineScope: WindowCoroutineScope, windowCoroutineScope: WindowCoroutineScope,
placeholderFactories: List<Placeholder.Factory>,
linkInfoExtractors: List<LinkInfoExtractor<LinkInfo, LinkInfo>>, linkInfoExtractors: List<LinkInfoExtractor<LinkInfo, LinkInfo>>,
itemId: String, itemId: String,
accountId: String, accountId: String,
@ -490,15 +495,8 @@ fun vaultViewScreenState(
val canEdit = canAddSecret && secretOrNull.canEdit() && !hasCanNotWriteCiphers val canEdit = canAddSecret && secretOrNull.canEdit() && !hasCanNotWriteCiphers
val canDelete = canAddSecret && secretOrNull.canDelete() && !hasCanNotWriteCiphers val canDelete = canAddSecret && secretOrNull.canDelete() && !hasCanNotWriteCiphers
val placeholders = listOf( val now = Clock.System.now()
CipherPlaceholder(secretOrNull),
CommentPlaceholder(),
CustomPlaceholder(secretOrNull),
DateTimePlaceholder(),
TextReplaceRegexPlaceholder(),
TextTransformPlaceholder(),
EnvironmentPlaceholder(),
)
val extractors = LinkInfoRegistry(linkInfoExtractors) val extractors = LinkInfoRegistry(linkInfoExtractors)
val cipherUris = secretOrNull val cipherUris = secretOrNull
.uris .uris
@ -516,16 +514,28 @@ fun vaultViewScreenState(
} }
else -> { else -> {
val newUriPlaceholders = placeholderFactories
.create(
scope = PlaceholderScope(
now = now,
cipher = secretOrNull,
),
)
val newUriString = kotlin.runCatching { val newUriString = kotlin.runCatching {
uri.uri.placeholderFormat(placeholders) uri.uri.placeholderFormat(newUriPlaceholders)
}.getOrElse { uri.uri } }.getOrElse { uri.uri }
val newUri = uri.copy(uri = newUriString) val newUri = uri.copy(uri = newUriString)
// Process URL overrides // Process URL overrides
val urlOverridePlaceholders by lazy { val urlOverridePlaceholders by lazy {
placeholders + listOf( placeholderFactories
UrlPlaceholder(newUriString), .create(
) scope = PlaceholderScope(
now = now,
cipher = secretOrNull,
url = newUriString,
),
)
} }
val urlOverrideList = urlOverrides val urlOverrideList = urlOverrides
.filter { override -> .filter { override ->

View File

@ -28,6 +28,14 @@ import com.artemchep.keyguard.common.service.license.impl.LicenseServiceImpl
import com.artemchep.keyguard.common.service.logging.LogRepository import com.artemchep.keyguard.common.service.logging.LogRepository
import com.artemchep.keyguard.common.service.passkey.PassKeyService import com.artemchep.keyguard.common.service.passkey.PassKeyService
import com.artemchep.keyguard.common.service.passkey.impl.PassKeyServiceImpl import com.artemchep.keyguard.common.service.passkey.impl.PassKeyServiceImpl
import com.artemchep.keyguard.common.service.placeholder.impl.CipherPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.CommentPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.CustomPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.DateTimePlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.EnvironmentPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.TextReplaceRegexPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.TextTransformPlaceholder
import com.artemchep.keyguard.common.service.placeholder.impl.UrlPlaceholder
import com.artemchep.keyguard.common.service.relays.di.emailRelayDiModule import com.artemchep.keyguard.common.service.relays.di.emailRelayDiModule
import com.artemchep.keyguard.common.service.review.ReviewLog import com.artemchep.keyguard.common.service.review.ReviewLog
import com.artemchep.keyguard.common.service.review.impl.ReviewLogImpl import com.artemchep.keyguard.common.service.review.impl.ReviewLogImpl
@ -952,6 +960,46 @@ fun globalModuleJvm() = DI.Module(
) )
} }
import(emailRelayDiModule()) import(emailRelayDiModule())
bindSingleton<CipherPlaceholder.Factory> {
CipherPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<CommentPlaceholder.Factory> {
CommentPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<CustomPlaceholder.Factory> {
CustomPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<DateTimePlaceholder.Factory> {
DateTimePlaceholder.Factory(
directDI = this,
)
}
bindSingleton<EnvironmentPlaceholder.Factory> {
EnvironmentPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<TextReplaceRegexPlaceholder.Factory> {
TextReplaceRegexPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<TextTransformPlaceholder.Factory> {
TextTransformPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<UrlPlaceholder.Factory> {
UrlPlaceholder.Factory(
directDI = this,
)
}
bindSingleton<DeeplinkService> { bindSingleton<DeeplinkService> {
DeeplinkServiceImpl( DeeplinkServiceImpl(
directDI = this, directDI = this,