diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt index b28176c05..5c7d5ba69 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/Auth.kt @@ -4,6 +4,7 @@ import io.github.jan.supabase.SupabaseClient import io.github.jan.supabase.exceptions.HttpRequestException import io.github.jan.supabase.exceptions.RestException import io.github.jan.supabase.gotrue.admin.AdminApi +import io.github.jan.supabase.gotrue.exception.AuthWeakPasswordException import io.github.jan.supabase.gotrue.mfa.MfaApi import io.github.jan.supabase.gotrue.providers.AuthProvider import io.github.jan.supabase.gotrue.providers.ExternalAuthConfigDefaults @@ -79,12 +80,14 @@ sealed interface Auth : MainPlugin, CustomSerializationPlugin { * * Example: * ```kotlin - * val result = gotrue.signUpWith(Email) { + * val result = auth.signUpWith(Email) { * email = "example@email.com" * password = "password" * } + * ``` * or - * gotrue.signUpWith(Google) // Opens the browser to login with google + * ```kotlin + * auth.signUpWith(Google) // Opens the browser to login with google * ``` * * @param provider the provider to use for signing up. E.g. [Email], [Phone] or [Google] @@ -94,6 +97,7 @@ sealed interface Auth : MainPlugin, CustomSerializationPlugin { * @throws RestException or one of its subclasses if receiving an error response * @throws HttpRequestTimeoutException if the request timed out * @throws HttpRequestException on network related issues + * @throws AuthWeakPasswordException if using the [Email] or [Phone] provider and the password is too weak. You can get the reasons via [AuthWeakPasswordException.reasons] */ suspend fun > signUpWith( provider: Provider, @@ -106,12 +110,14 @@ sealed interface Auth : MainPlugin, CustomSerializationPlugin { * * Example: * ```kotlin - * val result = gotrue.signInWith(Email) { + * val result = auth.signInWith(Email) { * email = "example@email.com" * password = "password" * } + * ``` * or - * gotrue.signInWith(Google) // Opens the browser to login with google + * ```kotlin + * auth.signInWith(Google) // Opens the browser to login with google * ``` * * @param provider the provider to use for signing in. E.g. [Email], [Phone] or [Google] diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt index 02a664fde..d4ce55d2e 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/AuthImpl.kt @@ -10,6 +10,8 @@ import io.github.jan.supabase.exceptions.UnauthorizedRestException import io.github.jan.supabase.exceptions.UnknownRestException import io.github.jan.supabase.gotrue.admin.AdminApi import io.github.jan.supabase.gotrue.admin.AdminApiImpl +import io.github.jan.supabase.gotrue.exception.AuthSessionMissingException +import io.github.jan.supabase.gotrue.exception.AuthWeakPasswordException import io.github.jan.supabase.gotrue.mfa.MfaApi import io.github.jan.supabase.gotrue.mfa.MfaApiImpl import io.github.jan.supabase.gotrue.providers.AuthProvider @@ -460,6 +462,7 @@ internal class AuthImpl( override suspend fun parseErrorResponse(response: HttpResponse): RestException { val errorBody = response.bodyOrNull() ?: GoTrueErrorResponse("Unknown error", "") + checkErrorCodes(errorBody)?.let { return it } return when (response.status) { HttpStatusCode.Unauthorized -> UnauthorizedRestException( errorBody.error, @@ -471,7 +474,6 @@ internal class AuthImpl( response, errorBody.description ) - HttpStatusCode.UnprocessableEntity -> BadRequestRestException( errorBody.error, response, @@ -481,6 +483,20 @@ internal class AuthImpl( } } + private fun checkErrorCodes(error: GoTrueErrorResponse): RestException? { + return when (error.error) { + AuthWeakPasswordException.CODE -> AuthWeakPasswordException(error.error, error.weakPassword?.reasons ?: emptyList()) + AuthSessionMissingException.CODE -> { + authScope.launch { + Auth.logger.e { "Received session not found api error. Clearing session..." } + clearSession() + } + AuthSessionMissingException() + } + else -> null + } + } + @OptIn(SupabaseExperimental::class) override fun getOAuthUrl( provider: OAuthProvider, diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt index 807bd882e..8ba6e887e 100644 --- a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/GoTrueErrorResponse.kt @@ -6,16 +6,24 @@ import kotlinx.serialization.builtins.serializer import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonDecoder +import kotlinx.serialization.json.decodeFromJsonElement import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonPrimitive @Serializable(with = GoTrueErrorResponse.Companion::class) internal data class GoTrueErrorResponse( val error: String, - val description: String? + val description: String? = null, + val weakPassword: WeakPassword? = null ) { + @Serializable + data class WeakPassword( + val reasons: List + ) + companion object : KSerializer { override val descriptor = buildClassSerialDescriptor("GoTrueErrorResponse") { @@ -25,9 +33,12 @@ internal data class GoTrueErrorResponse( override fun deserialize(decoder: Decoder): GoTrueErrorResponse { decoder as JsonDecoder val json = decoder.decodeJsonElement() - val error = json.jsonObject["error"]?.jsonPrimitive?.content ?: json.jsonObject["msg"]?.jsonPrimitive?.content ?: json.toString() - val description = json.jsonObject["error_description"]?.jsonPrimitive?.content - return GoTrueErrorResponse(error, description) + val error = json.jsonObject["error_code"]?.jsonPrimitive?.content ?: "unknown_error" + val description = json.jsonObject["error_description"]?.jsonPrimitive?.content ?: json.jsonObject["msg"]?.jsonPrimitive?.content ?: json.jsonObject["message"]?.jsonPrimitive?.content ?: json.toString() + val weakPassword = if(json.jsonObject.containsKey("weak_password")) { + Json.decodeFromJsonElement(json.jsonObject["weak_password"]!!) + } else null + return GoTrueErrorResponse(error, description, weakPassword) } override fun serialize(encoder: Encoder, value: GoTrueErrorResponse) { diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt new file mode 100644 index 000000000..9ab8a56ba --- /dev/null +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthRestException.kt @@ -0,0 +1,12 @@ +package io.github.jan.supabase.gotrue.exception + +import io.github.jan.supabase.exceptions.RestException + +/** + * Base class for rest exceptions thrown by the Auth API. + */ +open class AuthRestException(errorCode: String, message: String): RestException( + error = errorCode, + description = null, + message = message +) \ No newline at end of file diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt new file mode 100644 index 000000000..64c4b3cd2 --- /dev/null +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthSessionMissingException.kt @@ -0,0 +1,15 @@ +package io.github.jan.supabase.gotrue.exception + +/** + * Exception thrown when a session is not found. + */ +class AuthSessionMissingException: AuthRestException( + errorCode = CODE, + message = "Session not found. This can happen if the user was logged out or deleted." +) { + + internal companion object { + const val CODE = "session_not_found" + } + +} \ No newline at end of file diff --git a/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt new file mode 100644 index 000000000..198138366 --- /dev/null +++ b/GoTrue/src/commonMain/kotlin/io/github/jan/supabase/gotrue/exception/AuthWeakPasswordException.kt @@ -0,0 +1,20 @@ +package io.github.jan.supabase.gotrue.exception + +/** + * Exception thrown when a session is not found. + * @param description The description of the exception. + * @param reasons The reasons why the password is weak. + */ +class AuthWeakPasswordException( + description: String, + val reasons: List +) : AuthRestException( + CODE, + description, +) { + + internal companion object { + const val CODE = "weak_password" + } + +} \ No newline at end of file diff --git a/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt b/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt index 9daba2088..21c15075b 100644 --- a/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt +++ b/src/commonMain/kotlin/io/github/jan/supabase/exceptions/RestException.kt @@ -5,10 +5,16 @@ import io.ktor.client.statement.request /** * Base class for all response-related exceptions - * @param error The error returned by supabase - * @param description The error description returned by supabase + * + * Plugins may extend this class to provide more specific exceptions + * @param error The error returned by Supabase + * @param description The error description returned by Supabase + * @see UnauthorizedRestException + * @see BadRequestRestException + * @see NotFoundRestException + * @see UnknownRestException */ -sealed class RestException(val error: String, val description: String?, message: String): Exception(message) { +open class RestException(val error: String, val description: String?, message: String): Exception(message) { constructor(error: String, response: HttpResponse, message: String? = null): this(error, message, """ $error${message?.let { " ($it)" } ?: ""}