feature: Add OTP code by scanning Google Authenticator single export code #556
This commit is contained in:
parent
5e074d9586
commit
3714670287
|
@ -119,6 +119,7 @@ kotlin {
|
||||||
api(libs.kotlinx.datetime)
|
api(libs.kotlinx.datetime)
|
||||||
api(libs.kotlinx.serialization.json)
|
api(libs.kotlinx.serialization.json)
|
||||||
api(libs.kotlinx.serialization.cbor)
|
api(libs.kotlinx.serialization.cbor)
|
||||||
|
api(libs.kotlinx.serialization.protobuf)
|
||||||
api(libs.arrow.arrow.core)
|
api(libs.arrow.arrow.core)
|
||||||
api(libs.arrow.arrow.optics)
|
api(libs.arrow.arrow.optics)
|
||||||
api(libs.kodein.kodein.di)
|
api(libs.kodein.kodein.di)
|
||||||
|
|
|
@ -4,4 +4,5 @@ enum class CryptoHashAlgorithm {
|
||||||
SHA_1,
|
SHA_1,
|
||||||
SHA_256,
|
SHA_256,
|
||||||
SHA_512,
|
SHA_512,
|
||||||
|
MD5,
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,12 +157,8 @@ private fun parseTotpAuth(
|
||||||
keyBase32 = secretParam
|
keyBase32 = secretParam
|
||||||
}
|
}
|
||||||
params["algorithm"]?.also { algorithmParam ->
|
params["algorithm"]?.also { algorithmParam ->
|
||||||
val alg = when (algorithmParam.lowercase()) {
|
val alg = parseHashAlgorithmOrNull(algorithmParam)
|
||||||
"sha1" -> CryptoHashAlgorithm.SHA_1
|
?: return@also
|
||||||
"sha256" -> CryptoHashAlgorithm.SHA_256
|
|
||||||
"sha512" -> CryptoHashAlgorithm.SHA_512
|
|
||||||
else -> return@also
|
|
||||||
}
|
|
||||||
builder.algorithm = alg
|
builder.algorithm = alg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,12 +197,8 @@ private fun parseHotpAuth(
|
||||||
keyBase32 = secretParam
|
keyBase32 = secretParam
|
||||||
}
|
}
|
||||||
params["algorithm"]?.also { algorithmParam ->
|
params["algorithm"]?.also { algorithmParam ->
|
||||||
val alg = when (algorithmParam.lowercase()) {
|
val alg = parseHashAlgorithmOrNull(algorithmParam)
|
||||||
"sha1" -> CryptoHashAlgorithm.SHA_1
|
?: return@also
|
||||||
"sha256" -> CryptoHashAlgorithm.SHA_256
|
|
||||||
"sha512" -> CryptoHashAlgorithm.SHA_512
|
|
||||||
else -> return@also
|
|
||||||
}
|
|
||||||
builder.algorithm = alg
|
builder.algorithm = alg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,6 +212,14 @@ private fun parseHotpAuth(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun parseHashAlgorithmOrNull(name: String) = when (name.lowercase()) {
|
||||||
|
"sha1" -> CryptoHashAlgorithm.SHA_1
|
||||||
|
"sha256" -> CryptoHashAlgorithm.SHA_256
|
||||||
|
"sha512" -> CryptoHashAlgorithm.SHA_512
|
||||||
|
"md5" -> CryptoHashAlgorithm.MD5
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
private fun parseOtpSteam(
|
private fun parseOtpSteam(
|
||||||
url: String,
|
url: String,
|
||||||
): Either<Throwable, TotpToken.SteamAuth> = Either.catch {
|
): Either<Throwable, TotpToken.SteamAuth> = Either.catch {
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
|
||||||
|
interface OtpMigrationService {
|
||||||
|
/**
|
||||||
|
* Returns a migrations service to use, or
|
||||||
|
* `null` if there no tool to use.
|
||||||
|
*/
|
||||||
|
fun handler(uri: String): OtpMigrationService?
|
||||||
|
|
||||||
|
fun convert(uri: String): Either<Throwable, String>
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator.impl
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import arrow.core.flatMap
|
||||||
|
import arrow.core.flatten
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationConst
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.util.build
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationParser
|
||||||
|
import com.artemchep.keyguard.common.service.text.Base32Service
|
||||||
|
import org.kodein.di.DirectDI
|
||||||
|
import org.kodein.di.instance
|
||||||
|
|
||||||
|
class OtpMigrationServiceImpl(
|
||||||
|
private val base32Service: Base32Service,
|
||||||
|
private val otpMigrationParser: OtpMigrationParser,
|
||||||
|
) : OtpMigrationService {
|
||||||
|
constructor(
|
||||||
|
directDI: DirectDI,
|
||||||
|
) : this(
|
||||||
|
base32Service = directDI.instance(),
|
||||||
|
otpMigrationParser = directDI.instance(),
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun handler(uri: String): OtpMigrationService? =
|
||||||
|
this.takeIf { uri.startsWith(OtpMigrationConst.PREFIX) }
|
||||||
|
|
||||||
|
override fun convert(
|
||||||
|
uri: String,
|
||||||
|
): Either<Throwable, String> = Either.catch {
|
||||||
|
otpMigrationParser.parse(uri)
|
||||||
|
.flatMap {
|
||||||
|
it.otpParameters
|
||||||
|
.first()
|
||||||
|
.build(
|
||||||
|
base32Service = base32Service,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.protobuf.ProtoNumber
|
||||||
|
|
||||||
|
/*
|
||||||
|
Schema is available here
|
||||||
|
https://github.com/dim13/otpauth/blob/master/migration/migration.proto
|
||||||
|
under ISC License 2020 Dimitri Sokolyuk <demon@dim13.org>
|
||||||
|
|
||||||
|
message Payload {
|
||||||
|
message OtpParameters {
|
||||||
|
enum Algorithm {
|
||||||
|
ALGORITHM_UNSPECIFIED = 0;
|
||||||
|
ALGORITHM_SHA1 = 1;
|
||||||
|
ALGORITHM_SHA256 = 2;
|
||||||
|
ALGORITHM_SHA512 = 3;
|
||||||
|
ALGORITHM_MD5 = 4;
|
||||||
|
}
|
||||||
|
enum DigitCount {
|
||||||
|
DIGIT_COUNT_UNSPECIFIED = 0;
|
||||||
|
DIGIT_COUNT_SIX = 1;
|
||||||
|
DIGIT_COUNT_EIGHT = 2;
|
||||||
|
}
|
||||||
|
enum OtpType {
|
||||||
|
OTP_TYPE_UNSPECIFIED = 0;
|
||||||
|
OTP_TYPE_HOTP = 1;
|
||||||
|
OTP_TYPE_TOTP = 2;
|
||||||
|
}
|
||||||
|
bytes secret = 1;
|
||||||
|
string name = 2;
|
||||||
|
string issuer = 3;
|
||||||
|
Algorithm algorithm = 4;
|
||||||
|
DigitCount digits = 5;
|
||||||
|
OtpType type = 6;
|
||||||
|
uint64 counter = 7;
|
||||||
|
}
|
||||||
|
repeated OtpParameters otp_parameters = 1;
|
||||||
|
int32 version = 2;
|
||||||
|
int32 batch_size = 3;
|
||||||
|
int32 batch_index = 4;
|
||||||
|
int32 batch_id = 5;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@Serializable
|
||||||
|
data class OtpAuthMigrationData(
|
||||||
|
@ProtoNumber(1)
|
||||||
|
@SerialName("otp_parameters")
|
||||||
|
val otpParameters: List<OtpParameters>,
|
||||||
|
@ProtoNumber(2)
|
||||||
|
val version: Int = 0,
|
||||||
|
@ProtoNumber(3)
|
||||||
|
@SerialName("batch_size")
|
||||||
|
val batchSize: Int = 0,
|
||||||
|
@ProtoNumber(4)
|
||||||
|
@SerialName("batch_index")
|
||||||
|
val batchIndex: Int = 0,
|
||||||
|
@ProtoNumber(5)
|
||||||
|
@SerialName("batch_id")
|
||||||
|
val batchId: Int = 0,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class OtpParameters(
|
||||||
|
@ProtoNumber(1)
|
||||||
|
val secret: ByteArray,
|
||||||
|
@ProtoNumber(2)
|
||||||
|
val name: String? = null,
|
||||||
|
@ProtoNumber(3)
|
||||||
|
val issuer: String? = null,
|
||||||
|
@ProtoNumber(4)
|
||||||
|
val algorithm: Algorithm = Algorithm.ALGORITHM_UNSPECIFIED,
|
||||||
|
@ProtoNumber(5)
|
||||||
|
val digits: DigitCount = DigitCount.DIGIT_COUNT_UNSPECIFIED,
|
||||||
|
@ProtoNumber(6)
|
||||||
|
val type: Type = Type.OTP_TYPE_UNSPECIFIED,
|
||||||
|
@ProtoNumber(7)
|
||||||
|
val counter: Int? = null,
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
enum class Algorithm {
|
||||||
|
@ProtoNumber(0)
|
||||||
|
ALGORITHM_UNSPECIFIED,
|
||||||
|
@ProtoNumber(1)
|
||||||
|
ALGORITHM_SHA1,
|
||||||
|
@ProtoNumber(2)
|
||||||
|
ALGORITHM_SHA256,
|
||||||
|
@ProtoNumber(3)
|
||||||
|
ALGORITHM_SHA512,
|
||||||
|
@ProtoNumber(4)
|
||||||
|
ALGORITHM_MD5,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class DigitCount {
|
||||||
|
@ProtoNumber(0)
|
||||||
|
DIGIT_COUNT_UNSPECIFIED,
|
||||||
|
@ProtoNumber(1)
|
||||||
|
DIGIT_COUNT_SIX,
|
||||||
|
@ProtoNumber(2)
|
||||||
|
DIGIT_COUNT_EIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
enum class Type {
|
||||||
|
@ProtoNumber(0)
|
||||||
|
OTP_TYPE_UNSPECIFIED,
|
||||||
|
@ProtoNumber(1)
|
||||||
|
OTP_TYPE_HOTP,
|
||||||
|
@ProtoNumber(2)
|
||||||
|
OTP_TYPE_TOTP,
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as OtpParameters
|
||||||
|
|
||||||
|
if (!secret.contentEquals(other.secret)) return false
|
||||||
|
if (name != other.name) return false
|
||||||
|
if (issuer != other.issuer) return false
|
||||||
|
if (algorithm != other.algorithm) return false
|
||||||
|
if (digits != other.digits) return false
|
||||||
|
if (type != other.type) return false
|
||||||
|
if (counter != other.counter) return false
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
var result = secret.contentHashCode()
|
||||||
|
result = 31 * result + (name?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (issuer?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + algorithm.hashCode()
|
||||||
|
result = 31 * result + digits.hashCode()
|
||||||
|
result = 31 * result + type.hashCode()
|
||||||
|
result = 31 * result + (counter ?: 0)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator.util
|
||||||
|
|
||||||
|
object OtpMigrationConst {
|
||||||
|
const val PREFIX = "otpauth-migration://"
|
||||||
|
}
|
|
@ -0,0 +1,87 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator.util
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData
|
||||||
|
import com.artemchep.keyguard.common.service.text.Base32Service
|
||||||
|
import io.ktor.http.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a valid OTP URI for use with all other
|
||||||
|
* than Google Authenticator apps.
|
||||||
|
*/
|
||||||
|
fun OtpAuthMigrationData.OtpParameters.build(
|
||||||
|
base32Service: Base32Service,
|
||||||
|
): Either<Throwable, String> = Either.catch {
|
||||||
|
when (type) {
|
||||||
|
OtpAuthMigrationData.OtpParameters.Type.OTP_TYPE_TOTP -> buildTotp(base32Service)
|
||||||
|
OtpAuthMigrationData.OtpParameters.Type.OTP_TYPE_HOTP -> buildHotp(base32Service)
|
||||||
|
else -> throw IllegalArgumentException("Unsupported OTP type!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val OtpAuthMigrationData.OtpParameters.DigitCount.count
|
||||||
|
get() = when (this) {
|
||||||
|
OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_EIGHT -> 8
|
||||||
|
OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_SIX -> 6
|
||||||
|
OtpAuthMigrationData.OtpParameters.DigitCount.DIGIT_COUNT_UNSPECIFIED -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private val OtpAuthMigrationData.OtpParameters.Algorithm.str
|
||||||
|
get() = when (this) {
|
||||||
|
OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA1 -> "sha1"
|
||||||
|
OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA256 -> "sha256"
|
||||||
|
OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_SHA512 -> "sha512"
|
||||||
|
OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_MD5 -> "md5"
|
||||||
|
OtpAuthMigrationData.OtpParameters.Algorithm.ALGORITHM_UNSPECIFIED -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OtpAuthMigrationData.OtpParameters.buildTotp(
|
||||||
|
base32Service: Base32Service,
|
||||||
|
): String = build(
|
||||||
|
"totp",
|
||||||
|
base32Service = base32Service,
|
||||||
|
) {
|
||||||
|
// period
|
||||||
|
parameters.append("period", "30")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OtpAuthMigrationData.OtpParameters.buildHotp(
|
||||||
|
base32Service: Base32Service,
|
||||||
|
): String = build(
|
||||||
|
"hotp",
|
||||||
|
base32Service = base32Service,
|
||||||
|
) {
|
||||||
|
// counter
|
||||||
|
val counter = counter
|
||||||
|
if (counter != null) {
|
||||||
|
parameters.append("counter", counter.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun OtpAuthMigrationData.OtpParameters.build(
|
||||||
|
host: String,
|
||||||
|
base32Service: Base32Service,
|
||||||
|
builder: URLBuilder.() -> Unit,
|
||||||
|
): String {
|
||||||
|
return URLBuilder("otpauth://$host/").apply {
|
||||||
|
val path = issuer.orEmpty() + ":" + name.orEmpty()
|
||||||
|
appendPathSegments(path)
|
||||||
|
|
||||||
|
// digits
|
||||||
|
val digits = digits.count
|
||||||
|
if (digits != null) {
|
||||||
|
parameters.append("digits", digits.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
// secret
|
||||||
|
val secret = base32Service.encodeToString(secret)
|
||||||
|
parameters.append("secret", secret)
|
||||||
|
// algorithm
|
||||||
|
val algorithm = algorithm.str
|
||||||
|
if (algorithm != null) {
|
||||||
|
parameters.append("algorithm", algorithm)
|
||||||
|
}
|
||||||
|
|
||||||
|
builder()
|
||||||
|
}.buildString()
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.artemchep.keyguard.common.service.googleauthenticator.util
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData
|
||||||
|
import com.artemchep.keyguard.common.service.text.Base64Service
|
||||||
|
import io.ktor.http.*
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.decodeFromByteArray
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
import org.kodein.di.DirectDI
|
||||||
|
import org.kodein.di.instance
|
||||||
|
|
||||||
|
class OtpMigrationParser(
|
||||||
|
private val base64Service: Base64Service,
|
||||||
|
) {
|
||||||
|
constructor(
|
||||||
|
directDI: DirectDI,
|
||||||
|
) : this(
|
||||||
|
base64Service = directDI.instance(),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun parse(
|
||||||
|
uri: String,
|
||||||
|
): Either<Throwable, OtpAuthMigrationData> = Either.catch {
|
||||||
|
parseData(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
private fun parseData(
|
||||||
|
uri: String,
|
||||||
|
): OtpAuthMigrationData {
|
||||||
|
val protoDataBase64 = Url(uri)
|
||||||
|
.parameters["data"]
|
||||||
|
requireNotNull(protoDataBase64) {
|
||||||
|
"URI must have the data parameter!"
|
||||||
|
}
|
||||||
|
val protoData = base64Service.decode(protoDataBase64)
|
||||||
|
return ProtoBuf.decodeFromByteArray<OtpAuthMigrationData>(protoData)
|
||||||
|
}
|
||||||
|
}
|
|
@ -197,6 +197,7 @@ class TotpServiceImpl(
|
||||||
CryptoHashAlgorithm.SHA_1 -> "HmacSHA1"
|
CryptoHashAlgorithm.SHA_1 -> "HmacSHA1"
|
||||||
CryptoHashAlgorithm.SHA_256 -> "HmacSHA256"
|
CryptoHashAlgorithm.SHA_256 -> "HmacSHA256"
|
||||||
CryptoHashAlgorithm.SHA_512 -> "HmacSHA512"
|
CryptoHashAlgorithm.SHA_512 -> "HmacSHA512"
|
||||||
|
CryptoHashAlgorithm.MD5 -> "MD5"
|
||||||
}
|
}
|
||||||
val hash = Mac.getInstance(algorithmName).run {
|
val hash = Mac.getInstance(algorithmName).run {
|
||||||
init(SecretKeySpec(key, "RAW")) // The hard-coded value 'RAW' is specified in the RFC
|
init(SecretKeySpec(key, "RAW")) // The hard-coded value 'RAW' is specified in the RFC
|
||||||
|
|
|
@ -455,7 +455,7 @@ private fun TotpTextField(
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
trailing = {
|
trailing = {
|
||||||
ScanQrButton(
|
ScanQrButton(
|
||||||
onValueChange = state.value.onChange,
|
onValueChange = state.onScanned,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
leading = {
|
leading = {
|
||||||
|
|
|
@ -101,6 +101,7 @@ sealed interface AddStateItem {
|
||||||
data class State(
|
data class State(
|
||||||
val copyText: CopyText,
|
val copyText: CopyText,
|
||||||
val value: TextFieldModel2,
|
val value: TextFieldModel2,
|
||||||
|
val onScanned: ((String) -> Unit)? = null,
|
||||||
val totpToken: TotpToken? = null,
|
val totpToken: TotpToken? = null,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,7 @@ import com.artemchep.keyguard.common.model.firstOrNull
|
||||||
import com.artemchep.keyguard.common.model.title
|
import com.artemchep.keyguard.common.model.title
|
||||||
import com.artemchep.keyguard.common.model.titleH
|
import com.artemchep.keyguard.common.model.titleH
|
||||||
import com.artemchep.keyguard.common.service.clipboard.ClipboardService
|
import com.artemchep.keyguard.common.service.clipboard.ClipboardService
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService
|
||||||
import com.artemchep.keyguard.common.service.logging.LogRepository
|
import com.artemchep.keyguard.common.service.logging.LogRepository
|
||||||
import com.artemchep.keyguard.common.usecase.AddCipher
|
import com.artemchep.keyguard.common.usecase.AddCipher
|
||||||
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
|
import com.artemchep.keyguard.common.usecase.CipherUnsecureUrlCheck
|
||||||
|
@ -198,6 +199,7 @@ fun produceAddScreenState(
|
||||||
getMarkdown = instance(),
|
getMarkdown = instance(),
|
||||||
logRepository = instance(),
|
logRepository = instance(),
|
||||||
clipboardService = instance(),
|
clipboardService = instance(),
|
||||||
|
otpMigrationService = instance(),
|
||||||
cipherUnsecureUrlCheck = instance(),
|
cipherUnsecureUrlCheck = instance(),
|
||||||
showMessage = instance(),
|
showMessage = instance(),
|
||||||
addCipher = instance(),
|
addCipher = instance(),
|
||||||
|
@ -227,6 +229,7 @@ fun produceAddScreenState(
|
||||||
getMarkdown: GetMarkdown,
|
getMarkdown: GetMarkdown,
|
||||||
logRepository: LogRepository,
|
logRepository: LogRepository,
|
||||||
clipboardService: ClipboardService,
|
clipboardService: ClipboardService,
|
||||||
|
otpMigrationService: OtpMigrationService,
|
||||||
cipherUnsecureUrlCheck: CipherUnsecureUrlCheck,
|
cipherUnsecureUrlCheck: CipherUnsecureUrlCheck,
|
||||||
showMessage: ShowMessage,
|
showMessage: ShowMessage,
|
||||||
addCipher: AddCipher,
|
addCipher: AddCipher,
|
||||||
|
@ -300,6 +303,7 @@ fun produceAddScreenState(
|
||||||
copyText = copyText,
|
copyText = copyText,
|
||||||
getTotpCode = getTotpCode,
|
getTotpCode = getTotpCode,
|
||||||
getGravatarUrl = getGravatarUrl,
|
getGravatarUrl = getGravatarUrl,
|
||||||
|
otpMigrationService = otpMigrationService,
|
||||||
)
|
)
|
||||||
val cardHolder = produceCardState(
|
val cardHolder = produceCardState(
|
||||||
args = args,
|
args = args,
|
||||||
|
@ -2160,6 +2164,7 @@ private suspend fun RememberStateFlowScope.produceLoginState(
|
||||||
copyText: CopyText,
|
copyText: CopyText,
|
||||||
getTotpCode: GetTotpCode,
|
getTotpCode: GetTotpCode,
|
||||||
getGravatarUrl: GetGravatarUrl,
|
getGravatarUrl: GetGravatarUrl,
|
||||||
|
otpMigrationService: OtpMigrationService,
|
||||||
): TmpLogin {
|
): TmpLogin {
|
||||||
val prefix = "login"
|
val prefix = "login"
|
||||||
|
|
||||||
|
@ -2328,6 +2333,13 @@ private suspend fun RememberStateFlowScope.produceLoginState(
|
||||||
AddStateItem.Totp.State(
|
AddStateItem.Totp.State(
|
||||||
value = value,
|
value = value,
|
||||||
copyText = copyText,
|
copyText = copyText,
|
||||||
|
onScanned = { uri ->
|
||||||
|
val convertedUri = otpMigrationService.handler(uri)
|
||||||
|
?.convert(uri)
|
||||||
|
?.getOrNull()
|
||||||
|
?: uri
|
||||||
|
state.value = convertedUri
|
||||||
|
},
|
||||||
totpToken = totp,
|
totpToken = totp,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@ import com.artemchep.keyguard.common.service.export.JsonExportService
|
||||||
import com.artemchep.keyguard.common.service.export.impl.JsonExportServiceImpl
|
import com.artemchep.keyguard.common.service.export.impl.JsonExportServiceImpl
|
||||||
import com.artemchep.keyguard.common.service.extract.impl.LinkInfoExtractorExecute
|
import com.artemchep.keyguard.common.service.extract.impl.LinkInfoExtractorExecute
|
||||||
import com.artemchep.keyguard.common.service.extract.impl.LinkInfoPlatformExtractor
|
import com.artemchep.keyguard.common.service.extract.impl.LinkInfoPlatformExtractor
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.OtpMigrationService
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.impl.OtpMigrationServiceImpl
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.util.OtpMigrationParser
|
||||||
import com.artemchep.keyguard.common.service.gpmprivapps.PrivilegedAppsService
|
import com.artemchep.keyguard.common.service.gpmprivapps.PrivilegedAppsService
|
||||||
import com.artemchep.keyguard.common.service.gpmprivapps.impl.PrivilegedAppsServiceImpl
|
import com.artemchep.keyguard.common.service.gpmprivapps.impl.PrivilegedAppsServiceImpl
|
||||||
import com.artemchep.keyguard.common.service.id.IdRepository
|
import com.artemchep.keyguard.common.service.id.IdRepository
|
||||||
|
@ -1200,6 +1203,16 @@ fun globalModuleJvm() = DI.Module(
|
||||||
directDI = this,
|
directDI = this,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
bindSingleton<OtpMigrationService> {
|
||||||
|
OtpMigrationServiceImpl(
|
||||||
|
directDI = this,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
bindSingleton<OtpMigrationParser> {
|
||||||
|
OtpMigrationParser(
|
||||||
|
directDI = this,
|
||||||
|
)
|
||||||
|
}
|
||||||
bindSingleton<PrivilegedAppsService> {
|
bindSingleton<PrivilegedAppsService> {
|
||||||
PrivilegedAppsServiceImpl(
|
PrivilegedAppsServiceImpl(
|
||||||
directDI = this,
|
directDI = this,
|
||||||
|
|
|
@ -24,6 +24,7 @@ import com.artemchep.keyguard.common.io.bind
|
||||||
import com.artemchep.keyguard.common.model.MasterSession
|
import com.artemchep.keyguard.common.model.MasterSession
|
||||||
import com.artemchep.keyguard.common.model.PersistedSession
|
import com.artemchep.keyguard.common.model.PersistedSession
|
||||||
import com.artemchep.keyguard.common.model.ToastMessage
|
import com.artemchep.keyguard.common.model.ToastMessage
|
||||||
|
import com.artemchep.keyguard.common.service.googleauthenticator.model.OtpAuthMigrationData
|
||||||
import com.artemchep.keyguard.common.service.session.VaultSessionLocker
|
import com.artemchep.keyguard.common.service.session.VaultSessionLocker
|
||||||
import com.artemchep.keyguard.common.service.vault.KeyReadWriteRepository
|
import com.artemchep.keyguard.common.service.vault.KeyReadWriteRepository
|
||||||
import com.artemchep.keyguard.common.usecase.GetAccounts
|
import com.artemchep.keyguard.common.usecase.GetAccounts
|
||||||
|
@ -76,6 +77,9 @@ import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
import kotlinx.serialization.decodeFromByteArray
|
||||||
|
import kotlinx.serialization.protobuf.ProtoBuf
|
||||||
|
import kotlinx.serialization.protobuf.schema.ProtoBufSchemaGenerator
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
import org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
|
||||||
import org.jetbrains.compose.resources.painterResource
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
@ -89,8 +93,11 @@ import org.kodein.di.direct
|
||||||
import org.kodein.di.instance
|
import org.kodein.di.instance
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.io.encoding.Base64
|
||||||
|
import kotlin.io.encoding.ExperimentalEncodingApi
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
|
||||||
|
@OptIn(ExperimentalEncodingApi::class)
|
||||||
fun main() {
|
fun main() {
|
||||||
// Add BouncyCastle as the first security provider
|
// Add BouncyCastle as the first security provider
|
||||||
// to make OkHTTP use its TLS instead of a platform
|
// to make OkHTTP use its TLS instead of a platform
|
||||||
|
@ -99,6 +106,13 @@ fun main() {
|
||||||
Security.insertProviderAt(BouncyCastleProvider(), 1)
|
Security.insertProviderAt(BouncyCastleProvider(), 1)
|
||||||
Security.insertProviderAt(BouncyCastleJsseProvider(), 2)
|
Security.insertProviderAt(BouncyCastleJsseProvider(), 2)
|
||||||
|
|
||||||
|
val g = ProtoBufSchemaGenerator.generateSchemaText(listOf(OtpAuthMigrationData.serializer().descriptor))
|
||||||
|
println("help!")
|
||||||
|
println(g)
|
||||||
|
println("help!")
|
||||||
|
val f = ProtoBuf.decodeFromByteArray<OtpAuthMigrationData>(Base64.decode("Cj4KCpgu67BGYPX7QLkSH01pY3Jvc29mdDphcnRlbWNoZXBAb3V0bG9vay5jb20aCU1pY3Jvc29mdCABKAEwAgoxChRAhdYwy3lUa0se1A2wvpXq5vZnlxIJYXJ0ZW1jaGVwGghGYWNlYm9vayABKAEwAhABGAEgACjeyrmK/f////8B"))
|
||||||
|
println(f)
|
||||||
|
|
||||||
val kamelConfig = KamelConfig {
|
val kamelConfig = KamelConfig {
|
||||||
this.takeFrom(KamelConfig.Default)
|
this.takeFrom(KamelConfig.Default)
|
||||||
mapper(FaviconUrlMapper)
|
mapper(FaviconUrlMapper)
|
||||||
|
|
|
@ -222,6 +222,7 @@ kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-
|
||||||
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" }
|
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinDatetime" }
|
||||||
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
|
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinSerialization" }
|
||||||
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinSerialization" }
|
kotlinx-serialization-cbor = { module = "org.jetbrains.kotlinx:kotlinx-serialization-cbor", version.ref = "kotlinSerialization" }
|
||||||
|
kotlinx-serialization-protobuf = { module = "org.jetbrains.kotlinx:kotlinx-serialization-protobuf", version.ref = "kotlinSerialization" }
|
||||||
ktor-ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
ktor-ktor-client-cio = { module = "io.ktor:ktor-client-cio", version.ref = "ktor" }
|
||||||
ktor-ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
ktor-ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
|
||||||
ktor-ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
ktor-ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||||
|
|
Loading…
Reference in New Issue