mirror of
https://github.com/bitwarden/android.git
synced 2025-02-16 11:59:57 +03:00
BIT-2181: Move PreLogin and Register network calls to Identity API (#1285)
This commit is contained in:
parent
15cb60e3d2
commit
4293c0b9fd
10 changed files with 284 additions and 274 deletions
|
@ -1,10 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
@ -13,12 +9,6 @@ import retrofit2.http.POST
|
|||
* Defines raw calls under the /accounts API.
|
||||
*/
|
||||
interface AccountsApi {
|
||||
@POST("/accounts/prelogin")
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
||||
|
||||
@POST("/accounts/register")
|
||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
||||
|
||||
@POST("/accounts/password-hint")
|
||||
suspend fun passwordHintRequest(
|
||||
@Body body: PasswordHintRequestJson,
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
import retrofit2.http.FormUrlEncoded
|
||||
import retrofit2.http.GET
|
||||
|
@ -55,4 +60,10 @@ interface IdentityApi {
|
|||
@Field(value = "refresh_token") refreshToken: String,
|
||||
@Field(value = "grant_type") grantType: String,
|
||||
): Call<RefreshTokenResponseJson>
|
||||
|
||||
@POST("/accounts/prelogin")
|
||||
suspend fun preLogin(@Body body: PreLoginRequestJson): Result<PreLoginResponseJson>
|
||||
|
||||
@POST("/accounts/register")
|
||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
||||
}
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
|
@ -23,16 +20,6 @@ interface AccountsService {
|
|||
*/
|
||||
suspend fun deleteAccount(masterPasswordHash: String): Result<Unit>
|
||||
|
||||
/**
|
||||
* Make pre login request to get KDF params.
|
||||
*/
|
||||
suspend fun preLogin(email: String): Result<PreLoginResponseJson>
|
||||
|
||||
/**
|
||||
* Register a new account to Bitwarden.
|
||||
*/
|
||||
suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson>
|
||||
|
||||
/**
|
||||
* Request a one-time passcode that is sent to the user's email.
|
||||
*/
|
||||
|
|
|
@ -6,10 +6,6 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.CreateAccountKeysR
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeleteAccountRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
|
@ -38,27 +34,6 @@ class AccountsServiceImpl(
|
|||
override suspend fun deleteAccount(masterPasswordHash: String): Result<Unit> =
|
||||
authenticatedAccountsApi.deleteAccount(DeleteAccountRequestJson(masterPasswordHash))
|
||||
|
||||
override suspend fun preLogin(email: String): Result<PreLoginResponseJson> =
|
||||
accountsApi.preLogin(PreLoginRequestJson(email = email))
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
|
||||
accountsApi
|
||||
.register(body)
|
||||
.recoverCatching { throwable ->
|
||||
val bitwardenError = throwable.toBitwardenError()
|
||||
bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
|
||||
code = 400,
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
|
||||
codes = listOf(400, 429),
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Error>(
|
||||
code = 429,
|
||||
json = json,
|
||||
) ?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun requestOneTimePasscode(): Result<Unit> =
|
||||
authenticatedAccountsApi.requestOtp()
|
||||
|
||||
|
|
|
@ -2,8 +2,11 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
|
||||
/**
|
||||
|
@ -11,6 +14,16 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
|||
*/
|
||||
interface IdentityService {
|
||||
|
||||
/**
|
||||
* Make pre login request to get KDF params.
|
||||
*/
|
||||
suspend fun preLogin(email: String): Result<PreLoginResponseJson>
|
||||
|
||||
/**
|
||||
* Register a new account to Bitwarden.
|
||||
*/
|
||||
suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson>
|
||||
|
||||
/**
|
||||
* Make request to get an access token.
|
||||
*
|
||||
|
|
|
@ -3,8 +3,12 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.api.IdentityApi
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.model.toBitwardenError
|
||||
import com.x8bit.bitwarden.data.platform.datasource.network.util.base64UrlEncode
|
||||
|
@ -19,6 +23,27 @@ class IdentityServiceImpl(
|
|||
private val deviceModelProvider: DeviceModelProvider = DeviceModelProvider(),
|
||||
) : IdentityService {
|
||||
|
||||
override suspend fun preLogin(email: String): Result<PreLoginResponseJson> =
|
||||
api.preLogin(PreLoginRequestJson(email = email))
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override suspend fun register(body: RegisterRequestJson): Result<RegisterResponseJson> =
|
||||
api
|
||||
.register(body)
|
||||
.recoverCatching { throwable ->
|
||||
val bitwardenError = throwable.toBitwardenError()
|
||||
bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
|
||||
code = 400,
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
|
||||
codes = listOf(400, 429),
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Error>(
|
||||
code = 429,
|
||||
json = json,
|
||||
) ?: throw throwable
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override suspend fun getToken(
|
||||
uniqueAppId: String,
|
||||
|
|
|
@ -453,7 +453,7 @@ class AuthRepositoryImpl(
|
|||
email: String,
|
||||
password: String,
|
||||
captchaToken: String?,
|
||||
): LoginResult = accountsService
|
||||
): LoginResult = identityService
|
||||
.preLogin(email = email)
|
||||
.flatMap {
|
||||
authSdkSource.hashPassword(
|
||||
|
@ -664,7 +664,7 @@ class AuthRepositoryImpl(
|
|||
kdf = kdf,
|
||||
)
|
||||
.flatMap { registerKeyResponse ->
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = email,
|
||||
masterPasswordHash = registerKeyResponse.masterPasswordHash,
|
||||
|
|
|
@ -2,16 +2,12 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
|
|||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.AccountsApi
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.api.AuthenticatedAccountsApi
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson.PBKDF2_SHA256
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResendEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.ResetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.mockwebserver.MockResponse
|
||||
|
@ -57,168 +53,6 @@ class AccountsServiceTest : BaseServiceTest() {
|
|||
assertTrue(service.deleteAccount(masterPasswordHash).isSuccess)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin with unknown kdf type be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 2,
|
||||
"kdfIterations": 1,
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(service.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 without memory property should be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfParallelism": 1
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(service.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 without parallelism property should be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfMemory": 1
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(service.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 should be success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfMemory": 1,
|
||||
"kdfParallelism": 1
|
||||
}
|
||||
"""
|
||||
val expectedResponse = PreLoginResponseJson(
|
||||
kdfParams = PreLoginResponseJson.KdfParams.Argon2ID(
|
||||
iterations = 1u,
|
||||
memory = 1u,
|
||||
parallelism = 1u,
|
||||
),
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(expectedResponse.asSuccess(), service.preLogin(EMAIL))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Pbkdf2 should be success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 0,
|
||||
"kdfIterations": 1
|
||||
}
|
||||
"""
|
||||
val expectedResponse = PreLoginResponseJson(
|
||||
kdfParams = PreLoginResponseJson.KdfParams.Pbkdf2(1u),
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(expectedResponse.asSuccess(), service.preLogin(EMAIL))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register success json should be Success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"captchaBypassToken": "mock_token"
|
||||
}
|
||||
"""
|
||||
val expectedResponse = RegisterResponseJson.Success(
|
||||
captchaBypassToken = "mock_token",
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(expectedResponse.asSuccess(), service.register(registerRequestBody))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register failure with Invalid json should be Invalid`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"message": "The model state is invalid.",
|
||||
"validationErrors": {
|
||||
"": [
|
||||
"Email '' is already taken."
|
||||
]
|
||||
},
|
||||
"exceptionMessage": null,
|
||||
"exceptionStackTrace": null,
|
||||
"innerExceptionMessage": null,
|
||||
"object": "error"
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setResponseCode(400).setBody(json)
|
||||
server.enqueue(response)
|
||||
val result = service.register(registerRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Invalid(
|
||||
message = "The model state is invalid.",
|
||||
validationErrors = mapOf("" to listOf("Email '' is already taken.")),
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register failure with Error json should return Error`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"Object": "error",
|
||||
"Message": "Slow down! Too many requests. Try again soon."
|
||||
}
|
||||
""".trimIndent()
|
||||
val response = MockResponse().setResponseCode(429).setBody(json)
|
||||
server.enqueue(response)
|
||||
val result = service.register(registerRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Error(
|
||||
message = "Slow down! Too many requests. Try again soon.",
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register captcha json should be CaptchaRequired`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"validationErrors": {
|
||||
"HCaptcha_SiteKey": [
|
||||
"mock_token"
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
val expectedResponse = RegisterResponseJson.CaptchaRequired(
|
||||
validationErrors = RegisterResponseJson.CaptchaRequired.ValidationErrors(
|
||||
captchaKeys = listOf("mock_token"),
|
||||
),
|
||||
)
|
||||
val response = MockResponse().setResponseCode(400).setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(expectedResponse.asSuccess(), service.register(registerRequestBody))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `requestOtp success should return Success`() = runTest {
|
||||
val response = MockResponse().setResponseCode(200)
|
||||
|
@ -338,21 +172,4 @@ class AccountsServiceTest : BaseServiceTest() {
|
|||
)
|
||||
assertTrue(result.isSuccess)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val EMAIL = "email"
|
||||
private val registerRequestBody = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = "mockk_masterPasswordHash",
|
||||
masterPasswordHint = "mockk_masterPasswordHint",
|
||||
captchaResponse = "mockk_captchaResponse",
|
||||
key = "mockk_key",
|
||||
keys = RegisterRequestJson.Keys(
|
||||
publicKey = "mockk_publicKey",
|
||||
encryptedPrivateKey = "mockk_encryptedPrivateKey",
|
||||
),
|
||||
kdfType = PBKDF2_SHA256,
|
||||
kdfIterations = 600000U,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,11 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthM
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KdfTypeJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.KeyConnectorUserDecryptionOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.MasterPasswordPolicyOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PrevalidateSsoResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
||||
|
@ -36,10 +39,186 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
|
||||
private val identityService = IdentityServiceImpl(
|
||||
api = identityApi,
|
||||
json = Json,
|
||||
json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
},
|
||||
deviceModelProvider = deviceModelProvider,
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `preLogin with unknown kdf type be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 2,
|
||||
"kdfIterations": 1,
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(identityService.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 without memory property should be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfParallelism": 1
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(identityService.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 without parallelism property should be failure`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfMemory": 1
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertTrue(identityService.preLogin(EMAIL).isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Argon2 should be success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 1,
|
||||
"kdfIterations": 1,
|
||||
"kdfMemory": 1,
|
||||
"kdfParallelism": 1
|
||||
}
|
||||
"""
|
||||
val expectedResponse = PreLoginResponseJson(
|
||||
kdfParams = PreLoginResponseJson.KdfParams.Argon2ID(
|
||||
iterations = 1u,
|
||||
memory = 1u,
|
||||
parallelism = 1u,
|
||||
),
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
identityService.preLogin(EMAIL),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `preLogin Pbkdf2 should be success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"kdf": 0,
|
||||
"kdfIterations": 1
|
||||
}
|
||||
"""
|
||||
val expectedResponse = PreLoginResponseJson(
|
||||
kdfParams = PreLoginResponseJson.KdfParams.Pbkdf2(1u),
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
identityService.preLogin(EMAIL),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register success json should be Success`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"captchaBypassToken": "mock_token"
|
||||
}
|
||||
"""
|
||||
val expectedResponse = RegisterResponseJson.Success(
|
||||
captchaBypassToken = "mock_token",
|
||||
)
|
||||
val response = MockResponse().setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
identityService.register(registerRequestBody),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register failure with Invalid json should be Invalid`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"message": "The model state is invalid.",
|
||||
"validationErrors": {
|
||||
"": [
|
||||
"Email '' is already taken."
|
||||
]
|
||||
},
|
||||
"exceptionMessage": null,
|
||||
"exceptionStackTrace": null,
|
||||
"innerExceptionMessage": null,
|
||||
"object": "error"
|
||||
}
|
||||
"""
|
||||
val response = MockResponse().setResponseCode(400).setBody(json)
|
||||
server.enqueue(response)
|
||||
val result = identityService.register(registerRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Invalid(
|
||||
message = "The model state is invalid.",
|
||||
validationErrors = mapOf("" to listOf("Email '' is already taken.")),
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register failure with Error json should return Error`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"Object": "error",
|
||||
"Message": "Slow down! Too many requests. Try again soon."
|
||||
}
|
||||
""".trimIndent()
|
||||
val response = MockResponse().setResponseCode(429).setBody(json)
|
||||
server.enqueue(response)
|
||||
val result = identityService.register(registerRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Error(
|
||||
message = "Slow down! Too many requests. Try again soon.",
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register captcha json should be CaptchaRequired`() = runTest {
|
||||
val json = """
|
||||
{
|
||||
"validationErrors": {
|
||||
"HCaptcha_SiteKey": [
|
||||
"mock_token"
|
||||
]
|
||||
}
|
||||
}
|
||||
"""
|
||||
val expectedResponse = RegisterResponseJson.CaptchaRequired(
|
||||
validationErrors = RegisterResponseJson.CaptchaRequired.ValidationErrors(
|
||||
captchaKeys = listOf("mock_token"),
|
||||
),
|
||||
)
|
||||
val response = MockResponse().setResponseCode(400).setBody(json)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
identityService.register(registerRequestBody),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getToken when request response is Success should return Success`() = runTest {
|
||||
server.enqueue(MockResponse().setBody(LOGIN_SUCCESS_JSON))
|
||||
|
@ -153,6 +332,19 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
private const val REFRESH_TOKEN = "refreshToken"
|
||||
private const val EMAIL = "email"
|
||||
private const val PASSWORD_HASH = "passwordHash"
|
||||
private val registerRequestBody = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = "mockk_masterPasswordHash",
|
||||
masterPasswordHint = "mockk_masterPasswordHint",
|
||||
captchaResponse = "mockk_captchaResponse",
|
||||
key = "mockk_key",
|
||||
keys = RegisterRequestJson.Keys(
|
||||
publicKey = "mockk_publicKey",
|
||||
encryptedPrivateKey = "mockk_encryptedPrivateKey",
|
||||
),
|
||||
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||
kdfIterations = 600000U,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -396,7 +396,7 @@ class AuthRepositoryTest {
|
|||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -453,7 +453,7 @@ class AuthRepositoryTest {
|
|||
// Verify the results.
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
|
@ -1260,18 +1260,18 @@ class AuthRepositoryTest {
|
|||
@Test
|
||||
fun `login when pre login fails should return Error with no message`() = runTest {
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns RuntimeException().asFailure()
|
||||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Error(errorMessage = null), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `login get token fails should return Error with no message`() = runTest {
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1287,7 +1287,7 @@ class AuthRepositoryTest {
|
|||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Error(errorMessage = null), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1304,7 +1304,7 @@ class AuthRepositoryTest {
|
|||
@Test
|
||||
fun `login get token returns Invalid should return Error with correct message`() = runTest {
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1327,7 +1327,7 @@ class AuthRepositoryTest {
|
|||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Error(errorMessage = "mock_error_message"), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1347,7 +1347,7 @@ class AuthRepositoryTest {
|
|||
runTest {
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1381,7 +1381,7 @@ class AuthRepositoryTest {
|
|||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
|
@ -1436,7 +1436,7 @@ class AuthRepositoryTest {
|
|||
),
|
||||
)
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1463,7 +1463,7 @@ class AuthRepositoryTest {
|
|||
)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertMasterPasswordHash(
|
||||
userId = USER_ID_1,
|
||||
passwordHash = PASSWORD_HASH,
|
||||
|
@ -1509,7 +1509,7 @@ class AuthRepositoryTest {
|
|||
// Set up login for User 1
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1545,7 +1545,7 @@ class AuthRepositoryTest {
|
|||
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
|
@ -1585,7 +1585,7 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `login get token returns captcha request should return CaptchaRequired`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1600,7 +1600,7 @@ class AuthRepositoryTest {
|
|||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.CaptchaRequired(CAPTCHA_KEY), result)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1616,7 +1616,7 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `login get token returns two factor request should return TwoFactorRequired`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1641,7 +1641,7 @@ class AuthRepositoryTest {
|
|||
GetTokenResponseJson.TwoFactorRequired(TWO_FACTOR_AUTH_METHODS_DATA, null, null),
|
||||
)
|
||||
assertEquals(AuthState.Unauthenticated, repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1659,7 +1659,7 @@ class AuthRepositoryTest {
|
|||
fun `login two factor with remember saves two factor auth token`() = runTest {
|
||||
// Attempt a normal login with a two factor error first, so that the auth
|
||||
// data will be cached.
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1679,7 +1679,7 @@ class AuthRepositoryTest {
|
|||
.asSuccess()
|
||||
val firstResult = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.TwoFactorRequired, firstResult)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -1750,7 +1750,7 @@ class AuthRepositoryTest {
|
|||
)
|
||||
val successResponse = GET_TOKEN_RESPONSE_SUCCESS
|
||||
coEvery {
|
||||
accountsService.preLogin(email = EMAIL)
|
||||
identityService.preLogin(email = EMAIL)
|
||||
} returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
|
@ -1785,7 +1785,7 @@ class AuthRepositoryTest {
|
|||
val result = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.Success, result)
|
||||
assertEquals(AuthState.Authenticated(ACCESS_TOKEN), repository.authStateFlow.value)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
fakeAuthDiskSource.assertPrivateKey(
|
||||
userId = USER_ID_1,
|
||||
privateKey = "privateKey",
|
||||
|
@ -2908,7 +2908,7 @@ class AuthRepositoryTest {
|
|||
haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD)
|
||||
} returns Throwable().asFailure()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -2996,7 +2996,7 @@ class AuthRepositoryTest {
|
|||
haveIBeenPwnedService.hasPasswordBeenBreached(PASSWORD)
|
||||
} returns false.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3027,9 +3027,9 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `register Success should return Success`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3060,9 +3060,9 @@ class AuthRepositoryTest {
|
|||
@Test
|
||||
fun `register returns CaptchaRequired captchaKeys empty should return Error no message`() =
|
||||
runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3101,9 +3101,9 @@ class AuthRepositoryTest {
|
|||
@Test
|
||||
fun `register returns CaptchaRequired captchaKeys should return CaptchaRequired`() =
|
||||
runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3141,9 +3141,9 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `register Failure should return Error with no message`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3173,9 +3173,9 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `register returns Invalid should return Error with invalid message`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3205,9 +3205,9 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `register returns Invalid should return Error with first message in map`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -3242,9 +3242,9 @@ class AuthRepositoryTest {
|
|||
|
||||
@Test
|
||||
fun `register returns Error body should return Error with message`() = runTest {
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
accountsService.register(
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
|
@ -4108,7 +4108,7 @@ class AuthRepositoryTest {
|
|||
fun `resendVerificationCodeEmail uses cached request data to make api call`() = runTest {
|
||||
// Attempt a normal login with a two factor error first, so that the necessary
|
||||
// data will be cached.
|
||||
coEvery { accountsService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
@ -4128,7 +4128,7 @@ class AuthRepositoryTest {
|
|||
.asSuccess()
|
||||
val firstResult = repository.login(email = EMAIL, password = PASSWORD, captchaToken = null)
|
||||
assertEquals(LoginResult.TwoFactorRequired, firstResult)
|
||||
coVerify { accountsService.preLogin(email = EMAIL) }
|
||||
coVerify { identityService.preLogin(email = EMAIL) }
|
||||
coVerify {
|
||||
identityService.getToken(
|
||||
email = EMAIL,
|
||||
|
|
Loading…
Add table
Reference in a new issue