Skip to content

Commit

Permalink
feat: handle concurrent refreshes and improve graceful refreshing
Browse files Browse the repository at this point in the history
This patch improves Ory Hydra's ability to deal with refresh flows which, for example, concurrently refresh the same token. Furthermore, graceful token refresh has been improved to handle a variety of edge cases and scenarios.
  • Loading branch information
aeneasr committed Dec 5, 2024
1 parent 4c13d5f commit 322fc5b
Show file tree
Hide file tree
Showing 56 changed files with 2,289 additions and 1,810 deletions.
12 changes: 6 additions & 6 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1101,11 +1101,11 @@
"examples": ["https://my-example.app/token-refresh-hook"],
"oneOf": [
{
"type": "string",
"format": "uri"
"$ref": "#/definitions/webhook_config"
},
{
"$ref": "#/definitions/webhook_config"
"type": "string",
"format": "uri"
}
]
},
Expand All @@ -1114,11 +1114,11 @@
"examples": ["https://my-example.app/token-hook"],
"oneOf": [
{
"type": "string",
"format": "uri"
"$ref": "#/definitions/webhook_config"
},
{
"$ref": "#/definitions/webhook_config"
"type": "string",
"format": "uri"
}
]
}
Expand Down
17 changes: 9 additions & 8 deletions aead/aead_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import (
"io"
"testing"

"github.com/ory/hydra/v2/aead"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/internal/testhelpers"

"github.com/pborman/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ory/hydra/v2/aead"
"github.com/ory/hydra/v2/driver/config"
)

func secret(t *testing.T) string {
Expand All @@ -43,7 +44,7 @@ func TestAEAD(t *testing.T) {
t.Run("case=without-rotation", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
a := NewCipher(c)

Expand All @@ -63,7 +64,7 @@ func TestAEAD(t *testing.T) {
t.Run("case=wrong-secret", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
a := NewCipher(c)

Expand All @@ -78,7 +79,7 @@ func TestAEAD(t *testing.T) {
t.Run("case=with-rotation", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
old := secret(t)
c.MustSet(ctx, config.KeyGetSystemSecret, []string{old})
a := NewCipher(c)
Expand Down Expand Up @@ -106,7 +107,7 @@ func TestAEAD(t *testing.T) {
t.Run("case=with-rotation-wrong-secret", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
a := NewCipher(c)

Expand All @@ -123,7 +124,7 @@ func TestAEAD(t *testing.T) {
t.Run("suite=with additional data", func(t *testing.T) {
t.Parallel()
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeyGetSystemSecret, []string{secret(t)})
a := NewCipher(c)

Expand Down
3 changes: 1 addition & 2 deletions client/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ import (
"github.com/stretchr/testify/require"

"github.com/ory/hydra/v2/client"
"github.com/ory/hydra/v2/internal"
)

type responseSnapshot struct {
Expand All @@ -56,7 +55,7 @@ func getClientID(body string) string {

func TestHandler(t *testing.T) {
ctx := context.Background()
reg := internal.NewMockedRegistry(t, &contextx.Default{})
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
h := client.NewHandler(reg)
reg.WithContextualizer(&contextx.TestContextualizer{})

Expand Down
8 changes: 4 additions & 4 deletions client/sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
"strings"
"testing"

"github.com/ory/hydra/v2/internal/testhelpers"

"github.com/ory/x/assertx"

"github.com/ory/x/ioutilx"
Expand All @@ -26,8 +28,6 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ory/hydra/v2/internal"

hydra "github.com/ory/hydra-client-go/v2"
"github.com/ory/hydra/v2/client"
)
Expand Down Expand Up @@ -63,11 +63,11 @@ var defaultIgnoreFields = []string{"client_id", "registration_access_token", "re

func TestClientSDK(t *testing.T) {
ctx := context.Background()
conf := internal.NewConfigurationWithDefaults()
conf := testhelpers.NewConfigurationWithDefaults()
conf.MustSet(ctx, config.KeySubjectTypesSupported, []string{"public"})
conf.MustSet(ctx, config.KeyDefaultClientScope, []string{"foo", "bar"})
conf.MustSet(ctx, config.KeyPublicAllowDynamicRegistration, true)
r := internal.NewRegistryMemory(t, conf, &contextx.Static{C: conf.Source(ctx)})
r := testhelpers.NewRegistryMemory(t, conf, &contextx.Static{C: conf.Source(ctx)})

routerAdmin := x.NewRouterAdmin(conf.AdminURL)
routerPublic := x.NewRouterPublic()
Expand Down
17 changes: 9 additions & 8 deletions client/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"strings"
"testing"

"github.com/ory/hydra/v2/internal/testhelpers"

"github.com/hashicorp/go-retryablehttp"

"github.com/ory/fosite"
Expand All @@ -24,17 +26,16 @@ import (

. "github.com/ory/hydra/v2/client"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/x"
"github.com/ory/x/contextx"
)

func TestValidate(t *testing.T) {
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeySubjectTypesSupported, []string{"pairwise", "public"})
c.MustSet(ctx, config.KeyDefaultClientScope, []string{"openid"})
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
v := NewValidator(reg)

testCtx := context.TODO()
Expand Down Expand Up @@ -186,7 +187,7 @@ func (f *fakeHTTP) HTTPClient(ctx context.Context, opts ...httpx.ResilientOption
}

func TestValidateSectorIdentifierURL(t *testing.T) {
reg := internal.NewMockedRegistry(t, &contextx.Default{})
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
var payload string

var h http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -268,8 +269,8 @@ const validJWKS = `

func TestValidateIPRanges(t *testing.T) {
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
c := testhelpers.NewConfigurationWithDefaults()
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})

v := NewValidator(reg)
c.MustSet(ctx, config.KeyClientHTTPNoPrivateIPRanges, true)
Expand All @@ -287,10 +288,10 @@ func TestValidateIPRanges(t *testing.T) {

func TestValidateDynamicRegistration(t *testing.T) {
ctx := context.Background()
c := internal.NewConfigurationWithDefaults()
c := testhelpers.NewConfigurationWithDefaults()
c.MustSet(ctx, config.KeySubjectTypesSupported, []string{"pairwise", "public"})
c.MustSet(ctx, config.KeyDefaultClientScope, []string{"openid"})
reg := internal.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})
reg := testhelpers.NewRegistryMemory(t, c, &contextx.Static{C: c.Source(ctx)})

testCtx := context.TODO()
v := NewValidator(reg)
Expand Down
3 changes: 1 addition & 2 deletions cmd/cmd_helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (

"github.com/ory/hydra/v2/client"
"github.com/ory/hydra/v2/driver"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/internal/testhelpers"
"github.com/ory/x/cmdx"
"github.com/ory/x/contextx"
Expand All @@ -40,7 +39,7 @@ func setupRoutes(t *testing.T, cmd *cobra.Command) (*httptest.Server, *httptest.
ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

reg := internal.NewMockedRegistry(t, &contextx.Default{})
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
public, admin := testhelpers.NewOAuth2Server(ctx, t, reg)

cmdx.RegisterHTTPClientFlags(cmd.Flags())
Expand Down
19 changes: 10 additions & 9 deletions consent/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,14 @@ import (
"testing"
"time"

"github.com/ory/hydra/v2/internal/testhelpers"

"github.com/stretchr/testify/require"

hydra "github.com/ory/hydra-client-go/v2"
"github.com/ory/hydra/v2/client"
. "github.com/ory/hydra/v2/consent"
"github.com/ory/hydra/v2/flow"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/x"
"github.com/ory/x/contextx"
"github.com/ory/x/pointerx"
Expand All @@ -42,8 +43,8 @@ func TestGetLogoutRequest(t *testing.T) {
challenge := "challenge" + key
requestURL := "http://192.0.2.1"

conf := internal.NewConfigurationWithDefaults()
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
conf := testhelpers.NewConfigurationWithDefaults()
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})

if tc.exists {
cl := &client.Client{ID: "client" + key}
Expand Down Expand Up @@ -97,8 +98,8 @@ func TestGetLoginRequest(t *testing.T) {
challenge := "challenge" + key
requestURL := "http://192.0.2.1"

conf := internal.NewConfigurationWithDefaults()
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
conf := testhelpers.NewConfigurationWithDefaults()
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})

if tc.exists {
cl := &client.Client{ID: "client" + key}
Expand Down Expand Up @@ -163,8 +164,8 @@ func TestGetConsentRequest(t *testing.T) {
challenge := "challenge" + key
requestURL := "http://192.0.2.1"

conf := internal.NewConfigurationWithDefaults()
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
conf := testhelpers.NewConfigurationWithDefaults()
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})

if tc.exists {
cl := &client.Client{ID: "client" + key}
Expand Down Expand Up @@ -238,8 +239,8 @@ func TestGetLoginRequestWithDuplicateAccept(t *testing.T) {
challenge := "challenge"
requestURL := "http://192.0.2.1"

conf := internal.NewConfigurationWithDefaults()
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
conf := testhelpers.NewConfigurationWithDefaults()
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})

cl := &client.Client{ID: "client"}
require.NoError(t, reg.ClientManager().CreateClient(ctx, cl))
Expand Down
7 changes: 4 additions & 3 deletions consent/sdk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"testing"
"time"

"github.com/ory/hydra/v2/internal/testhelpers"

"github.com/ory/hydra/v2/consent/test"

hydra "github.com/ory/hydra-client-go/v2"
Expand All @@ -23,7 +25,6 @@ import (

. "github.com/ory/hydra/v2/consent"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/x"
"github.com/ory/x/contextx"
)
Expand All @@ -35,10 +36,10 @@ func makeID(base string, network string, key string) string {
func TestSDK(t *testing.T) {
ctx := context.Background()
network := "t1"
conf := internal.NewConfigurationWithDefaults()
conf := testhelpers.NewConfigurationWithDefaults()
conf.MustSet(ctx, config.KeyIssuerURL, "https://www.ory.sh")
conf.MustSet(ctx, config.KeyAccessTokenLifespan, time.Minute)
reg := internal.NewRegistryMemory(t, conf, &contextx.Default{})
reg := testhelpers.NewRegistryMemory(t, conf, &contextx.Default{})

consentChallenge := func(f *Flow) string { return x.Must(f.ToConsentChallenge(ctx, reg)) }
consentVerifier := func(f *Flow) string { return x.Must(f.ToConsentVerifier(ctx, reg)) }
Expand Down
3 changes: 1 addition & 2 deletions consent/strategy_logout_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
hydra "github.com/ory/hydra-client-go/v2"
"github.com/ory/hydra/v2/client"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal"
"github.com/ory/hydra/v2/internal/testhelpers"
"github.com/ory/x/contextx"
"github.com/ory/x/ioutilx"
Expand All @@ -37,7 +36,7 @@ import (
func TestLogoutFlows(t *testing.T) {
ctx := context.Background()
fakeKratos := kratos.NewFake()
reg := internal.NewMockedRegistry(t, &contextx.Default{})
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque")
reg.Config().MustSet(ctx, config.KeyConsentRequestMaxAge, time.Hour)

Expand Down
3 changes: 1 addition & 2 deletions consent/strategy_oauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,11 @@ import (
hydra "github.com/ory/hydra-client-go/v2"
"github.com/ory/hydra/v2/client"
"github.com/ory/hydra/v2/driver/config"
"github.com/ory/hydra/v2/internal"
)

func TestStrategyLoginConsentNext(t *testing.T) {
ctx := context.Background()
reg := internal.NewMockedRegistry(t, &contextx.Default{})
reg := testhelpers.NewMockedRegistry(t, &contextx.Default{})
reg.Config().MustSet(ctx, config.KeyAccessTokenStrategy, "opaque")
reg.Config().MustSet(ctx, config.KeyConsentRequestMaxAge, time.Hour)
reg.Config().MustSet(ctx, config.KeyConsentRequestMaxAge, time.Hour)
Expand Down
2 changes: 2 additions & 0 deletions consent/test/manager_test_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ func ManagerTests(deps Deps, m consent.Manager, clientManager client.Manager, fo
require.NoError(t, fositeManager.CreateRefreshTokenSession(
ctx,
makeID("", network, "rrva1"),
"",
&fosite.Request{Client: cr1.Client, ID: crr1.ID, RequestedAt: time.Now(), Session: &oauth2.Session{DefaultSession: openid.NewDefaultSession()}},
))
require.NoError(t, fositeManager.CreateAccessTokenSession(
Expand All @@ -693,6 +694,7 @@ func ManagerTests(deps Deps, m consent.Manager, clientManager client.Manager, fo
require.NoError(t, fositeManager.CreateRefreshTokenSession(
ctx,
makeID("", network, "rrva2"),
"",
&fosite.Request{Client: cr2.Client, ID: crr2.ID, RequestedAt: time.Now(), Session: &oauth2.Session{DefaultSession: openid.NewDefaultSession()}},
))

Expand Down
8 changes: 4 additions & 4 deletions cypress/integration/oauth2/refresh_token.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,13 @@ describe("The OAuth 2.0 Refresh Token Grant", function () {
return cy
.refreshTokenBrowser(client, originalToken)
.then((response) => {
expect(response.status).to.eq(401)
expect(response.body.error).to.eq("token_inactive")
expect(response.status).to.eq(400)
expect(response.body.error).to.eq("invalid_grant")
})
.then(() => cy.refreshTokenBrowser(client, refreshedToken))
.then((response) => {
expect(response.status).to.eq(401)
expect(response.body.error).to.eq("token_inactive")
expect(response.status).to.eq(400)
expect(response.body.error).to.eq("invalid_grant")
})
},
)
Expand Down
Loading

0 comments on commit 322fc5b

Please sign in to comment.