Skip to content

Commit

Permalink
Handle HTTP Gone responses from Hydra
Browse files Browse the repository at this point in the history
This is a common error if a user resubmits a form.
  • Loading branch information
Brutus5000 committed Jul 22, 2022
1 parent a087ab1 commit 4b01c7d
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 3 deletions.
37 changes: 34 additions & 3 deletions src/main/kotlin/com/faforever/userservice/domain/UserService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,15 @@ import com.faforever.userservice.hydra.HydraService
import com.faforever.userservice.security.OAuthScope
import io.micronaut.context.annotation.ConfigurationProperties
import io.micronaut.context.annotation.Context
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.http.uri.UriBuilder
import jakarta.inject.Singleton
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.security.crypto.password.PasswordEncoder
import reactor.core.publisher.Mono
import reactor.kotlin.core.publisher.onErrorResume
import reactor.kotlin.core.publisher.switchIfEmpty
import reactor.kotlin.core.publisher.toMono
import reactor.kotlin.core.util.function.component1
Expand All @@ -19,6 +22,7 @@ import sh.ory.hydra.model.AcceptLoginRequest
import sh.ory.hydra.model.ConsentRequestSession
import sh.ory.hydra.model.GenericError
import sh.ory.hydra.model.LoginRequest
import sh.ory.hydra.model.RequestWasHandledResponse
import java.time.LocalDateTime
import java.time.OffsetDateTime
import javax.validation.constraints.NotNull
Expand Down Expand Up @@ -71,7 +75,10 @@ class UserService(
fun findUserBySubject(subject: String) =
userRepository.findById(subject.toInt())

private fun checkLoginThrottlingRequired(ip: String) = loginLogRepository.findFailedAttemptsByIpAfterDate(ip, LocalDateTime.now().minusDays(securityProperties.failedLoginDaysToCheck))
private fun checkLoginThrottlingRequired(ip: String) = loginLogRepository.findFailedAttemptsByIpAfterDate(
ip,
LocalDateTime.now().minusDays(securityProperties.failedLoginDaysToCheck)
)
.map {
val accountsAffected = it.accountsAffected ?: 0
val totalFailedAttempts = it.totalAttempts ?: 0
Expand Down Expand Up @@ -112,8 +119,12 @@ class UserService(
internalLogin(challenge, usernameOrEmail, password, ip, loginRequest)
}
}
}
.onErrorResume { error ->
}.onErrorResume(HttpClientResponseException::class) { exception ->
handleOryGoneRedirect(exception) { redirectTo ->
LOG.debug("Login challenge $challenge was already solved, following Ory redirect")
LoginResult.SuccessfulLogin(redirectTo)
}
}.onErrorResume { error ->
LOG.debug("Login failed with technical error for challenge $challenge", error)
LoginResult.TechnicalError.toMono()
}
Expand Down Expand Up @@ -233,5 +244,25 @@ class UserService(
}
}.map {
it.redirectTo
}.onErrorResume(HttpClientResponseException::class) { exception ->
handleOryGoneRedirect(exception) { redirectTo ->
LOG.debug("Consent challenge $challenge was already solved, following Ory redirect")
redirectTo
}
}

private fun <T : Any> handleOryGoneRedirect(
exception: HttpClientResponseException,
redirectMapper: (String) -> T
): Mono<T> =
if (exception.status == HttpStatus.GONE) {
val mappedResponse = exception.response.getBody(RequestWasHandledResponse::class.java)
.map { redirectMapper(it.redirectTo) }
.orElseThrow()

Mono.just(mappedResponse)
} else {
// pass through unknown error
Mono.error(exception)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class RequiredRoleAndScopeRule : SecurityRule {
authentication: Authentication?
): Publisher<SecurityRuleResult> {
if (authentication == null) {
LOG.debug("No authentication available")
return SecurityRuleResult.REJECTED.toMono()
}

Expand Down
23 changes: 23 additions & 0 deletions src/main/kotlin/sh/ory/hydra/model/RequestWasHandledResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* ORY Hydra
* Welcome to the ORY Hydra HTTP API documentation. You will find documentation for all HTTP APIs here.
*
* The version of the OpenAPI document: latest
* *
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
package sh.ory.hydra.model

import com.fasterxml.jackson.annotation.JsonProperty

/**
* * @param redirectTo Original request URL to which you should redirect the user if request was already handled.
*/

data class RequestWasHandledResponse(
/* Original request URL to which you should redirect the user if request was already handled. */
@field:JsonProperty("redirect_to")
val redirectTo: kotlin.String
)

0 comments on commit 4b01c7d

Please sign in to comment.