Skip to content

Commit

Permalink
[RKOTLIN-1091] Decouple authentication errors from server error messa…
Browse files Browse the repository at this point in the history
…ges (#1762)
  • Loading branch information
rorbech authored May 23, 2024
1 parent cd6cc63 commit 80bcec4
Show file tree
Hide file tree
Showing 8 changed files with 27 additions and 70 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This release will bump the Realm file format 24. Opening a file with an older fo
* Removed `LogConfiguration`. Log levels and custom loggers can be set with `RealmLog`. (Issue [#1691](https://github.com/realm/realm-kotlin/issues/1691) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1038))
* Removed deprecated `io.realm.kotlin.types.ObjectId`. Use `org.mongodb.kbson.BsonObjectId` or its type alias `org.mongodb.kbson.ObjectId` instead. (Issue [#1749](https://github.com/realm/realm-kotlin/issues/1749) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1082))
* Removed deprecated `RealmClass.isEmbedded`. Class embeddeness can be check with `RealmClassKind.EMBEDDED`. (Issue [#1753](https://github.com/realm/realm-kotlin/issues/1753) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1080))
* Some authentication related operations will no longer throw specialized `InvalidCredentialsException` and `CredentialsCannotBeLinkedException` but the more general `AuthException` and `ServiceException`. (Issue [#1763](https://github.com/realm/realm-kotlin/issues/1763)/[RKOTLIN-1091](https://jira.mongodb.org/browse/RKOTLIN-1091))
* [Sync] Removed deprecated methods `User.identity` and `User.provider`, user identities can be accessed with the already existing `User.identities`. (Issue [#1751](https://github.com/realm/realm-kotlin/issues/1751) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1083))
* [Sync] `App.allUsers` does no longer return a map, but only a list of users known locally. (Issue [#1751](https://github.com/realm/realm-kotlin/issues/1751) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1083))
* [Sync ]Removed deprecated `DiscardUnsyncedChangesStrategy.onError`. (Issue [#1755](https://github.com/realm/realm-kotlin/issues/1755) [JIRA](https://jira.mongodb.org/browse/RKOTLIN-1085))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import io.realm.kotlin.mongodb.annotations.ExperimentalEdgeServerApi
import io.realm.kotlin.mongodb.auth.EmailPasswordAuth
import io.realm.kotlin.mongodb.exceptions.AppException
import io.realm.kotlin.mongodb.exceptions.AuthException
import io.realm.kotlin.mongodb.exceptions.InvalidCredentialsException
import io.realm.kotlin.mongodb.internal.AppConfigurationImpl
import io.realm.kotlin.mongodb.internal.AppImpl
import io.realm.kotlin.mongodb.sync.Sync
Expand Down Expand Up @@ -115,10 +114,6 @@ public interface App {
*
* @param credentials the credentials representing the type of login.
* @return the logged in [User].
* @throws InvalidCredentialsException if the provided credentials were not correct. Note, only
* [AuthenticationProvider.EMAIL_PASSWORD], [AuthenticationProvider.API_KEY] and
* [AuthenticationProvider.JWT] can throw this exception. Other authentication providers throw
* an [AuthException] instead.
* @throws AuthException if a problem occurred when logging in. See the exception message for
* further details.
* @throws io.realm.kotlin.mongodb.exceptions.ServiceException for other failures that can happen when
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ public class BadRequestException internal constructor(message: String) : Service
* This exception is considered the top-level or "catch-all" for problems related to user account
* actions. The exact reason for the error can be found in [Throwable.message].
*
* Generally, this exception does not need to be caught as more specific subtypes are available.
* These will be documented for the relevant API methods.
* For some error scenarios there are more specific and descriptive subtypes available.
* These are documented for the relevant API methods where they can be thrown.
*
* @see UserAlreadyConfirmedException
* @see UserNotFoundException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,39 +172,14 @@ internal fun convertAppError(appError: AppError): Throwable {
// generic `ServiceException`'s.
when (appError.code) {
ErrorCode.RLM_ERR_INTERNAL_SERVER_ERROR -> {
if (msg.contains("linking an anonymous identity is not allowed") || // Trying to link an anonymous account to a named one.
msg.contains("linking a local-userpass identity is not allowed") // Trying to link two email logins with each other
) {
CredentialsCannotBeLinkedException(msg)
} else {
ServiceException(msg)
}
ServiceException(msg)
}
ErrorCode.RLM_ERR_INVALID_SESSION -> {
if (msg.contains("a user already exists with the specified provider")) {
CredentialsCannotBeLinkedException(msg)
} else {
ServiceException(msg)
}
ServiceException(msg)
}
ErrorCode.RLM_ERR_USER_DISABLED,
ErrorCode.RLM_ERR_AUTH_ERROR -> {
// Some auth providers return a generic AuthError when
// invalid credentials are presented. We make a best effort
// to map these to a more sensible `InvalidCredentialsExceptions`
if (msg.contains("invalid API key")) {
// API Key
// See https://github.com/10gen/baas/blob/master/authprovider/providers/apikey/provider.go
InvalidCredentialsException(msg)
} else if (msg.contains("invalid custom auth token:")) {
// Custom JWT
// See https://github.com/10gen/baas/blob/master/authprovider/providers/custom/provider.go
InvalidCredentialsException(msg)
} else {
// It does not look possible to reliably detect Facebook, Google and Apple
// invalid tokens: https://github.com/10gen/baas/blob/master/authprovider/providers/oauth2/oauth.go#L139
AuthException(msg)
}
AuthException(msg)
}
ErrorCode.RLM_ERR_USER_NOT_FOUND -> {
UserNotFoundException(msg)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ import io.realm.kotlin.mongodb.Functions
import io.realm.kotlin.mongodb.User
import io.realm.kotlin.mongodb.UserIdentity
import io.realm.kotlin.mongodb.auth.ApiKeyAuth
import io.realm.kotlin.mongodb.exceptions.CredentialsCannotBeLinkedException
import io.realm.kotlin.mongodb.exceptions.ServiceException
import io.realm.kotlin.mongodb.mongo.MongoClient
import kotlinx.coroutines.channels.Channel
import org.mongodb.kbson.ExperimentalKBsonSerializerApi
Expand Down Expand Up @@ -153,29 +151,17 @@ public class UserImpl(
if (state != User.State.LOGGED_IN) {
throw IllegalStateException("User must be logged in, in order to link credentials to it.")
}
try {
Channel<Result<User>>(1).use { channel ->
RealmInterop.realm_app_link_credentials(
app.nativePointer,
nativePointer,
(credentials as CredentialsImpl).nativePointer,
channelResultCallback<RealmUserPointer, User>(channel) { userPointer ->
UserImpl(userPointer, app)
}
)
channel.receive().getOrThrow()
return this
}
} catch (ex: ServiceException) {
// Linking an account with itself throws a different error code than other linking errors:
// It is unclear if this error is shared between other error scenarios, so for now,
// we remap the exception type here instead of in the generic handler in
// `RealmSyncUtils.kt`.
if (ex.message?.contains("[Service][InvalidSession(2)] a user already exists with the specified provider.") == true) {
throw CredentialsCannotBeLinkedException(ex.message!!)
} else {
throw ex
}
Channel<Result<User>>(1).use { channel ->
RealmInterop.realm_app_link_credentials(
app.nativePointer,
nativePointer,
(credentials as CredentialsImpl).nativePointer,
channelResultCallback<RealmUserPointer, User>(channel) { userPointer ->
UserImpl(userPointer, app)
}
)
channel.receive().getOrThrow()
return this
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import io.realm.kotlin.mongodb.LoggedOut
import io.realm.kotlin.mongodb.Removed
import io.realm.kotlin.mongodb.User
import io.realm.kotlin.mongodb.annotations.ExperimentalEdgeServerApi
import io.realm.kotlin.mongodb.exceptions.InvalidCredentialsException
import io.realm.kotlin.mongodb.exceptions.AuthException
import io.realm.kotlin.mongodb.exceptions.ServiceException
import io.realm.kotlin.mongodb.sync.SyncConfiguration
import io.realm.kotlin.test.mongodb.SyncServerConfig
Expand Down Expand Up @@ -144,7 +144,7 @@ class AppTests {
null
}
}?.let { credentials: Credentials ->
assertFailsWith<InvalidCredentialsException> {
assertFailsWith<AuthException> {
app.login(credentials)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,7 +369,7 @@ class CredentialsTests {
payload = mapOf("mail" to TestHelper.randomEmail(), "id" to 0)
)

assertFailsWithMessage<AuthException>("Error: Authentication failed.") {
assertFailsWithMessage<AuthException>("unauthorized") {
runBlocking {
app.login(credentials)
}
Expand All @@ -383,7 +383,7 @@ class CredentialsTests {
}
fail()
} catch (error: AppException) {
assertTrue(error.message!!.contains("authentication via"), error.message)
assertTrue(error.message!!.contains("unauthorized"), error.message)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,11 @@ class UserTests {
app.emailPasswordAuth.registerUser(otherEmail, otherPassword)
val credentials = Credentials.emailPassword(otherEmail, otherPassword)

assertFailsWith<CredentialsCannotBeLinkedException> {
assertFailsWith<ServiceException> {
anonUser.linkCredentials(credentials)
}.let {
assertTrue(
it.message!!.contains("linking a local-userpass identity is not allowed when one is already linked"),
it.message!!.contains("unauthorized"),
it.message
)
}
Expand All @@ -478,11 +478,11 @@ class UserTests {
val (email2, password2) = randomEmail() to "123456"
app.emailPasswordAuth.registerUser(email2, password2)
val credentials2 = Credentials.emailPassword(email2, password2)
assertFailsWith<CredentialsCannotBeLinkedException> {
assertFailsWith<ServiceException> {
emailUser1.linkCredentials(credentials2)
}.let {
assertTrue(
it.message!!.contains("linking a local-userpass identity is not allowed when one is already linked"),
it.message!!.contains("unauthorized"),
it.message
)
}
Expand Down Expand Up @@ -512,11 +512,11 @@ class UserTests {
app.emailPasswordAuth.registerUser(email, password)
val creds = Credentials.emailPassword(email, password)
app.login(creds)
assertFailsWith<CredentialsCannotBeLinkedException> {
assertFailsWith<ServiceException> {
anonUser.linkCredentials(creds)
}.let {
assertTrue(
it.message!!.contains("a user already exists with the specified provider"),
it.message!!.contains("unauthorized"),
it.message
)
}
Expand Down

0 comments on commit 80bcec4

Please sign in to comment.