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
173 lines
5.2 KiB
Go
173 lines
5.2 KiB
Go
// 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 = ¶ms.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
|
|
}
|