mirror of
https://github.com/bitwarden/android.git
synced 2024-11-25 02:46:00 +03:00
[PM-6702] 1# Add service calls for email verification (#3617)
This commit is contained in:
parent
edb87202d2
commit
e717183239
11 changed files with 526 additions and 52 deletions
|
@ -5,8 +5,11 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJso
|
|||
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.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.Field
|
||||
|
@ -66,4 +69,14 @@ interface IdentityApi {
|
|||
|
||||
@POST("/accounts/register")
|
||||
suspend fun register(@Body body: RegisterRequestJson): Result<RegisterResponseJson.Success>
|
||||
|
||||
@POST("/accounts/register/finish")
|
||||
suspend fun registerFinish(
|
||||
@Body body: RegisterFinishRequestJson,
|
||||
): Result<RegisterResponseJson.Success>
|
||||
|
||||
@POST("/accounts/register/send-verification-email")
|
||||
suspend fun sendVerificationEmail(
|
||||
@Body body: SendVerificationEmailRequestJson,
|
||||
): Result<JsonPrimitive?>
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson.Keys
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body for register.
|
||||
*
|
||||
* @param email the email to be registered.
|
||||
* @param emailVerificationToken token used to finish the registration process.
|
||||
* @param masterPasswordHash the master password (encrypted).
|
||||
* @param masterPasswordHint the hint for the master password (nullable).
|
||||
* @param captchaResponse the captcha bypass token.
|
||||
* @param userSymmetricKey the user key for the request (encrypted).
|
||||
* @param userAsymmetricKeys a [Keys] object containing public and private keys.
|
||||
* @param kdfType the kdf type represented as an [Int].
|
||||
* @param kdfIterations the number of kdf iterations.
|
||||
*/
|
||||
@Serializable
|
||||
data class RegisterFinishRequestJson(
|
||||
@SerialName("email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("emailVerificationToken")
|
||||
val emailVerificationToken: String,
|
||||
|
||||
@SerialName("masterPasswordHash")
|
||||
val masterPasswordHash: String,
|
||||
|
||||
@SerialName("masterPasswordHint")
|
||||
val masterPasswordHint: String?,
|
||||
|
||||
@SerialName("captchaResponse")
|
||||
val captchaResponse: String?,
|
||||
|
||||
@SerialName("userSymmetricKey")
|
||||
val userSymmetricKey: String,
|
||||
|
||||
@SerialName("userAsymmetricKeys")
|
||||
val userAsymmetricKeys: Keys,
|
||||
|
||||
@SerialName("kdf")
|
||||
val kdfType: KdfTypeJson,
|
||||
|
||||
@SerialName("kdfIterations")
|
||||
val kdfIterations: UInt,
|
||||
) {
|
||||
|
||||
/**
|
||||
* A keys object containing public and private keys.
|
||||
*
|
||||
* @param publicKey the public key (encrypted).
|
||||
* @param encryptedPrivateKey the private key (encrypted).
|
||||
*/
|
||||
@Serializable
|
||||
data class Keys(
|
||||
@SerialName("publicKey")
|
||||
val publicKey: String,
|
||||
|
||||
@SerialName("encryptedPrivateKey")
|
||||
val encryptedPrivateKey: String,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Request body for send verification email.
|
||||
*
|
||||
* @param email the email to be registered.
|
||||
* @param name the name to be registered.
|
||||
* @param receiveMarketingEmails the answer to receive marketing emails.
|
||||
*/
|
||||
@Serializable
|
||||
data class SendVerificationEmailRequestJson(
|
||||
@SerialName("email")
|
||||
val email: String,
|
||||
|
||||
@SerialName("name")
|
||||
val name: String?,
|
||||
|
||||
@SerialName("receiveMarketingEmails")
|
||||
val receiveMarketingEmails: Boolean,
|
||||
)
|
|
@ -0,0 +1,47 @@
|
|||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* The response body for sending a verification email.
|
||||
*/
|
||||
@Serializable
|
||||
sealed class SendVerificationEmailResponseJson {
|
||||
|
||||
/**
|
||||
* Models a successful json response.
|
||||
*
|
||||
* @param emailVerificationToken the token to verify the email.
|
||||
*/
|
||||
@Serializable
|
||||
data class Success(
|
||||
val emailVerificationToken: String?,
|
||||
) : SendVerificationEmailResponseJson()
|
||||
|
||||
/**
|
||||
* Represents the json body of an invalid request.
|
||||
*
|
||||
* @param message
|
||||
* @param validationErrors a map where each value is a list of error messages for each key.
|
||||
* The values in the array should be used for display to the user, since the keys tend to come
|
||||
* back as nonsense. (eg: empty string key)
|
||||
*/
|
||||
@Serializable
|
||||
data class Invalid(
|
||||
@SerialName("message")
|
||||
val message: String?,
|
||||
|
||||
@SerialName("validationErrors")
|
||||
val validationErrors: Map<String, List<String>>?,
|
||||
) : SendVerificationEmailResponseJson()
|
||||
|
||||
/**
|
||||
* A different error with a message.
|
||||
*/
|
||||
@Serializable
|
||||
data class Error(
|
||||
@SerialName("Message")
|
||||
val message: String?,
|
||||
) : SendVerificationEmailResponseJson()
|
||||
}
|
|
@ -5,8 +5,10 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthM
|
|||
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.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||
|
||||
/**
|
||||
|
@ -58,4 +60,16 @@ interface IdentityService {
|
|||
* @param refreshToken The refresh token needed to obtain a new token.
|
||||
*/
|
||||
fun refreshTokenSynchronously(refreshToken: String): Result<RefreshTokenResponseJson>
|
||||
|
||||
/**
|
||||
* Send a verification email.
|
||||
*/
|
||||
suspend fun sendVerificationEmail(
|
||||
body: SendVerificationEmailRequestJson,
|
||||
): Result<String?>
|
||||
|
||||
/**
|
||||
* Register a new account to Bitwarden using email verification flow.
|
||||
*/
|
||||
suspend fun registerFinish(body: RegisterFinishRequestJson): Result<RegisterResponseJson>
|
||||
}
|
||||
|
|
|
@ -7,8 +7,10 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PreLoginRequestJso
|
|||
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.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
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
|
||||
|
@ -32,16 +34,21 @@ class IdentityServiceImpl(
|
|||
.register(body)
|
||||
.recoverCatching { throwable ->
|
||||
val bitwardenError = throwable.toBitwardenError()
|
||||
bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
|
||||
bitwardenError
|
||||
.parseErrorBodyOrNull<RegisterResponseJson.CaptchaRequired>(
|
||||
code = 400,
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
|
||||
)
|
||||
?: bitwardenError
|
||||
.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
|
||||
codes = listOf(400, 429),
|
||||
json = json,
|
||||
) ?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Error>(
|
||||
)
|
||||
?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Error>(
|
||||
code = 429,
|
||||
json = json,
|
||||
) ?: throw throwable
|
||||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
|
@ -101,4 +108,32 @@ class IdentityServiceImpl(
|
|||
refreshToken = refreshToken,
|
||||
)
|
||||
.executeForResult()
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
override suspend fun registerFinish(
|
||||
body: RegisterFinishRequestJson,
|
||||
): Result<RegisterResponseJson> =
|
||||
api
|
||||
.registerFinish(body)
|
||||
.recoverCatching { throwable ->
|
||||
val bitwardenError = throwable.toBitwardenError()
|
||||
bitwardenError
|
||||
.parseErrorBodyOrNull<RegisterResponseJson.Invalid>(
|
||||
codes = listOf(400, 429),
|
||||
json = json,
|
||||
)
|
||||
?: bitwardenError.parseErrorBodyOrNull<RegisterResponseJson.Error>(
|
||||
code = 429,
|
||||
json = json,
|
||||
)
|
||||
?: throw throwable
|
||||
}
|
||||
|
||||
override suspend fun sendVerificationEmail(
|
||||
body: SendVerificationEmailRequestJson,
|
||||
): Result<String?> {
|
||||
return api
|
||||
.sendVerificationEmail(body = body)
|
||||
.map { it?.content }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||
|
@ -253,6 +254,7 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
|||
email: String,
|
||||
masterPassword: String,
|
||||
masterPasswordHint: String?,
|
||||
emailVerificationToken: String? = null,
|
||||
captchaToken: String?,
|
||||
shouldCheckDataBreaches: Boolean,
|
||||
isMasterPasswordStrong: Boolean,
|
||||
|
@ -354,4 +356,13 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
|||
* policies for the current user.
|
||||
*/
|
||||
suspend fun validatePasswordAgainstPolicies(password: String): Boolean
|
||||
|
||||
/**
|
||||
* Send a verification email.
|
||||
*/
|
||||
suspend fun sendVerificationEmail(
|
||||
email: String,
|
||||
name: String,
|
||||
receiveMarketingEmails: Boolean,
|
||||
): SendVerificationEmailResult
|
||||
}
|
||||
|
|
|
@ -16,10 +16,12 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.GetTokenResponseJs
|
|||
import com.x8bit.bitwarden.data.auth.datasource.network.model.IdentityTokenAuthModel
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RefreshTokenResponseJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||
|
@ -50,6 +52,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserAccountTokens
|
||||
|
@ -723,6 +726,7 @@ class AuthRepositoryImpl(
|
|||
email: String,
|
||||
masterPassword: String,
|
||||
masterPasswordHint: String?,
|
||||
emailVerificationToken: String?,
|
||||
captchaToken: String?,
|
||||
shouldCheckDataBreaches: Boolean,
|
||||
isMasterPasswordStrong: Boolean,
|
||||
|
@ -751,6 +755,7 @@ class AuthRepositoryImpl(
|
|||
kdf = kdf,
|
||||
)
|
||||
.flatMap { registerKeyResponse ->
|
||||
if (emailVerificationToken == null) {
|
||||
identityService.register(
|
||||
body = RegisterRequestJson(
|
||||
email = email,
|
||||
|
@ -766,6 +771,24 @@ class AuthRepositoryImpl(
|
|||
kdfIterations = kdf.iterations,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
identityService.registerFinish(
|
||||
body = RegisterFinishRequestJson(
|
||||
email = email,
|
||||
masterPasswordHash = registerKeyResponse.masterPasswordHash,
|
||||
masterPasswordHint = masterPasswordHint,
|
||||
emailVerificationToken = emailVerificationToken,
|
||||
captchaResponse = captchaToken,
|
||||
userSymmetricKey = registerKeyResponse.encryptedUserKey,
|
||||
userAsymmetricKeys = RegisterFinishRequestJson.Keys(
|
||||
publicKey = registerKeyResponse.keys.public,
|
||||
encryptedPrivateKey = registerKeyResponse.keys.private,
|
||||
),
|
||||
kdfType = kdf.toKdfTypeJson(),
|
||||
kdfIterations = kdf.iterations,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
.fold(
|
||||
onSuccess = {
|
||||
|
@ -1159,6 +1182,28 @@ class AuthRepositoryImpl(
|
|||
): Boolean = passwordPolicies
|
||||
.all { validatePasswordAgainstPolicy(password, it) }
|
||||
|
||||
override suspend fun sendVerificationEmail(
|
||||
email: String,
|
||||
name: String,
|
||||
receiveMarketingEmails: Boolean,
|
||||
): SendVerificationEmailResult =
|
||||
identityService
|
||||
.sendVerificationEmail(
|
||||
SendVerificationEmailRequestJson(
|
||||
email = email,
|
||||
name = name,
|
||||
receiveMarketingEmails = receiveMarketingEmails,
|
||||
),
|
||||
)
|
||||
.fold(
|
||||
onSuccess = {
|
||||
SendVerificationEmailResult.Success(it)
|
||||
},
|
||||
onFailure = {
|
||||
SendVerificationEmailResult.Error(null)
|
||||
},
|
||||
)
|
||||
|
||||
@Suppress("CyclomaticComplexMethod")
|
||||
private suspend fun validatePasswordAgainstPolicy(
|
||||
password: String,
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
package com.x8bit.bitwarden.data.auth.repository.model
|
||||
|
||||
/**
|
||||
* Models result of sending a verification email.
|
||||
*/
|
||||
sealed class SendVerificationEmailResult {
|
||||
/**
|
||||
* Email sent succeeded.
|
||||
*
|
||||
* @param emailVerificationToken the token to verify the email.
|
||||
*/
|
||||
data class Success(
|
||||
val emailVerificationToken: String?,
|
||||
) : SendVerificationEmailResult()
|
||||
|
||||
/**
|
||||
* There was an error sending the email.
|
||||
*
|
||||
* @param errorMessage a message describing the error.
|
||||
*/
|
||||
data class Error(val errorMessage: String?) : SendVerificationEmailResult()
|
||||
}
|
|
@ -9,8 +9,10 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.MasterPasswordPoli
|
|||
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.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
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
|
||||
|
@ -132,15 +134,10 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
|
||||
@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)
|
||||
val response = MockResponse().setBody(CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
|
@ -150,21 +147,9 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
|
||||
@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)
|
||||
val response = MockResponse().setResponseCode(400).setBody(
|
||||
INVALID_MODEL_STATE_EMAIL_TAKEN_ERROR_JSON,
|
||||
)
|
||||
server.enqueue(response)
|
||||
val result = identityService.register(registerRequestBody)
|
||||
assertEquals(
|
||||
|
@ -178,13 +163,7 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
|
||||
@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)
|
||||
val response = MockResponse().setResponseCode(429).setBody(TOO_MANY_REQUEST_ERROR_JSON)
|
||||
server.enqueue(response)
|
||||
val result = identityService.register(registerRequestBody)
|
||||
assertEquals(
|
||||
|
@ -327,9 +306,74 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
assertTrue(result.isFailure)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerFinish success json should be Success`() = runTest {
|
||||
val expectedResponse = RegisterResponseJson.Success(
|
||||
captchaBypassToken = "mock_token",
|
||||
)
|
||||
val response = MockResponse().setBody(CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON)
|
||||
server.enqueue(response)
|
||||
assertEquals(
|
||||
expectedResponse.asSuccess(),
|
||||
identityService.registerFinish(registerFinishRequestBody),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerFinish failure with Invalid json should be Invalid`() = runTest {
|
||||
val response = MockResponse().setResponseCode(400).setBody(
|
||||
INVALID_MODEL_STATE_EMAIL_TAKEN_ERROR_JSON,
|
||||
)
|
||||
server.enqueue(response)
|
||||
val result = identityService.registerFinish(registerFinishRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Invalid(
|
||||
message = "The model state is invalid.",
|
||||
validationErrors = mapOf("" to listOf("Email '' is already taken.")),
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `registerFinish failure with Error json should return Error`() = runTest {
|
||||
val response = MockResponse().setResponseCode(429).setBody(TOO_MANY_REQUEST_ERROR_JSON)
|
||||
server.enqueue(response)
|
||||
val result = identityService.registerFinish(registerFinishRequestBody)
|
||||
assertEquals(
|
||||
RegisterResponseJson.Error(
|
||||
message = "Slow down! Too many requests. Try again soon.",
|
||||
),
|
||||
result.getOrThrow(),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail should return a string when response is populated success`() =
|
||||
runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(200).setBody(EMAIL_TOKEN))
|
||||
val result = identityService.sendVerificationEmail(SEND_VERIFICATION_EMAIL_REQUEST)
|
||||
assertEquals(JsonPrimitive(EMAIL_TOKEN).content.asSuccess(), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail should return null when response is empty success`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(204))
|
||||
val result = identityService.sendVerificationEmail(SEND_VERIFICATION_EMAIL_REQUEST)
|
||||
assertEquals(null.asSuccess(), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail should return an error when response is an error`() = runTest {
|
||||
server.enqueue(MockResponse().setResponseCode(400))
|
||||
val result = identityService.sendVerificationEmail(SEND_VERIFICATION_EMAIL_REQUEST)
|
||||
assertTrue(result.isFailure)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
||||
private const val REFRESH_TOKEN = "refreshToken"
|
||||
private const val EMAIL_TOKEN = "emailToken"
|
||||
private const val EMAIL = "email"
|
||||
private const val PASSWORD_HASH = "passwordHash"
|
||||
private val registerRequestBody = RegisterRequestJson(
|
||||
|
@ -345,6 +389,20 @@ class IdentityServiceTest : BaseServiceTest() {
|
|||
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||
kdfIterations = 600000U,
|
||||
)
|
||||
private val registerFinishRequestBody = RegisterFinishRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = "mockk_masterPasswordHash",
|
||||
masterPasswordHint = "mockk_masterPasswordHint",
|
||||
emailVerificationToken = "mock_emailVerificationToken",
|
||||
captchaResponse = "mockk_captchaResponse",
|
||||
userSymmetricKey = "mockk_key",
|
||||
userAsymmetricKeys = RegisterFinishRequestJson.Keys(
|
||||
publicKey = "mockk_publicKey",
|
||||
encryptedPrivateKey = "mockk_encryptedPrivateKey",
|
||||
),
|
||||
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||
kdfIterations = 600000U,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -484,8 +542,42 @@ private const val INVALID_LOGIN_JSON = """
|
|||
}
|
||||
"""
|
||||
|
||||
private const val TOO_MANY_REQUEST_ERROR_JSON = """
|
||||
{
|
||||
"Object": "error",
|
||||
"Message": "Slow down! Too many requests. Try again soon."
|
||||
}
|
||||
"""
|
||||
|
||||
private const val INVALID_MODEL_STATE_EMAIL_TAKEN_ERROR_JSON = """
|
||||
{
|
||||
"message": "The model state is invalid.",
|
||||
"validationErrors": {
|
||||
"": [
|
||||
"Email '' is already taken."
|
||||
]
|
||||
},
|
||||
"exceptionMessage": null,
|
||||
"exceptionStackTrace": null,
|
||||
"innerExceptionMessage": null,
|
||||
"object": "error"
|
||||
}
|
||||
"""
|
||||
|
||||
private const val CAPTCHA_BYPASS_TOKEN_RESPONSE_JSON = """
|
||||
{
|
||||
"captchaBypassToken": "mock_token"
|
||||
}
|
||||
"""
|
||||
|
||||
private val INVALID_LOGIN = GetTokenResponseJson.Invalid(
|
||||
errorModel = GetTokenResponseJson.Invalid.ErrorModel(
|
||||
errorMessage = "123",
|
||||
),
|
||||
)
|
||||
|
||||
private val SEND_VERIFICATION_EMAIL_REQUEST = SendVerificationEmailRequestJson(
|
||||
email = "email@example.com",
|
||||
name = "Name Example",
|
||||
receiveMarketingEmails = true,
|
||||
)
|
||||
|
|
|
@ -29,10 +29,12 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.PasswordHintRespon
|
|||
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.RegisterFinishRequestJson
|
||||
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.SendVerificationEmailRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.SetPasswordRequestJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserDecryptionOptionsJson
|
||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||
|
@ -67,6 +69,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.RegisterResult
|
|||
import com.x8bit.bitwarden.data.auth.repository.model.RequestOtpResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResendEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.ResetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SendVerificationEmailResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SetPasswordResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||
import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||
|
@ -3791,6 +3794,40 @@ class AuthRepositoryTest {
|
|||
assertEquals(RegisterResult.Error(errorMessage = "message"), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `register with email token Success should return Success`() = runTest {
|
||||
coEvery { identityService.preLogin(EMAIL) } returns PRE_LOGIN_SUCCESS.asSuccess()
|
||||
coEvery {
|
||||
identityService.registerFinish(
|
||||
body = RegisterFinishRequestJson(
|
||||
email = EMAIL,
|
||||
masterPasswordHash = PASSWORD_HASH,
|
||||
masterPasswordHint = null,
|
||||
emailVerificationToken = EMAIL_VERIFICATION_TOKEN,
|
||||
captchaResponse = null,
|
||||
userSymmetricKey = ENCRYPTED_USER_KEY,
|
||||
userAsymmetricKeys = RegisterFinishRequestJson.Keys(
|
||||
publicKey = PUBLIC_KEY,
|
||||
encryptedPrivateKey = PRIVATE_KEY,
|
||||
),
|
||||
kdfType = KdfTypeJson.PBKDF2_SHA256,
|
||||
kdfIterations = DEFAULT_KDF_ITERATIONS.toUInt(),
|
||||
),
|
||||
)
|
||||
} returns RegisterResponseJson.Success(captchaBypassToken = CAPTCHA_KEY).asSuccess()
|
||||
|
||||
val result = repository.register(
|
||||
email = EMAIL,
|
||||
masterPassword = PASSWORD,
|
||||
masterPasswordHint = null,
|
||||
emailVerificationToken = EMAIL_VERIFICATION_TOKEN,
|
||||
captchaToken = null,
|
||||
shouldCheckDataBreaches = false,
|
||||
isMasterPasswordStrong = true,
|
||||
)
|
||||
assertEquals(RegisterResult.Success(CAPTCHA_KEY), result)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `resetPassword Success should return Success`() = runTest {
|
||||
val currentPassword = "currentPassword"
|
||||
|
@ -5337,10 +5374,81 @@ class AuthRepositoryTest {
|
|||
assertFalse(repository.validatePasswordAgainstPolicies(password = "letters"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail success should return success`() = runTest {
|
||||
coEvery {
|
||||
identityService.sendVerificationEmail(
|
||||
SendVerificationEmailRequestJson(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
),
|
||||
)
|
||||
} returns EMAIL_VERIFICATION_TOKEN.asSuccess()
|
||||
|
||||
val result = repository.sendVerificationEmail(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
)
|
||||
assertEquals(
|
||||
SendVerificationEmailResult.Success(EMAIL_VERIFICATION_TOKEN),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail failure should return success if body null`() = runTest {
|
||||
coEvery {
|
||||
identityService.sendVerificationEmail(
|
||||
SendVerificationEmailRequestJson(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
),
|
||||
)
|
||||
} returns null.asSuccess()
|
||||
|
||||
val result = repository.sendVerificationEmail(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
)
|
||||
assertEquals(
|
||||
SendVerificationEmailResult.Success(null),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `sendVerificationEmail failure should return error`() = runTest {
|
||||
coEvery {
|
||||
identityService.sendVerificationEmail(
|
||||
SendVerificationEmailRequestJson(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
),
|
||||
)
|
||||
} returns Throwable("fail").asFailure()
|
||||
|
||||
val result = repository.sendVerificationEmail(
|
||||
email = EMAIL,
|
||||
name = NAME,
|
||||
receiveMarketingEmails = true,
|
||||
)
|
||||
assertEquals(
|
||||
SendVerificationEmailResult.Error(null),
|
||||
result,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val UNIQUE_APP_ID = "testUniqueAppId"
|
||||
private const val NAME = "Example Name"
|
||||
private const val EMAIL = "test@bitwarden.com"
|
||||
private const val EMAIL_2 = "test2@bitwarden.com"
|
||||
private const val EMAIL_VERIFICATION_TOKEN = "thisisanawesometoken"
|
||||
private const val PASSWORD = "password"
|
||||
private const val PASSWORD_HASH = "passwordHash"
|
||||
private const val ACCESS_TOKEN = "accessToken"
|
||||
|
|
Loading…
Reference in a new issue