snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped

This commit is contained in:
upstream-mirror
2026-09-15 21:19:12 +05:00
commit c60cc7ca75
8092 changed files with 394160 additions and 0 deletions

70
hydra/fake.go Normal file
View File

@@ -0,0 +1,70 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package hydra
import (
"context"
"errors"
"github.com/ory/herodot"
hydraclientgo "github.com/ory/hydra-client-go/v2"
)
const (
FakeInvalidLoginChallenge = "2e98454e-031b-4870-9ad6-8517df1ce604"
FakeValidLoginChallenge = "5ff59a39-ecc5-467e-bb10-26644c0700ee"
FakePostLoginURL = "https://www.example.com/fake-post-login"
FakeClientID = "fake-oauth2-client-id"
FakeClientName = "Fake OAuth2 Client"
FakeClientLogoURI = "https://www.example.com/logo.png"
)
var ErrFakeAcceptLoginRequestFailed = errors.New("failed to accept login request")
type FakeHydra struct {
Skip bool
RequestURL string
}
var _ Hydra = &FakeHydra{}
func NewFake() *FakeHydra {
return &FakeHydra{
RequestURL: "https://www.ory.com",
}
}
func (h *FakeHydra) AcceptLoginRequest(_ context.Context, params AcceptLoginRequestParams) (string, error) {
if params.SessionID == "" {
return "", errors.New("session id must not be empty")
}
switch params.LoginChallenge {
case FakeInvalidLoginChallenge:
return "", ErrFakeAcceptLoginRequestFailed
case FakeValidLoginChallenge:
return FakePostLoginURL, nil
default:
panic("unknown fake login_challenge " + params.LoginChallenge)
}
}
func (h *FakeHydra) GetLoginRequest(_ context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error) {
switch loginChallenge {
case FakeInvalidLoginChallenge:
return nil, herodot.ErrBadRequest().WithReasonf("Unable to get OAuth 2.0 Login Challenge.")
case FakeValidLoginChallenge:
return &hydraclientgo.OAuth2LoginRequest{
Challenge: loginChallenge,
RequestUrl: h.RequestURL,
Skip: h.Skip,
Client: hydraclientgo.OAuth2Client{
ClientId: hydraclientgo.PtrString(FakeClientID),
ClientName: hydraclientgo.PtrString(FakeClientName),
LogoUri: hydraclientgo.PtrString(FakeClientLogoURI),
},
}, nil
default:
panic("unknown fake login_challenge " + loginChallenge)
}
}

172
hydra/hydra.go Normal file
View File

@@ -0,0 +1,172 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package hydra
import (
"context"
"net/http"
"time"
"github.com/ory/x/httpx"
"github.com/ory/x/sqlxx"
"github.com/pkg/errors"
"github.com/ory/herodot"
hydraclientgo "github.com/ory/hydra-client-go/v2"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/session"
)
type (
hydraDependencies interface {
config.Provider
httpx.ClientProvider
}
Provider interface {
Hydra() Hydra
}
AcceptLoginRequestParams struct {
LoginChallenge string
IdentityID string
SessionID string
AuthenticationMethods session.AuthenticationMethods
}
Hydra interface {
AcceptLoginRequest(ctx context.Context, params AcceptLoginRequestParams) (string, error)
GetLoginRequest(ctx context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error)
}
DefaultHydra struct {
d hydraDependencies
}
)
func NewDefaultHydra(d hydraDependencies) *DefaultHydra {
return &DefaultHydra{
d: d,
}
}
func GetLoginChallengeID(conf *config.Config, r *http.Request) (sqlxx.NullString, error) {
if !r.URL.Query().Has("login_challenge") {
return "", nil
} else if conf.OAuth2ProviderURL(r.Context()) == nil {
return "", errors.WithStack(herodot.ErrMisconfiguration().WithReason("refusing to parse login_challenge query parameter because " + config.ViperKeyOAuth2ProviderURL + " is invalid or unset"))
}
loginChallenge := r.URL.Query().Get("login_challenge")
if loginChallenge == "" {
return "", errors.WithStack(herodot.ErrBadRequest().WithReason("the login_challenge parameter is present but empty"))
}
return sqlxx.NullString(loginChallenge), nil
}
func (h *DefaultHydra) getAdminURL(ctx context.Context) (string, error) {
u := h.d.Config().OAuth2ProviderURL(ctx)
if u == nil {
return "", errors.WithStack(herodot.ErrMisconfiguration().WithReason(config.ViperKeyOAuth2ProviderURL + " is not configured"))
}
return u.String(), nil
}
func (h *DefaultHydra) getAdminAPIClient(ctx context.Context) (hydraclientgo.OAuth2API, error) {
url, err := h.getAdminURL(ctx)
if err != nil {
return nil, err
}
configuration := hydraclientgo.NewConfiguration()
configuration.Servers = hydraclientgo.ServerConfigurations{{URL: url}}
client := h.d.HTTPClient(ctx).StandardClient()
if header := h.d.Config().OAuth2ProviderHeader(ctx); header != nil {
client.Transport = httpx.WrapTransportWithHeader(client.Transport, header)
}
configuration.HTTPClient = client
return hydraclientgo.NewAPIClient(configuration).OAuth2API, nil
}
func (h *DefaultHydra) AcceptLoginRequest(ctx context.Context, params AcceptLoginRequestParams) (string, error) {
remember := h.d.Config().SessionPersistentCookie(ctx)
rememberFor := int64(h.d.Config().SessionLifespan(ctx) / time.Second)
alr := hydraclientgo.NewAcceptOAuth2LoginRequest(params.IdentityID)
alr.IdentityProviderSessionId = &params.SessionID
alr.Remember = &remember
alr.RememberFor = &rememberFor
alr.Amr = []string{}
for _, r := range params.AuthenticationMethods {
alr.Amr = append(alr.Amr, string(r.Method))
}
aa, err := h.getAdminAPIClient(ctx)
if err != nil {
return "", err
}
resp, r, err := aa.AcceptOAuth2LoginRequest(ctx).LoginChallenge(params.LoginChallenge).AcceptOAuth2LoginRequest(*alr).Execute()
if err != nil {
innerErr := herodot.ErrInternalServerError().WithWrap(err).WithReasonf("Unable to accept OAuth 2.0 Login Challenge.")
if r != nil {
innerErr = innerErr.
WithDetail("status_code", r.StatusCode).
WithDebug(err.Error())
}
if openApiErr := new(hydraclientgo.GenericOpenAPIError); errors.As(err, &openApiErr) {
switch oauth2Err := openApiErr.Model().(type) {
case hydraclientgo.ErrorOAuth2:
innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint())
case *hydraclientgo.ErrorOAuth2:
innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint())
}
}
return "", errors.WithStack(innerErr)
}
return resp.RedirectTo, nil
}
func (h *DefaultHydra) GetLoginRequest(ctx context.Context, loginChallenge string) (*hydraclientgo.OAuth2LoginRequest, error) {
if loginChallenge == "" {
return nil, errors.WithStack(herodot.ErrBadRequest().WithReason("invalid login_challenge"))
}
aa, err := h.getAdminAPIClient(ctx)
if err != nil {
return nil, err
}
hlr, r, err := aa.GetOAuth2LoginRequest(ctx).LoginChallenge(loginChallenge).Execute()
if err != nil {
var innerErr *herodot.DefaultError
if r == nil || r.StatusCode >= 500 {
innerErr = herodot.ErrInternalServerError()
} else {
innerErr = herodot.ErrBadRequest()
}
innerErr = innerErr.WithReasonf("Unable to get OAuth 2.0 Login Challenge.")
if r != nil {
innerErr = innerErr.
WithDetail("status_code", r.StatusCode).
WithDebug(err.Error())
}
if openApiErr := new(hydraclientgo.GenericOpenAPIError); errors.As(err, &openApiErr) {
switch oauth2Err := openApiErr.Model().(type) {
case hydraclientgo.ErrorOAuth2:
innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint())
case *hydraclientgo.ErrorOAuth2:
innerErr = innerErr.WithDetail("oauth2_error_hint", oauth2Err.GetErrorHint())
}
}
return nil, errors.WithStack(innerErr)
}
return hlr, nil
}

105
hydra/hydra_test.go Normal file
View File

@@ -0,0 +1,105 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package hydra_test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/hydra"
"github.com/ory/x/configx"
"github.com/ory/x/contextx"
"github.com/ory/x/logrusx"
"github.com/ory/x/sqlxx"
"github.com/ory/x/urlx"
)
func requestFromChallenge(s string) *http.Request {
return &http.Request{URL: urlx.ParseOrPanic("https://hydra?login_challenge=" + s)}
}
func TestGetLoginChallengeID(t *testing.T) {
uuidChallenge := "b346a452-e8fb-4828-8ef8-a4dbc98dc23a"
blobChallenge := "1337deadbeefcafe"
defaultConfig := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation())
configWithHydra := config.MustNew(t, logrusx.New("", ""), &contextx.Default{}, configx.SkipValidation(), configx.WithValues(map[string]interface{}{
config.ViperKeyOAuth2ProviderURL: "https://hydra",
}))
type args struct {
conf *config.Config
r *http.Request
}
tests := []struct {
name string
args args
want string
assertErr assert.ErrorAssertionFunc
}{
{
name: "no login challenge; hydra is not configured",
args: args{
conf: defaultConfig,
r: &http.Request{URL: urlx.ParseOrPanic("https://hydra")},
},
want: "",
assertErr: assert.NoError,
},
{
name: "no login challenge; hydra is configured",
args: args{
conf: configWithHydra,
r: &http.Request{URL: urlx.ParseOrPanic("https://hydra")},
},
want: "",
assertErr: assert.NoError,
},
{
name: "empty login challenge; hydra is configured",
args: args{
conf: configWithHydra,
r: requestFromChallenge(""),
},
want: "",
assertErr: assert.Error,
},
{
name: "login_challenge is present; Hydra is not configured",
args: args{
conf: defaultConfig,
r: requestFromChallenge(uuidChallenge),
},
want: "",
assertErr: assert.Error,
},
{
name: "login_challenge is present; hydra is configured",
args: args{
conf: configWithHydra,
r: requestFromChallenge(uuidChallenge),
},
want: uuidChallenge,
assertErr: assert.NoError,
},
{
name: "login_challenge is present & non-uuid; hydra is configured",
args: args{
conf: configWithHydra,
r: requestFromChallenge(blobChallenge),
},
want: blobChallenge,
assertErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := hydra.GetLoginChallengeID(tt.args.conf, tt.args.r)
tt.assertErr(t, err)
assert.Equal(t, sqlxx.NullString(tt.want), got)
})
}
}