Skip to content

Commit

Permalink
Update to Ory Hydra 2.x API
Browse files Browse the repository at this point in the history
  • Loading branch information
Brutus5000 committed Nov 9, 2024
1 parent ab4f350 commit 8c1712e
Show file tree
Hide file tree
Showing 23 changed files with 798 additions and 266 deletions.
5 changes: 5 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,8 @@ max_line_length=120
end_of_line=lf
ij_any_line_comment_add_space = true
ij_any_line_comment_at_first_column = false

# Disable wildcard imports entirely
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_packages_to_use_import_on_demand = unset
4 changes: 2 additions & 2 deletions generate-hydra-model.sh
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
#!/bin/sh

HYDRA_VERSION="v1.10.2"
HYDRA_VERSION="v2.2.0"

mkdir -p hydra-generated

docker run --user 1000 --rm -v "${PWD}/hydra-generated:/local" openapitools/openapi-generator-cli generate \
-i "https://raw.githubusercontent.com/ory/hydra/${HYDRA_VERSION}/spec/api.json" \
-g kotlin \
--additional-properties modelPackage=sh.ory.hydra.model,serializationLibrary=jackson,swaggerAnnotations=false,hideGenerationTimestamp=true \
--additional-properties modelPackage=sh.ory.hydra.model,serializationLibrary=jackson \
-o /local
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import jakarta.ws.rs.QueryParam
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient
import sh.ory.hydra.model.AcceptConsentRequest
import sh.ory.hydra.model.AcceptLoginRequest
import sh.ory.hydra.model.ConsentRequest
import sh.ory.hydra.model.AcceptOAuth2ConsentRequest
import sh.ory.hydra.model.AcceptOAuth2LoginRequest
import sh.ory.hydra.model.GenericError
import sh.ory.hydra.model.LoginRequest
import sh.ory.hydra.model.OAuth2TokenIntrospection
import sh.ory.hydra.model.IntrospectedOAuth2Token
import sh.ory.hydra.model.OAuth2ConsentRequest
import sh.ory.hydra.model.OAuth2LoginRequest
import sh.ory.hydra.model.OAuth2RedirectTo
import sh.ory.hydra.model.RejectOAuth2Request

@Path("/")
@Path("/admin")
@ApplicationScoped
@RegisterRestClient(configKey = "faf-ory-hydra")
interface HydraClient {
Expand All @@ -41,42 +43,42 @@ interface HydraClient {
// requesting a handled challenge throws HTTP 410 - Gone
@GET
@Path("/oauth2/auth/requests/login")
fun getLoginRequest(@QueryParam("login_challenge") @NotBlank challenge: String): LoginRequest
fun getLoginRequest(@QueryParam("login_challenge") @NotBlank challenge: String): OAuth2LoginRequest

@GET
@Path("/oauth2/auth/requests/consent")
fun getConsentRequest(@QueryParam("consent_challenge") @NotBlank challenge: String): ConsentRequest
fun getConsentRequest(@QueryParam("consent_challenge") @NotBlank challenge: String): OAuth2ConsentRequest

// accepting login request more than once throws HTTP 409 - Conflict
@PUT
@Path("/oauth2/auth/requests/login/accept")
fun acceptLoginRequest(
@QueryParam("login_challenge") @NotBlank challenge: String,
acceptLoginRequest: AcceptLoginRequest,
): RedirectResponse
acceptLoginRequest: AcceptOAuth2LoginRequest,
): OAuth2RedirectTo

@PUT
@Path("/oauth2/auth/requests/login/reject")
fun rejectLoginRequest(
@QueryParam("login_challenge") @NotBlank challenge: String,
error: GenericError,
): RedirectResponse
payload: RejectOAuth2Request,
): OAuth2RedirectTo

// accepting consent more than once does not cause an error
@PUT
@Path("/oauth2/auth/requests/consent/accept")
fun acceptConsentRequest(
@QueryParam("consent_challenge") @NotBlank challenge: String,
acceptConsentRequest: AcceptConsentRequest,
): RedirectResponse
acceptConsentRequest: AcceptOAuth2ConsentRequest,
): OAuth2RedirectTo

// rejecting consent more than once does not cause an error
@PUT
@Path("/oauth2/auth/requests/consent/reject")
fun rejectConsentRequest(
@QueryParam("consent_challenge") @NotBlank challenge: String,
error: GenericError,
): RedirectResponse
): OAuth2RedirectTo

@DELETE
@Path("/oauth2/auth/sessions/consent")
Expand All @@ -92,7 +94,7 @@ interface HydraClient {
fun introspectToken(
@FormParam("token") @NotBlank token: String,
@FormParam("scope") scope: String?,
): OAuth2TokenIntrospection
): IntrospectedOAuth2Token
}

class GoneException(override val message: String?) : RuntimeException(message)
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import jakarta.enterprise.context.ApplicationScoped
import jakarta.enterprise.inject.Produces
import jakarta.transaction.Transactional
import org.eclipse.microprofile.rest.client.inject.RestClient
import sh.ory.hydra.model.AcceptConsentRequest
import sh.ory.hydra.model.AcceptLoginRequest
import sh.ory.hydra.model.ConsentRequest
import sh.ory.hydra.model.ConsentRequestSession
import sh.ory.hydra.model.AcceptOAuth2ConsentRequest
import sh.ory.hydra.model.AcceptOAuth2ConsentRequestSession
import sh.ory.hydra.model.AcceptOAuth2LoginRequest
import sh.ory.hydra.model.GenericError
import sh.ory.hydra.model.LoginRequest
import sh.ory.hydra.model.OAuth2ConsentRequest
import sh.ory.hydra.model.OAuth2LoginRequest
import sh.ory.hydra.model.RejectOAuth2Request
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
Expand Down Expand Up @@ -53,14 +54,14 @@ class HydraService(
private const val HYDRA_ERROR_TECHNICAL_ERROR = "technical_error"
}

fun getLoginRequest(challenge: String): LoginRequest = hydraClient.getLoginRequest(challenge)
fun getLoginRequest(challenge: String): OAuth2LoginRequest = hydraClient.getLoginRequest(challenge)

@Transactional
fun login(challenge: String, usernameOrEmail: String, password: String, ip: IpAddress): LoginResponse {
val loginRequest = hydraClient.getLoginRequest(challenge)
val lobbyRequested = loginRequest.requestedScope.contains(OAuthScope.LOBBY)
val lobbyDefault =
loginRequest.requestedScope.isEmpty() && loginRequest.client.scope?.contains(OAuthScope.LOBBY) ?: false
val loginRequest = getLoginRequest(challenge)
val lobbyRequested = loginRequest.requestedScope?.contains(OAuthScope.LOBBY) ?: false
val lobbyDefault = loginRequest.requestedScope.isNullOrEmpty() &&
loginRequest.client.scope?.contains(OAuthScope.LOBBY) ?: false
val requiresGameOwnership = lobbyRequested || lobbyDefault

return when (val loginResult = loginService.login(usernameOrEmail, password, ip, requiresGameOwnership)) {
Expand All @@ -69,7 +70,7 @@ class HydraService(
is LoginResult.UserNoGameOwnership -> {
rejectLoginRequest(
challenge,
GenericError(
RejectOAuth2Request(
error = HYDRA_ERROR_NO_OWNERSHIP_VERIFICATION,
errorDescription = "You must prove game ownership to play",
statusCode = 403,
Expand All @@ -83,7 +84,7 @@ class HydraService(
"You are banned from FAF ${loginResult.expiresAt?.let { "until $it" } ?: "forever"}"
rejectLoginRequest(
challenge,
GenericError(
RejectOAuth2Request(
error = HYDRA_ERROR_USER_BANNED,
errorDescription = errorDescription,
statusCode = 403,
Expand All @@ -95,7 +96,7 @@ class HydraService(
is LoginResult.TechnicalError -> {
rejectLoginRequest(
challenge,
GenericError(
RejectOAuth2Request(
error = HYDRA_ERROR_TECHNICAL_ERROR,
errorDescription = "Something went wrong while logging in. Please try again",
statusCode = 500,
Expand All @@ -107,17 +108,17 @@ class HydraService(
is LoginResult.SuccessfulLogin -> {
val redirectResponse = hydraClient.acceptLoginRequest(
challenge,
AcceptLoginRequest(subject = loginResult.userId.toString()),
AcceptOAuth2LoginRequest(subject = loginResult.userId.toString()),
)
LoginResponse.SuccessfulLogin(RedirectTo(redirectResponse.redirectTo))
}
}
}

fun rejectLoginRequest(challenge: String, error: GenericError) {
fun rejectLoginRequest(challenge: String, request: RejectOAuth2Request) {
val redirectResponse = hydraClient.rejectLoginRequest(
challenge,
GenericError(
RejectOAuth2Request(
error = HYDRA_ERROR_TECHNICAL_ERROR,
errorDescription = "Something went wrong while logging in. Please try again",
statusCode = 500,
Expand All @@ -129,7 +130,7 @@ class HydraService(
)
}

fun getConsentRequest(challenge: String): ConsentRequest = hydraClient.getConsentRequest(challenge)
fun getConsentRequest(challenge: String): OAuth2ConsentRequest = hydraClient.getConsentRequest(challenge)

@Transactional
fun acceptConsentRequest(challenge: String): RedirectTo {
Expand All @@ -155,8 +156,8 @@ class HydraService(

val redirectResponse = hydraClient.acceptConsentRequest(
challenge,
AcceptConsentRequest(
session = ConsentRequestSession(
AcceptOAuth2ConsentRequest(
session = AcceptOAuth2ConsentRequestSession(
accessToken = context,
idToken = context,
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import com.vaadin.flow.component.orderedlayout.HorizontalLayout
import com.vaadin.flow.router.BeforeEnterEvent
import com.vaadin.flow.router.BeforeEnterObserver
import com.vaadin.flow.router.Route
import sh.ory.hydra.model.ConsentRequest
import sh.ory.hydra.model.OAuth2ConsentRequest

@Route("/oauth2/consent", layout = CardLayout::class)
class ConsentView(
Expand Down Expand Up @@ -51,7 +51,7 @@ class ConsentView(
add(socialIcons)
}

private fun setDetailsFromRequest(consentRequest: ConsentRequest) {
private fun setDetailsFromRequest(consentRequest: OAuth2ConsentRequest) {
consentRequest.client?.let { oAuthClientHeader.setClient(it) }

if (consentRequest.requestedScope.isNullOrEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,26 @@
/**
* 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.
*/
*
* Please note:
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* Do not edit this file manually.
*
*/

@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport",
)

package sh.ory.hydra.model

import com.fasterxml.jackson.annotation.JsonProperty
import io.quarkus.runtime.annotations.RegisterForReflection

/**
*
*
* @param context
* @param grantAccessTokenAudience
* @param grantScope
* @param handledAt
Expand All @@ -24,20 +29,29 @@ import io.quarkus.runtime.annotations.RegisterForReflection
* @param session
*/

@RegisterForReflection
data class AcceptConsentRequest(
data class AcceptOAuth2ConsentRequest(

@JsonProperty("context")
val context: kotlin.Any? = null,

@JsonProperty("grant_access_token_audience")
val grantAccessTokenAudience: kotlin.collections.List<kotlin.String>? = null,

@JsonProperty("grant_scope")
val grantScope: kotlin.collections.List<kotlin.String>? = null,

@JsonProperty("handled_at")
val handledAt: java.time.OffsetDateTime? = null,

/* Remember, if set to true, tells ORY Hydra to remember this consent authorization and reuse it if the same client asks the same user for the same, or a subset of, scope. */
@JsonProperty("remember")
val remember: kotlin.Boolean? = null,

/* RememberFor sets how long the consent authorization should be remembered for in seconds. If set to `0`, the authorization will be remembered indefinitely. */
@JsonProperty("remember_for")
val rememberFor: kotlin.Long? = null,

@JsonProperty("session")
val session: ConsentRequestSession? = null,
val session: AcceptOAuth2ConsentRequestSession? = null,

)
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
/**
* 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.
*/
*
* Please note:
* This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* Do not edit this file manually.
*
*/

@file:Suppress(
"ArrayInDataClass",
"EnumEntryName",
"RemoveRedundantQualifierName",
"UnusedImport",
)

package sh.ory.hydra.model

import com.fasterxml.jackson.annotation.JsonProperty
import io.quarkus.runtime.annotations.RegisterForReflection

/**
*
*
* @param accessToken AccessToken sets session data for the access and refresh token, as well as any future tokens issued by the refresh grant. Keep in mind that this data will be available to anyone performing OAuth 2.0 Challenge Introspection. If only your services can perform OAuth 2.0 Challenge Introspection, this is usually fine. But if third parties can access that endpoint as well, sensitive data from the session might be exposed to them. Use with care!
* @param idToken IDToken sets session data for the OpenID Connect ID token. Keep in mind that the session'id payloads are readable by anyone that has access to the ID Challenge. Use with care!
*/

@RegisterForReflection
data class ConsentRequestSession(
data class AcceptOAuth2ConsentRequestSession(

/* AccessToken sets session data for the access and refresh token, as well as any future tokens issued by the refresh grant. Keep in mind that this data will be available to anyone performing OAuth 2.0 Challenge Introspection. If only your services can perform OAuth 2.0 Challenge Introspection, this is usually fine. But if third parties can access that endpoint as well, sensitive data from the session might be exposed to them. Use with care! */
@JsonProperty("access_token")
val accessToken: kotlin.Any? = null,

/* IDToken sets session data for the OpenID Connect ID token. Keep in mind that the session'id payloads are readable by anyone that has access to the ID Challenge. Use with care! */
@JsonProperty("id_token")
val idToken: kotlin.Any? = null,

)
Loading

0 comments on commit 8c1712e

Please sign in to comment.