diff --git a/client/manager.go b/client/manager.go index 6b0d9c5de05..b4c54cb3b2a 100644 --- a/client/manager.go +++ b/client/manager.go @@ -31,7 +31,7 @@ type Filter struct { type Manager interface { Storage - Authenticate(ctx context.Context, id string, secret []byte) (*Client, error) + AuthenticateClient(ctx context.Context, id string, secret []byte) (*Client, error) } type Storage interface { diff --git a/client/manager_test_helpers.go b/client/manager_test_helpers.go index 2f88ae06ddc..b47b78de88e 100644 --- a/client/manager_test_helpers.go +++ b/client/manager_test_helpers.go @@ -52,11 +52,11 @@ func TestHelperClientAuthenticate(k string, m Manager) func(t *testing.T) { RedirectURIs: []string{"http://redirect"}, })) - c, err := m.Authenticate(ctx, "1234321", []byte("secret1")) + c, err := m.AuthenticateClient(ctx, "1234321", []byte("secret1")) require.Error(t, err) require.Nil(t, c) - c, err = m.Authenticate(ctx, "1234321", []byte("secret")) + c, err = m.AuthenticateClient(ctx, "1234321", []byte("secret")) require.NoError(t, err) assert.Equal(t, "1234321", c.GetID()) } diff --git a/internal/httpclient/api_oidc.go b/internal/httpclient/api_oidc.go index 814348a1376..6ef2ee58a70 100644 --- a/internal/httpclient/api_oidc.go +++ b/internal/httpclient/api_oidc.go @@ -643,7 +643,7 @@ GetOidcUserInfo OpenID Connect Userinfo This endpoint returns the payload of the ID Token, including `session.id_token` values, of the provided OAuth 2.0 Access Token's consent request. -In the case of authentication error, a WWW-Authenticate header might be set in the response +In the case of authentication error, a WWW-AuthenticateClient header might be set in the response with more information about the error. See [the spec](https://datatracker.ietf.org/doc/html/rfc6750#section-3) for more details about header format. diff --git a/internal/kratos/fake_kratos.go b/internal/kratos/fake_kratos.go index 9164b742f58..17141b046bc 100644 --- a/internal/kratos/fake_kratos.go +++ b/internal/kratos/fake_kratos.go @@ -22,13 +22,17 @@ func NewFake() *FakeKratos { return &FakeKratos{} } -func (f *FakeKratos) DisableSession(ctx context.Context, identityProviderSessionID string) error { +func (f *FakeKratos) DisableSession(_ context.Context, identityProviderSessionID string) error { f.DisableSessionWasCalled = true f.LastDisabledSession = identityProviderSessionID return nil } +func (f *FakeKratos) Authenticate(context.Context, string, string) error { + panic("missing") +} + func (f *FakeKratos) Reset() { f.DisableSessionWasCalled = false f.LastDisabledSession = "" diff --git a/internal/kratos/kratos.go b/internal/kratos/kratos.go index fc9d8bdc86e..57a097c9bce 100644 --- a/internal/kratos/kratos.go +++ b/internal/kratos/kratos.go @@ -8,6 +8,7 @@ import ( "fmt" "net/url" + "github.com/pkg/errors" "go.opentelemetry.io/otel/attribute" "github.com/ory/hydra/v2/driver/config" @@ -29,6 +30,7 @@ type ( } Client interface { DisableSession(ctx context.Context, identityProviderSessionID string) error + Authenticate(ctx context.Context, name, secret string) error } Default struct { dependencies @@ -39,6 +41,40 @@ func New(d dependencies) Client { return &Default{dependencies: d} } +func (k *Default) Authenticate(ctx context.Context, name, secret string) (err error) { + ctx, span := k.Tracer(ctx).Tracer().Start(ctx, "kratos.Authenticate") + otelx.End(span, &err) + + adminURL, ok := k.Config().KratosAdminURL(ctx) + span.SetAttributes(attribute.String("admin_url", fmt.Sprintf("%+v", adminURL))) + if !ok { + span.SetAttributes(attribute.Bool("skipped", true)) + span.SetAttributes(attribute.String("reason", "kratos admin url not set")) + + return errors.New("kratos admin url not set") + } + + configuration := k.clientConfiguration(ctx, adminURL) + if header := k.Config().KratosRequestHeader(ctx); header != nil { + configuration.HTTPClient.Transport = httpx.WrapTransportWithHeader(configuration.HTTPClient.Transport, header) + } + kratos := client.NewAPIClient(configuration) + flow, _, err := kratos.FrontendApi.CreateNativeLoginFlow(ctx).Execute() + if err != nil { + return err + } + + _, _, err = kratos.FrontendApi.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody(client.UpdateLoginFlowBody{ + UpdateLoginFlowWithPasswordMethod: &client.UpdateLoginFlowWithPasswordMethod{ + Method: "password", + Identifier: name, + Password: secret, + }, + }).Execute() + + return err +} + func (k *Default) DisableSession(ctx context.Context, identityProviderSessionID string) (err error) { ctx, span := k.Tracer(ctx).Tracer().Start(ctx, "kratos.DisableSession") otelx.End(span, &err) diff --git a/oauth2/handler.go b/oauth2/handler.go index 5662be5cc8e..80b86bc9083 100644 --- a/oauth2/handler.go +++ b/oauth2/handler.go @@ -593,7 +593,7 @@ type oidcUserInfo struct { // This endpoint returns the payload of the ID Token, including `session.id_token` values, of // the provided OAuth 2.0 Access Token's consent request. // -// In the case of authentication error, a WWW-Authenticate header might be set in the response +// In the case of authentication error, a WWW-AuthenticateClient header might be set in the response // with more information about the error. See [the spec](https://datatracker.ietf.org/doc/html/rfc6750#section-3) // for more details about header format. // diff --git a/persistence/sql/persister.go b/persistence/sql/persister.go index 908a4884b8d..ae4f7ce1825 100644 --- a/persistence/sql/persister.go +++ b/persistence/sql/persister.go @@ -17,6 +17,7 @@ import ( "github.com/ory/fosite/storage" "github.com/ory/hydra/v2/aead" "github.com/ory/hydra/v2/driver/config" + "github.com/ory/hydra/v2/internal/kratos" "github.com/ory/hydra/v2/persistence" "github.com/ory/hydra/v2/x" "github.com/ory/x/contextx" @@ -51,6 +52,7 @@ type ( ClientHasher() fosite.Hasher KeyCipher() *aead.AESGCM FlowCipher() *aead.XChaCha20Poly1305 + Kratos() kratos.Client contextx.Provider x.RegistryLogger x.TracingProvider diff --git a/persistence/sql/persister_authenticate.go b/persistence/sql/persister_authenticate.go new file mode 100644 index 00000000000..810e8ead370 --- /dev/null +++ b/persistence/sql/persister_authenticate.go @@ -0,0 +1,7 @@ +package sql + +import "context" + +func (p *Persister) Authenticate(ctx context.Context, name, secret string) error { + return p.r.Kratos().Authenticate(ctx, name, secret) +} diff --git a/persistence/sql/persister_client.go b/persistence/sql/persister_client.go index 34846ca1220..422d651bf4e 100644 --- a/persistence/sql/persister_client.go +++ b/persistence/sql/persister_client.go @@ -76,8 +76,8 @@ func (p *Persister) UpdateClient(ctx context.Context, cl *client.Client) (err er }) } -func (p *Persister) Authenticate(ctx context.Context, id string, secret []byte) (_ *client.Client, err error) { - ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.Authenticate") +func (p *Persister) AuthenticateClient(ctx context.Context, id string, secret []byte) (_ *client.Client, err error) { + ctx, span := p.r.Tracer(ctx).Tracer().Start(ctx, "persistence.sql.AuthenticateClient") defer otelx.End(span, &err) c, err := p.GetConcreteClient(ctx, id) diff --git a/persistence/sql/persister_nid_test.go b/persistence/sql/persister_nid_test.go index ef1649864c9..96f8c902ffc 100644 --- a/persistence/sql/persister_nid_test.go +++ b/persistence/sql/persister_nid_test.go @@ -158,11 +158,11 @@ func (s *PersisterTestSuite) TestAuthenticate() { client := &client.Client{ID: "client-id", Secret: "secret"} require.NoError(t, r.Persister().CreateClient(s.t1, client)) - actual, err := r.Persister().Authenticate(s.t2, "client-id", []byte("secret")) + actual, err := r.Persister().AuthenticateClient(s.t2, "client-id", []byte("secret")) require.Error(t, err) require.Nil(t, actual) - actual, err = r.Persister().Authenticate(s.t1, "client-id", []byte("secret")) + actual, err = r.Persister().AuthenticateClient(s.t1, "client-id", []byte("secret")) require.NoError(t, err) require.NotNil(t, actual) }) diff --git a/x/fosite_storer.go b/x/fosite_storer.go index e7de8603f6c..23654c519b9 100644 --- a/x/fosite_storer.go +++ b/x/fosite_storer.go @@ -22,6 +22,7 @@ type FositeStorer interface { pkce.PKCERequestStorage rfc7523.RFC7523KeyStorage verifiable.NonceManager + oauth2.ResourceOwnerPasswordCredentialsGrantStorage RevokeRefreshToken(ctx context.Context, requestID string) error