fix: Avoid being flagged as a bot by using desktop devices' personas #324
This commit is contained in:
parent
b5a8e01e1c
commit
1bae2319e3
@ -1,6 +1,8 @@
|
|||||||
package com.artemchep.keyguard.provider.bitwarden.api.builder
|
package com.artemchep.keyguard.provider.bitwarden.api.builder
|
||||||
|
|
||||||
|
import com.artemchep.keyguard.platform.CurrentPlatform
|
||||||
import com.artemchep.keyguard.provider.bitwarden.ServerEnv
|
import com.artemchep.keyguard.provider.bitwarden.ServerEnv
|
||||||
|
import com.artemchep.keyguard.provider.bitwarden.api.BitwardenPersona
|
||||||
import com.artemchep.keyguard.provider.bitwarden.api.entity.SyncResponse
|
import com.artemchep.keyguard.provider.bitwarden.api.entity.SyncResponse
|
||||||
import com.artemchep.keyguard.provider.bitwarden.entity.AttachmentEntity
|
import com.artemchep.keyguard.provider.bitwarden.entity.AttachmentEntity
|
||||||
import com.artemchep.keyguard.provider.bitwarden.entity.AvatarRequestEntity
|
import com.artemchep.keyguard.provider.bitwarden.entity.AvatarRequestEntity
|
||||||
@ -551,10 +553,14 @@ private suspend inline fun String.delete(
|
|||||||
.bodyOrApiException<HttpResponse>()
|
.bodyOrApiException<HttpResponse>()
|
||||||
|
|
||||||
fun HttpRequestBuilder.headers(env: ServerEnv) {
|
fun HttpRequestBuilder.headers(env: ServerEnv) {
|
||||||
|
// Let Bitwarden know who we are.
|
||||||
|
header("Keyguard-Client", "1")
|
||||||
// Seems like now Bitwarden now requires you to specify
|
// Seems like now Bitwarden now requires you to specify
|
||||||
// the client name and version.
|
// the client name and version.
|
||||||
header("Bitwarden-Client-Name", "web")
|
val persona = CurrentPlatform
|
||||||
header("Bitwarden-Client-Version", "2024.03.0")
|
.let(BitwardenPersona::of)
|
||||||
|
header("Bitwarden-Client-Name", persona.clientName)
|
||||||
|
header("Bitwarden-Client-Version", persona.clientVersion)
|
||||||
// App does not work if hidden behind reverse-proxy under
|
// App does not work if hidden behind reverse-proxy under
|
||||||
// a subdirectory. We should specify the 'referer' so the server
|
// a subdirectory. We should specify the 'referer' so the server
|
||||||
// generates correct urls for us.
|
// generates correct urls for us.
|
||||||
|
@ -9,6 +9,8 @@ import com.artemchep.keyguard.common.service.text.url
|
|||||||
import com.artemchep.keyguard.common.usecase.DeviceIdUseCase
|
import com.artemchep.keyguard.common.usecase.DeviceIdUseCase
|
||||||
import com.artemchep.keyguard.common.util.int
|
import com.artemchep.keyguard.common.util.int
|
||||||
import com.artemchep.keyguard.core.store.bitwarden.BitwardenToken
|
import com.artemchep.keyguard.core.store.bitwarden.BitwardenToken
|
||||||
|
import com.artemchep.keyguard.platform.CurrentPlatform
|
||||||
|
import com.artemchep.keyguard.platform.Platform
|
||||||
import com.artemchep.keyguard.provider.bitwarden.ServerEnv
|
import com.artemchep.keyguard.provider.bitwarden.ServerEnv
|
||||||
import com.artemchep.keyguard.provider.bitwarden.ServerTwoFactorToken
|
import com.artemchep.keyguard.provider.bitwarden.ServerTwoFactorToken
|
||||||
import com.artemchep.keyguard.provider.bitwarden.api.builder.api
|
import com.artemchep.keyguard.provider.bitwarden.api.builder.api
|
||||||
@ -41,8 +43,52 @@ import java.util.Locale
|
|||||||
|
|
||||||
private const val PBKDF2_KEY_LENGTH = 32
|
private const val PBKDF2_KEY_LENGTH = 32
|
||||||
|
|
||||||
private const val CLIENT_ID = "web"
|
data class BitwardenPersona(
|
||||||
private const val DEVICE_TYPE = "9"
|
val clientId: String,
|
||||||
|
val clientName: String,
|
||||||
|
val clientVersion: String,
|
||||||
|
val deviceType: String,
|
||||||
|
val deviceName: String,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private const val CLIENT_VERSION = "2024.4.0"
|
||||||
|
|
||||||
|
fun of(platform: Platform) = when (platform) {
|
||||||
|
is Platform.Mobile -> desktopLinux()
|
||||||
|
is Platform.Desktop -> when (platform) {
|
||||||
|
is Platform.Desktop.Windows -> desktopWindows()
|
||||||
|
is Platform.Desktop.MacOS -> desktopMacOs()
|
||||||
|
is Platform.Desktop.Other,
|
||||||
|
is Platform.Desktop.Linux,
|
||||||
|
-> desktopLinux()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun desktopLinux() = BitwardenPersona(
|
||||||
|
clientId = "desktop",
|
||||||
|
clientName = "desktop",
|
||||||
|
clientVersion = CLIENT_VERSION,
|
||||||
|
deviceType = "8",
|
||||||
|
deviceName = "linux",
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun desktopMacOs() = BitwardenPersona(
|
||||||
|
clientId = "desktop",
|
||||||
|
clientName = "desktop",
|
||||||
|
clientVersion = CLIENT_VERSION,
|
||||||
|
deviceType = "7",
|
||||||
|
deviceName = "macos",
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun desktopWindows() = BitwardenPersona(
|
||||||
|
clientId = "desktop",
|
||||||
|
clientName = "desktop",
|
||||||
|
clientVersion = CLIENT_VERSION,
|
||||||
|
deviceType = "6",
|
||||||
|
deviceName = "windows",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun login(
|
suspend fun login(
|
||||||
deviceIdUseCase: DeviceIdUseCase,
|
deviceIdUseCase: DeviceIdUseCase,
|
||||||
@ -91,9 +137,12 @@ private suspend fun internalLogin(
|
|||||||
passwordKey: ByteArray,
|
passwordKey: ByteArray,
|
||||||
): Login = httpClient
|
): Login = httpClient
|
||||||
.post(env.identity.connect.token) {
|
.post(env.identity.connect.token) {
|
||||||
|
val persona = CurrentPlatform
|
||||||
|
.let(BitwardenPersona::of)
|
||||||
|
|
||||||
headers(env)
|
headers(env)
|
||||||
header("device-type", DEVICE_TYPE)
|
header("device-type", persona.deviceType)
|
||||||
header("dnt", "1")
|
header("cache-control", "no-store")
|
||||||
// Official Bitwarden backend specifically checks for this header,
|
// Official Bitwarden backend specifically checks for this header,
|
||||||
// which is just a base-64 string of an email.
|
// which is just a base-64 string of an email.
|
||||||
val emailBase64 = base64Service
|
val emailBase64 = base64Service
|
||||||
@ -108,7 +157,7 @@ private suspend fun internalLogin(
|
|||||||
append("username", email)
|
append("username", email)
|
||||||
append("password", passwordBase64)
|
append("password", passwordBase64)
|
||||||
append("scope", "api offline_access")
|
append("scope", "api offline_access")
|
||||||
append("client_id", CLIENT_ID)
|
append("client_id", persona.clientId)
|
||||||
// As per
|
// As per
|
||||||
// https://github.com/bitwarden/cli/issues/383#issuecomment-937819752
|
// https://github.com/bitwarden/cli/issues/383#issuecomment-937819752
|
||||||
// the backdoor to a captcha is a client secret.
|
// the backdoor to a captcha is a client secret.
|
||||||
@ -116,8 +165,8 @@ private suspend fun internalLogin(
|
|||||||
append("captchaResponse", clientSecret)
|
append("captchaResponse", clientSecret)
|
||||||
}
|
}
|
||||||
append("deviceIdentifier", deviceId)
|
append("deviceIdentifier", deviceId)
|
||||||
append("deviceType", DEVICE_TYPE)
|
append("deviceType", persona.deviceType)
|
||||||
append("deviceName", "chrome")
|
append("deviceName", persona.deviceName)
|
||||||
|
|
||||||
if (twoFactorToken != null) {
|
if (twoFactorToken != null) {
|
||||||
val providerEntity = TwoFactorProviderTypeEntity.of(twoFactorToken.provider)
|
val providerEntity = TwoFactorProviderTypeEntity.of(twoFactorToken.provider)
|
||||||
@ -150,10 +199,12 @@ suspend fun refresh(
|
|||||||
token: BitwardenToken.Token,
|
token: BitwardenToken.Token,
|
||||||
): Login = httpClient
|
): Login = httpClient
|
||||||
.post(env.identity.connect.token) {
|
.post(env.identity.connect.token) {
|
||||||
headers(env)
|
val persona = CurrentPlatform
|
||||||
header("device-type", DEVICE_TYPE)
|
.let(BitwardenPersona::of)
|
||||||
header("dnt", "1")
|
|
||||||
|
|
||||||
|
headers(env)
|
||||||
|
header("device-type", persona.deviceType)
|
||||||
|
header("cache-control", "no-store")
|
||||||
val clientId = kotlin.runCatching {
|
val clientId = kotlin.runCatching {
|
||||||
val jwtData = parseAccessTokenData(
|
val jwtData = parseAccessTokenData(
|
||||||
base64Service = base64Service,
|
base64Service = base64Service,
|
||||||
@ -162,7 +213,7 @@ suspend fun refresh(
|
|||||||
)
|
)
|
||||||
jwtData["client_id"]?.jsonPrimitive?.content
|
jwtData["client_id"]?.jsonPrimitive?.content
|
||||||
}.getOrNull()
|
}.getOrNull()
|
||||||
?: CLIENT_ID
|
?: persona.clientId
|
||||||
body = FormDataContent(
|
body = FormDataContent(
|
||||||
Parameters.build {
|
Parameters.build {
|
||||||
append("grant_type", "refresh_token")
|
append("grant_type", "refresh_token")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user