feat: enable JSONNet templating for password migration hook (#4390)

This enables JSONNet body templating for the password migration hook.
There is also a significant refactoring of some internals around webhook config handling.
This commit is contained in:
Patrik 2025-04-30 16:25:09 +02:00 committed by GitHub
parent ea4da51f4d
commit b1628976a0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
113 changed files with 1523 additions and 1315 deletions

View File

@ -9,6 +9,8 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/pkg/errors"
"github.com/rs/cors"
"github.com/spf13/cobra"
@ -97,7 +99,7 @@ func servePublic(ctx context.Context, r driver.Registry, cmd *cobra.Command, slO
n.Use(r.PrometheusManager())
router := x.NewRouterPublic()
csrf := x.NewCSRFHandler(router, r)
csrf := nosurfx.NewCSRFHandler(router, r)
// we need to always load the CORS middleware even if it is disabled, to allow hot-enabling CORS
n.UseFunc(func(w http.ResponseWriter, req *http.Request, next http.HandlerFunc) {

View File

@ -31,7 +31,7 @@ func (c *courier) channels(ctx context.Context, id string) (Channel, error) {
}
return courierChannel, nil
case "http":
return newHttpChannel(channel.ID, channel.RequestConfig, c.deps), nil
return newHttpChannel(channel.ID, &channel.RequestConfig, c.deps), nil
default:
return nil, errors.Errorf("unknown courier channel type: %s", channel.Type)
}

View File

@ -7,6 +7,9 @@ import (
"fmt"
"net/http"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/gofrs/uuid"
"github.com/ory/herodot"
@ -29,7 +32,7 @@ type (
handlerDependencies interface {
x.WriterProvider
x.LoggingProvider
x.CSRFProvider
nosurfx.CSRFProvider
PersistenceProvider
config.Provider
}
@ -47,8 +50,8 @@ func NewHandler(r handlerDependencies) *Handler {
func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
h.r.CSRFHandler().IgnoreGlobs(x.AdminPrefix+AdminRouteListMessages, AdminRouteListMessages)
public.GET(x.AdminPrefix+AdminRouteListMessages, x.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+AdminRouteGetMessage, x.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+AdminRouteListMessages, redir.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+AdminRouteGetMessage, redir.RedirectToAdminRoute(h.r))
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {

View File

@ -5,11 +5,8 @@ package courier
import (
"context"
"encoding/json"
"fmt"
"github.com/tidwall/gjson"
"github.com/pkg/errors"
"github.com/ory/kratos/courier/template"
@ -22,7 +19,7 @@ import (
type (
httpChannel struct {
id string
requestConfig json.RawMessage
requestConfig *request.Config
d channelDependencies
}
channelDependencies interface {
@ -36,7 +33,7 @@ type (
var _ Channel = new(httpChannel)
func newHttpChannel(id string, requestConfig json.RawMessage, d channelDependencies) *httpChannel {
func newHttpChannel(id string, requestConfig *request.Config, d channelDependencies) *httpChannel {
return &httpChannel{
id: id,
requestConfig: requestConfig,
@ -96,7 +93,7 @@ func (c *httpChannel) Dispatch(ctx context.Context, msg Message) (err error) {
}
logger := c.d.Logger().
WithField("http_server", gjson.GetBytes(c.requestConfig, "url").String()).
WithField("http_server", c.requestConfig.URL).
WithField("message_id", msg.ID).
WithField("message_nid", msg.NID).
WithField("message_type", msg.Type).

View File

@ -18,11 +18,6 @@ import (
"testing"
"time"
"go.opentelemetry.io/otel/trace/noop"
"github.com/ory/x/crdbx"
"github.com/ory/x/pointerx"
"github.com/go-webauthn/webauthn/protocol"
"github.com/go-webauthn/webauthn/webauthn"
"github.com/gofrs/uuid"
@ -30,18 +25,22 @@ import (
"github.com/pkg/errors"
"github.com/rs/cors"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
"golang.org/x/net/publicsuffix"
"github.com/ory/herodot"
"github.com/ory/jsonschema/v3"
"github.com/ory/jsonschema/v3/httploader"
"github.com/ory/kratos/embedx"
"github.com/ory/kratos/request"
"github.com/ory/x/configx"
"github.com/ory/x/contextx"
"github.com/ory/x/crdbx"
"github.com/ory/x/httpx"
"github.com/ory/x/jsonschemax"
"github.com/ory/x/logrusx"
"github.com/ory/x/otelx"
"github.com/ory/x/pointerx"
"github.com/ory/x/stringsx"
"github.com/ory/x/tlsx"
"github.com/ory/x/watcherx"
@ -286,11 +285,10 @@ type (
PlainText string `json:"plaintext"`
}
CourierChannel struct {
ID string `json:"id" koanf:"id"`
Type string `json:"type" koanf:"type"`
SMTPConfig *SMTPConfig `json:"smtp_config" koanf:"smtp_config"`
RequestConfig json.RawMessage `json:"request_config" koanf:"-"`
RequestConfigRaw map[string]any `json:"-" koanf:"request_config"`
ID string `json:"id" koanf:"id"`
Type string `json:"type" koanf:"type"`
SMTPConfig *SMTPConfig `json:"smtp_config" koanf:"smtp_config"`
RequestConfig request.Config `json:"request_config" koanf:"request_config"`
}
SMTPConfig struct {
ConnectionURI string `json:"connection_uri" koanf:"connection_uri"`
@ -302,8 +300,8 @@ type (
LocalName string `json:"local_name" koanf:"local_name"`
}
PasswordMigrationHook struct {
Enabled bool `json:"enabled" koanf:"enabled"`
Config json.RawMessage `json:"config" koanf:"config"`
Enabled bool `json:"enabled" koanf:"enabled"`
Config request.Config `json:"config" koanf:"config"`
}
Config struct {
l *logrusx.Logger
@ -1238,17 +1236,6 @@ func (p *Config) CourierChannels(ctx context.Context) (ccs []*CourierChannel, _
if err := p.GetProvider(ctx).Koanf.Unmarshal(ViperKeyCourierChannels, &ccs); err != nil {
return nil, errors.WithStack(err)
}
if len(ccs) != 0 {
for _, c := range ccs {
if c.RequestConfigRaw != nil {
var err error
c.RequestConfig, err = json.Marshal(c.RequestConfigRaw)
if err != nil {
return nil, errors.WithStack(err)
}
}
}
}
// load legacy configs
channel := CourierChannel{
@ -1260,9 +1247,7 @@ func (p *Config) CourierChannels(ctx context.Context) (ccs []*CourierChannel, _
return nil, errors.WithStack(err)
}
} else {
var err error
channel.RequestConfig, err = json.Marshal(p.GetProvider(ctx).Get(ViperKeyCourierHTTPRequestConfig))
if err != nil {
if err := p.GetProvider(ctx).Koanf.Unmarshal(ViperKeyCourierHTTPRequestConfig, &channel.RequestConfig); err != nil {
return nil, errors.WithStack(err)
}
}
@ -1687,7 +1672,7 @@ func (p *Config) PasswordMigrationHook(ctx context.Context) *PasswordMigrationHo
return hook
}
hook.Config, _ = json.Marshal(p.GetProvider(ctx).Get(ViperKeyPasswordMigrationHook + ".config"))
_ = p.GetProvider(ctx).Unmarshal(ViperKeyPasswordMigrationHook+".config", &hook.Config)
return hook
}

View File

@ -31,6 +31,7 @@ import (
password2 "github.com/ory/kratos/selfservice/strategy/password"
"github.com/ory/kratos/session"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/nosurf"
"github.com/ory/x/contextx"
"github.com/ory/x/dbal"
@ -51,7 +52,7 @@ type Registry interface {
WithJsonnetVMProvider(jsonnetsecure.VMProvider) Registry
WithCSRFHandler(c nosurf.Handler)
WithCSRFTokenGenerator(cg x.CSRFToken)
WithCSRFTokenGenerator(cg nosurfx.CSRFToken)
MetricsHandler() *prometheus.Handler
HealthHandler(ctx context.Context) *healthx.Handler
@ -70,7 +71,7 @@ type Registry interface {
WithConfig(c *config.Config) Registry
WithContextualizer(ctxer contextx.Contextualizer) Registry
x.CSRFProvider
nosurfx.CSRFProvider
x.WriterProvider
x.LoggingProvider
x.HTTPClientProvider
@ -151,7 +152,7 @@ type Registry interface {
recovery.HandlerProvider
recovery.StrategyProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
}
func NewRegistryFromDSN(ctx context.Context, c *config.Config, l *logrusx.Logger) (Registry, error) {

View File

@ -12,6 +12,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/lestrrat-go/jwx/jwk"
"github.com/ory/kratos/selfservice/strategy/idfirst"
@ -157,7 +159,7 @@ type RegistryDefault struct {
buildHash string
buildDate string
csrfTokenGenerator x.CSRFToken
csrfTokenGenerator nosurfx.CSRFToken
jsonnetVMProvider jsonnetsecure.VMProvider
jsonnetPool jsonnetsecure.Pool
@ -830,13 +832,13 @@ func (m *RegistryDefault) Ping() error {
return m.persister.Ping(context.Background())
}
func (m *RegistryDefault) WithCSRFTokenGenerator(cg x.CSRFToken) {
func (m *RegistryDefault) WithCSRFTokenGenerator(cg nosurfx.CSRFToken) {
m.csrfTokenGenerator = cg
}
func (m *RegistryDefault) GenerateCSRFToken(r *http.Request) string {
if m.csrfTokenGenerator == nil {
m.csrfTokenGenerator = x.DefaultCSRFToken
m.csrfTokenGenerator = nosurfx.DefaultCSRFToken
}
return m.csrfTokenGenerator(r)
}

View File

@ -4,7 +4,13 @@
package driver
import (
"encoding/json"
"fmt"
"github.com/pkg/errors"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/request"
"github.com/ory/kratos/selfservice/hook"
)
@ -57,35 +63,50 @@ func (m *RegistryDefault) WithExtraHandlers(handlers []NewHandlerRegistrar) {
m.extraHandlerFactories = handlers
}
func (m *RegistryDefault) getHooks(credentialsType string, configs []config.SelfServiceHook) (i []interface{}) {
func getHooks[T any](m *RegistryDefault, credentialsType string, configs []config.SelfServiceHook) ([]T, error) {
hooks := make([]T, 0, len(configs))
var addSessionIssuer bool
allHooksLoop:
for _, h := range configs {
switch h.Name {
case hook.KeySessionIssuer:
// The session issuer hook always needs to come last.
addSessionIssuer = true
case hook.KeySessionDestroyer:
i = append(i, m.HookSessionDestroyer())
if h, ok := any(m.HookSessionDestroyer()).(T); ok {
hooks = append(hooks, h)
}
case hook.KeyWebHook:
i = append(i, hook.NewWebHook(m, h.Config))
cfg := request.Config{}
if err := json.Unmarshal(h.Config, &cfg); err != nil {
m.l.WithError(err).WithField("raw_config", string(h.Config)).Error("failed to unmarshal hook configuration, ignoring hook")
return nil, errors.WithStack(fmt.Errorf("failed to unmarshal webhook configuration for %s: %w", credentialsType, err))
}
if h, ok := any(hook.NewWebHook(m, &cfg)).(T); ok {
hooks = append(hooks, h)
}
case hook.KeyAddressVerifier:
i = append(i, m.HookAddressVerifier())
if h, ok := any(m.HookAddressVerifier()).(T); ok {
hooks = append(hooks, h)
}
case hook.KeyVerificationUI:
i = append(i, m.HookShowVerificationUI())
if h, ok := any(m.HookShowVerificationUI()).(T); ok {
hooks = append(hooks, h)
}
case hook.KeyVerifier:
i = append(i, m.HookVerifier())
if h, ok := any(m.HookVerifier()).(T); ok {
hooks = append(hooks, h)
}
default:
var found bool
for name, m := range m.injectedSelfserviceHooks {
if name == h.Name {
i = append(i, m(h))
found = true
break
if h, ok := m(h).(T); ok {
hooks = append(hooks, h)
}
continue allHooksLoop
}
}
if found {
continue
}
m.l.
WithField("for", credentialsType).
WithField("hook", h.Name).
@ -93,8 +114,10 @@ func (m *RegistryDefault) getHooks(credentialsType string, configs []config.Self
}
}
if addSessionIssuer {
i = append(i, m.HookSessionIssuer())
if h, ok := any(m.HookSessionIssuer()).(T); ok {
hooks = append(hooks, h)
}
}
return i
return hooks, nil
}

View File

@ -18,32 +18,22 @@ func (m *RegistryDefault) LoginHookExecutor() *login.HookExecutor {
return m.selfserviceLoginExecutor
}
func (m *RegistryDefault) PreLoginHooks(ctx context.Context) (b []login.PreHookExecutor) {
for _, v := range m.getHooks("", m.Config().SelfServiceFlowLoginBeforeHooks(ctx)) {
if hook, ok := v.(login.PreHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PreLoginHooks(ctx context.Context) ([]login.PreHookExecutor, error) {
return getHooks[login.PreHookExecutor](m, "", m.Config().SelfServiceFlowLoginBeforeHooks(ctx))
}
func (m *RegistryDefault) PostLoginHooks(ctx context.Context, credentialsType identity.CredentialsType) (b []login.PostHookExecutor) {
for _, v := range m.getHooks(string(credentialsType), m.Config().SelfServiceFlowLoginAfterHooks(ctx, string(credentialsType))) {
if hook, ok := v.(login.PostHookExecutor); ok {
b = append(b, hook)
}
func (m *RegistryDefault) PostLoginHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]login.PostHookExecutor, error) {
hooks, err := getHooks[login.PostHookExecutor](m, string(credentialsType), m.Config().SelfServiceFlowLoginAfterHooks(ctx, string(credentialsType)))
if err != nil {
return nil, err
}
if len(hooks) > 0 {
return hooks, nil
}
if len(b) == 0 {
// since we don't want merging hooks defined in a specific strategy and global hooks
// global hooks are added only if no strategy specific hooks are defined
for _, v := range m.getHooks(config.HookGlobal, m.Config().SelfServiceFlowLoginAfterHooks(ctx, "global")) {
if hook, ok := v.(login.PostHookExecutor); ok {
b = append(b, hook)
}
}
}
return
// since we don't want merging hooks defined in a specific strategy and global hooks
// global hooks are added only if no strategy specific hooks are defined
return getHooks[login.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowLoginAfterHooks(ctx, config.HookGlobal))
}
func (m *RegistryDefault) LoginHandler() *login.Handler {

View File

@ -69,23 +69,12 @@ func (m *RegistryDefault) RecoveryExecutor() *recovery.HookExecutor {
return m.selfserviceRecoveryExecutor
}
func (m *RegistryDefault) PreRecoveryHooks(ctx context.Context) (b []recovery.PreHookExecutor) {
for _, v := range m.getHooks("", m.Config().SelfServiceFlowRecoveryBeforeHooks(ctx)) {
if hook, ok := v.(recovery.PreHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PreRecoveryHooks(ctx context.Context) ([]recovery.PreHookExecutor, error) {
return getHooks[recovery.PreHookExecutor](m, "", m.Config().SelfServiceFlowRecoveryBeforeHooks(ctx))
}
func (m *RegistryDefault) PostRecoveryHooks(ctx context.Context) (b []recovery.PostHookExecutor) {
for _, v := range m.getHooks(config.HookGlobal, m.Config().SelfServiceFlowRecoveryAfterHooks(ctx, config.HookGlobal)) {
if hook, ok := v.(recovery.PostHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PostRecoveryHooks(ctx context.Context) ([]recovery.PostHookExecutor, error) {
return getHooks[recovery.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowRecoveryAfterHooks(ctx, config.HookGlobal))
}
func (m *RegistryDefault) CodeSender() *code.Sender {

View File

@ -5,59 +5,47 @@ package driver
import (
"context"
"slices"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/selfservice/flow/registration"
)
func (m *RegistryDefault) PostRegistrationPrePersistHooks(ctx context.Context, credentialsType identity.CredentialsType) (b []registration.PostHookPrePersistExecutor) {
func (m *RegistryDefault) PostRegistrationPrePersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]registration.PostHookPrePersistExecutor, error) {
hooks, err := getHooks[registration.PostHookPrePersistExecutor](m, string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType)))
if err != nil {
return nil, err
}
if credentialsType == identity.CredentialsTypeCodeAuth && m.Config().SelfServiceCodeStrategy(ctx).PasswordlessEnabled {
b = append(b, m.HookCodeAddressVerifier())
hooks = slices.Insert(hooks, 0, registration.PostHookPrePersistExecutor(m.HookCodeAddressVerifier()))
}
for _, v := range m.getHooks(string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType))) {
if hook, ok := v.(registration.PostHookPrePersistExecutor); ok {
b = append(b, hook)
}
}
return
return hooks, nil
}
func (m *RegistryDefault) PostRegistrationPostPersistHooks(ctx context.Context, credentialsType identity.CredentialsType) (b []registration.PostHookPostPersistExecutor) {
initialHookCount := 0
if m.Config().SelfServiceFlowVerificationEnabled(ctx) {
b = append(b, m.HookVerifier())
initialHookCount = 1
func (m *RegistryDefault) PostRegistrationPostPersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]registration.PostHookPostPersistExecutor, error) {
hooks, err := getHooks[registration.PostHookPostPersistExecutor](m, string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType)))
if err != nil {
return nil, err
}
for _, v := range m.getHooks(string(credentialsType), m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, string(credentialsType))) {
if hook, ok := v.(registration.PostHookPostPersistExecutor); ok {
b = append(b, hook)
}
}
if len(b) == initialHookCount {
if len(hooks) == 0 {
// since we don't want merging hooks defined in a specific strategy and
// global hooks are added only if no strategy specific hooks are defined
for _, v := range m.getHooks(config.HookGlobal, m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, config.HookGlobal)) {
if hook, ok := v.(registration.PostHookPostPersistExecutor); ok {
b = append(b, hook)
}
hooks, err = getHooks[registration.PostHookPostPersistExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowRegistrationAfterHooks(ctx, config.HookGlobal))
if err != nil {
return nil, err
}
}
return
if m.Config().SelfServiceFlowVerificationEnabled(ctx) {
hooks = slices.Insert(hooks, 0, registration.PostHookPostPersistExecutor(m.HookVerifier()))
}
return hooks, nil
}
func (m *RegistryDefault) PreRegistrationHooks(ctx context.Context) (b []registration.PreHookExecutor) {
for _, v := range m.getHooks("", m.Config().SelfServiceFlowRegistrationBeforeHooks(ctx)) {
if hook, ok := v.(registration.PreHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PreRegistrationHooks(ctx context.Context) ([]registration.PreHookExecutor, error) {
return getHooks[registration.PreHookExecutor](m, "", m.Config().SelfServiceFlowRegistrationBeforeHooks(ctx))
}
func (m *RegistryDefault) RegistrationExecutor() *registration.HookExecutor {

View File

@ -5,53 +5,39 @@ package driver
import (
"context"
"slices"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/selfservice/flow/settings"
)
func (m *RegistryDefault) PostSettingsPrePersistHooks(ctx context.Context, settingsType string) (b []settings.PostHookPrePersistExecutor) {
for _, v := range m.getHooks(settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType)) {
if hook, ok := v.(settings.PostHookPrePersistExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PostSettingsPrePersistHooks(ctx context.Context, settingsType string) ([]settings.PostHookPrePersistExecutor, error) {
return getHooks[settings.PostHookPrePersistExecutor](m, settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType))
}
func (m *RegistryDefault) PreSettingsHooks(ctx context.Context) (b []settings.PreHookExecutor) {
for _, v := range m.getHooks("", m.Config().SelfServiceFlowSettingsBeforeHooks(ctx)) {
if hook, ok := v.(settings.PreHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PreSettingsHooks(ctx context.Context) ([]settings.PreHookExecutor, error) {
return getHooks[settings.PreHookExecutor](m, "", m.Config().SelfServiceFlowSettingsBeforeHooks(ctx))
}
func (m *RegistryDefault) PostSettingsPostPersistHooks(ctx context.Context, settingsType string) (b []settings.PostHookPostPersistExecutor) {
initialHookCount := 0
if m.Config().SelfServiceFlowVerificationEnabled(ctx) {
b = append(b, m.HookVerifier())
initialHookCount = 1
func (m *RegistryDefault) PostSettingsPostPersistHooks(ctx context.Context, settingsType string) ([]settings.PostHookPostPersistExecutor, error) {
hooks, err := getHooks[settings.PostHookPostPersistExecutor](m, settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType))
if err != nil {
return nil, err
}
for _, v := range m.getHooks(settingsType, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, settingsType)) {
if hook, ok := v.(settings.PostHookPostPersistExecutor); ok {
b = append(b, hook)
}
}
if len(b) == initialHookCount {
// since we don't want merging hooks defined in a specific strategy and global hooks
if len(hooks) == 0 {
// since we don't want merging hooks defined in a specific strategy and
// global hooks are added only if no strategy specific hooks are defined
for _, v := range m.getHooks(config.HookGlobal, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, config.HookGlobal)) {
if hook, ok := v.(settings.PostHookPostPersistExecutor); ok {
b = append(b, hook)
}
hooks, err = getHooks[settings.PostHookPostPersistExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowSettingsAfterHooks(ctx, config.HookGlobal))
if err != nil {
return nil, err
}
}
return
if m.Config().SelfServiceFlowVerificationEnabled(ctx) {
hooks = slices.Insert(hooks, 0, settings.PostHookPostPersistExecutor(m.HookVerifier()))
}
return hooks, nil
}
func (m *RegistryDefault) SettingsHookExecutor() *settings.HookExecutor {

View File

@ -5,32 +5,28 @@ package driver_test
import (
"context"
"encoding/json"
"fmt"
"os"
"testing"
confighelpers "github.com/ory/kratos/driver/config/testhelpers"
"github.com/ory/x/contextx"
"github.com/ory/kratos/selfservice/flow/recovery"
"github.com/ory/kratos/selfservice/flow/verification"
"github.com/ory/kratos/driver"
"github.com/ory/x/configx"
"github.com/ory/x/logrusx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/ory/x/configx"
"github.com/ory/x/contextx"
"github.com/ory/x/logrusx"
"github.com/ory/kratos/driver"
"github.com/ory/kratos/driver/config"
confighelpers "github.com/ory/kratos/driver/config/testhelpers"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/internal"
"github.com/ory/kratos/request"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/selfservice/flow/recovery"
"github.com/ory/kratos/selfservice/flow/registration"
"github.com/ory/kratos/selfservice/flow/settings"
"github.com/ory/kratos/selfservice/flow/verification"
"github.com/ory/kratos/selfservice/hook"
)
@ -49,8 +45,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect func(reg *driver.RegistryDefault) []verification.PreHookExecutor
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []verification.PreHookExecutor { return nil },
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []verification.PreHookExecutor {
return []verification.PreHookExecutor{}
},
},
{
uc: "Two web_hooks are configured",
@ -62,8 +60,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []verification.PreHookExecutor {
return []verification.PreHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -73,11 +71,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PreVerificationHooks(ctx)
h, err := reg.PreVerificationHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
@ -89,9 +86,11 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect func(reg *driver.RegistryDefault) []verification.PostHookExecutor
}{
{
uc: "No hooks configured",
prep: func(conf *config.Config) {},
expect: func(reg *driver.RegistryDefault) []verification.PostHookExecutor { return nil },
uc: "No hooks configured",
prep: func(conf *config.Config) {},
expect: func(reg *driver.RegistryDefault) []verification.PostHookExecutor {
return []verification.PostHookExecutor{}
},
},
{
uc: "Multiple web_hooks configured",
@ -103,8 +102,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []verification.PostHookExecutor {
return []verification.PostHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -114,11 +113,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PostVerificationHooks(ctx)
h, err := reg.PostVerificationHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
})
@ -133,7 +131,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []recovery.PreHookExecutor { return nil },
expect: func(reg *driver.RegistryDefault) []recovery.PreHookExecutor { return []recovery.PreHookExecutor{} },
},
{
uc: "Two web_hooks are configured",
@ -145,8 +143,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []recovery.PreHookExecutor {
return []recovery.PreHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -156,11 +154,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PreRecoveryHooks(ctx)
h, err := reg.PreRecoveryHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
@ -172,7 +169,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []recovery.PostHookExecutor { return nil },
expect: func(reg *driver.RegistryDefault) []recovery.PostHookExecutor { return []recovery.PostHookExecutor{} },
},
{
uc: "Multiple web_hooks configured",
@ -184,8 +181,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []recovery.PostHookExecutor {
return []recovery.PostHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -195,11 +192,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PostRecoveryHooks(ctx)
h, err := reg.PostRecoveryHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
})
@ -215,7 +211,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []registration.PreHookExecutor {
return nil
return []registration.PreHookExecutor{}
},
},
{
@ -228,8 +224,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []registration.PreHookExecutor {
return []registration.PreHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -239,11 +235,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PreRegistrationHooks(ctx)
h, err := reg.PreRegistrationHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.EqualValues(t, expectedExecutors, h)
assert.EqualValues(t, tc.expect(reg), h)
})
}
@ -254,8 +249,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor { return nil },
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor {
return []registration.PostHookPostPersistExecutor{}
},
},
{
uc: "Only session hook configured for password strategy",
@ -284,7 +281,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor {
return []registration.PostHookPostPersistExecutor{
hook.NewVerifier(reg),
hook.NewWebHook(reg, json.RawMessage(`{"body":"bar","headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{URL: "foo", Method: "POST", TemplateURI: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewSessionIssuer(reg),
}
},
@ -299,8 +296,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor {
return []registration.PostHookPostPersistExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -319,7 +316,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect: func(reg *driver.RegistryDefault) []registration.PostHookPostPersistExecutor {
return []registration.PostHookPostPersistExecutor{
hook.NewVerifier(reg),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewSessionIssuer(reg),
}
},
@ -343,11 +340,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PostRegistrationPostPersistHooks(ctx, identity.CredentialsTypePassword)
h, err := reg.PostRegistrationPostPersistHooks(ctx, identity.CredentialsTypePassword)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
})
@ -362,7 +358,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []login.PreHookExecutor { return nil },
expect: func(reg *driver.RegistryDefault) []login.PreHookExecutor { return []login.PreHookExecutor{} },
},
{
uc: "Two web_hooks are configured",
@ -374,8 +370,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []login.PreHookExecutor {
return []login.PreHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -385,11 +381,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PreLoginHooks(ctx)
h, err := reg.PreLoginHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
@ -401,7 +396,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return nil },
expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor { return []login.PostHookExecutor{} },
},
{
uc: "Only revoke_active_sessions hook configured for password strategy",
@ -440,7 +435,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor {
return []login.PostHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"body":"bar","headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{TemplateURI: "bar", Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewAddressVerifier(),
hook.NewSessionDestroyer(reg),
}
@ -456,8 +451,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor {
return []login.PostHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -475,7 +470,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []login.PostHookExecutor {
return []login.PostHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewSessionDestroyer(reg),
hook.NewAddressVerifier(),
}
@ -487,11 +482,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PostLoginHooks(ctx, identity.CredentialsTypePassword)
h, err := reg.PostLoginHooks(ctx, identity.CredentialsTypePassword)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
})
@ -506,7 +500,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []settings.PreHookExecutor { return nil },
expect: func(reg *driver.RegistryDefault) []settings.PreHookExecutor { return []settings.PreHookExecutor{} },
},
{
uc: "Two web_hooks are configured",
@ -518,8 +512,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []settings.PreHookExecutor {
return []settings.PreHookExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -529,11 +523,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PreSettingsHooks(ctx)
h, err := reg.PreSettingsHooks(ctx)
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
@ -544,8 +537,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor
}{
{
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor { return nil },
uc: "No hooks configured",
expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor {
return []settings.PostHookPostPersistExecutor{}
},
},
{
uc: "Only verify hook configured for the strategy",
@ -565,14 +560,14 @@ func TestDriverDefault_Hooks(t *testing.T) {
uc: "A verify hook and a web_hook are configured for profile strategy",
config: map[string]any{
config.ViperKeySelfServiceSettingsAfter + ".profile.hooks": []map[string]any{
{"hook": "web_hook", "config": map[string]any{"headers": []map[string]string{{"X-Custom-Header": "test"}}, "url": "foo", "method": "POST", "body": "bar"}},
{"hook": "web_hook", "config": map[string]any{"headers": map[string]string{"X-Custom-Header": "test"}, "url": "foo", "method": "POST", "body": "bar"}},
},
config.ViperKeySelfServiceVerificationEnabled: true,
},
expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor {
return []settings.PostHookPostPersistExecutor{
hook.NewVerifier(reg),
hook.NewWebHook(reg, json.RawMessage(`{"body":"bar","headers":[{"X-Custom-Header":"test"}],"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{TemplateURI: "bar", Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -586,8 +581,8 @@ func TestDriverDefault_Hooks(t *testing.T) {
},
expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor {
return []settings.PostHookPostPersistExecutor{
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"POST","url":"foo"}`)),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"bar"}`)),
hook.NewWebHook(reg, &request.Config{Method: "POST", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "bar", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -605,7 +600,7 @@ func TestDriverDefault_Hooks(t *testing.T) {
expect: func(reg *driver.RegistryDefault) []settings.PostHookPostPersistExecutor {
return []settings.PostHookPostPersistExecutor{
hook.NewVerifier(reg),
hook.NewWebHook(reg, json.RawMessage(`{"headers":{"X-Custom-Header":"test"},"method":"GET","url":"foo"}`)),
hook.NewWebHook(reg, &request.Config{Method: "GET", URL: "foo", Headers: map[string]string{"X-Custom-Header": "test"}}),
}
},
},
@ -615,11 +610,10 @@ func TestDriverDefault_Hooks(t *testing.T) {
ctx := confighelpers.WithConfigValues(ctx, tc.config)
h := reg.PostSettingsPostPersistHooks(ctx, "profile")
h, err := reg.PostSettingsPostPersistHooks(ctx, "profile")
require.NoError(t, err)
expectedExecutors := tc.expect(reg)
require.Len(t, h, len(expectedExecutors))
assert.Equal(t, expectedExecutors, h)
assert.Equal(t, tc.expect(reg), h)
})
}
})

View File

@ -91,21 +91,10 @@ func (m *RegistryDefault) VerificationExecutor() *verification.HookExecutor {
return m.selfserviceVerificationExecutor
}
func (m *RegistryDefault) PreVerificationHooks(ctx context.Context) (b []verification.PreHookExecutor) {
for _, v := range m.getHooks("", m.Config().SelfServiceFlowVerificationBeforeHooks(ctx)) {
if hook, ok := v.(verification.PreHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PreVerificationHooks(ctx context.Context) ([]verification.PreHookExecutor, error) {
return getHooks[verification.PreHookExecutor](m, "", m.Config().SelfServiceFlowVerificationBeforeHooks(ctx))
}
func (m *RegistryDefault) PostVerificationHooks(ctx context.Context) (b []verification.PostHookExecutor) {
for _, v := range m.getHooks(config.HookGlobal, m.Config().SelfServiceFlowVerificationAfterHooks(ctx, config.HookGlobal)) {
if hook, ok := v.(verification.PostHookExecutor); ok {
b = append(b, hook)
}
}
return
func (m *RegistryDefault) PostVerificationHooks(ctx context.Context) ([]verification.PostHookExecutor, error) {
return getHooks[verification.PostHookExecutor](m, config.HookGlobal, m.Config().SelfServiceFlowVerificationAfterHooks(ctx, config.HookGlobal))
}

View File

@ -365,9 +365,7 @@
"response": {
"properties": {
"ignore": {
"enum": [
true
]
"const": true
}
},
"required": [
@ -383,9 +381,7 @@
{
"properties": {
"can_interrupt": {
"enum": [
false
]
"const": false
}
},
"require": [
@ -1918,6 +1914,18 @@
}
]
},
"body": {
"type": "string",
"format": "uri",
"pattern": "^(http|https|file|base64)://",
"description": "URI pointing to the jsonnet template used for payload generation. Only used for those HTTP methods, which support HTTP body payloads",
"examples": [
"file:///path/to/body.jsonnet",
"file://./body.jsonnet",
"base64://ZnVuY3Rpb24oY3R4KSB7CiAgaWRlbnRpdHlfaWQ6IGlmIGN0eFsiaWRlbnRpdHkiXSAhPSBudWxsIHRoZW4gY3R4LmlkZW50aXR5LmlkLAp9=",
"https://oryapis.com/default_body.jsonnet"
]
},
"additionalProperties": false
}
}

View File

@ -11,6 +11,9 @@ import (
"strings"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/gofrs/uuid"
"github.com/ory/x/crdbx"
@ -54,7 +57,7 @@ type (
ManagementProvider
x.WriterProvider
config.Provider
x.CSRFProvider
nosurfx.CSRFProvider
cipher.Provider
hash.HashProvider
}
@ -86,21 +89,21 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
x.AdminPrefix+RouteCollection+"/*/credentials/*",
)
public.GET(RouteCollection, x.RedirectToAdminRoute(h.r))
public.GET(RouteItem, x.RedirectToAdminRoute(h.r))
public.DELETE(RouteItem, x.RedirectToAdminRoute(h.r))
public.POST(RouteCollection, x.RedirectToAdminRoute(h.r))
public.PUT(RouteItem, x.RedirectToAdminRoute(h.r))
public.PATCH(RouteItem, x.RedirectToAdminRoute(h.r))
public.DELETE(RouteCredentialItem, x.RedirectToAdminRoute(h.r))
public.GET(RouteCollection, redir.RedirectToAdminRoute(h.r))
public.GET(RouteItem, redir.RedirectToAdminRoute(h.r))
public.DELETE(RouteItem, redir.RedirectToAdminRoute(h.r))
public.POST(RouteCollection, redir.RedirectToAdminRoute(h.r))
public.PUT(RouteItem, redir.RedirectToAdminRoute(h.r))
public.PATCH(RouteItem, redir.RedirectToAdminRoute(h.r))
public.DELETE(RouteCredentialItem, redir.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+RouteCollection, x.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+RouteItem, x.RedirectToAdminRoute(h.r))
public.DELETE(x.AdminPrefix+RouteItem, x.RedirectToAdminRoute(h.r))
public.POST(x.AdminPrefix+RouteCollection, x.RedirectToAdminRoute(h.r))
public.PUT(x.AdminPrefix+RouteItem, x.RedirectToAdminRoute(h.r))
public.PATCH(x.AdminPrefix+RouteItem, x.RedirectToAdminRoute(h.r))
public.DELETE(x.AdminPrefix+RouteCredentialItem, x.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+RouteCollection, redir.RedirectToAdminRoute(h.r))
public.GET(x.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r))
public.DELETE(x.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r))
public.POST(x.AdminPrefix+RouteCollection, redir.RedirectToAdminRoute(h.r))
public.PUT(x.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r))
public.PATCH(x.AdminPrefix+RouteItem, redir.RedirectToAdminRoute(h.r))
public.DELETE(x.AdminPrefix+RouteCredentialItem, redir.RedirectToAdminRoute(h.r))
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {

View File

@ -9,6 +9,8 @@ import (
"runtime"
"testing"
"github.com/ory/kratos/x/nosurfx"
confighelpers "github.com/ory/kratos/driver/config/testhelpers"
"github.com/ory/x/contextx"
@ -31,7 +33,6 @@ import (
"github.com/ory/kratos/driver"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/selfservice/hook"
"github.com/ory/kratos/x"
)
func init() {
@ -70,8 +71,8 @@ func NewConfigurationWithDefaults(t testing.TB, opts ...configx.OptionModifier)
// easier and way faster. This suite does not work for e2e or advanced integration tests.
func NewFastRegistryWithMocks(t *testing.T, opts ...configx.OptionModifier) (*config.Config, *driver.RegistryDefault) {
conf, reg := NewRegistryDefaultWithDSN(t, "", opts...)
reg.WithCSRFTokenGenerator(x.FakeCSRFTokenGenerator)
reg.WithCSRFHandler(x.NewFakeCSRFHandler(""))
reg.WithCSRFTokenGenerator(nosurfx.FakeCSRFTokenGenerator)
reg.WithCSRFHandler(nosurfx.NewFakeCSRFHandler(""))
reg.WithHooks(map[string]func(config.SelfServiceHook) interface{}{
"err": func(c config.SelfServiceHook) interface{} {
return &hook.Error{Config: c.Config}

View File

@ -16,6 +16,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
@ -115,7 +117,7 @@ func AssertSchemDoesNotExist(t *testing.T, reg *driver.RegistryDefault, flows []
values := url.Values{
"traits.username": {testhelpers.RandomEmail()},
"traits.foobar": {"bar"},
"csrf_token": {x.FakeCSRFToken},
"csrf_token": {nosurfx.FakeCSRFToken},
}
payload(values)
@ -180,7 +182,7 @@ func AssertCSRFFailures(t *testing.T, reg *driver.RegistryDefault, flows []strin
actual, res := testhelpers.RegistrationMakeRequest(t, false, false, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusOK, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(actual), "%s", actual)
})
@ -192,7 +194,7 @@ func AssertCSRFFailures(t *testing.T, reg *driver.RegistryDefault, flows []strin
actual, res := testhelpers.RegistrationMakeRequest(t, false, true, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusForbidden, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
})

View File

@ -9,9 +9,10 @@ import (
"net/http/httptest"
"net/url"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/x/pointerx"
kratos "github.com/ory/kratos/internal/httpclient"
@ -69,7 +70,7 @@ func NewFakeCSRFNode() *kratos.UiNode {
Name: "csrf_token",
Required: pointerx.Bool(true),
Type: "hidden",
Value: x.FakeCSRFToken,
Value: nosurfx.FakeCSRFToken,
}),
}
}

View File

@ -12,6 +12,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/tidwall/gjson"
kratos "github.com/ory/kratos/internal/httpclient"
@ -182,7 +184,7 @@ func NewSettingsAPIServer(t *testing.T, reg *driver.RegistryDefault, ids map[str
n := negroni.Classic()
n.UseHandler(public)
hh := x.NewTestCSRFHandler(n, reg)
hh := nosurfx.NewTestCSRFHandler(n, reg)
reg.WithCSRFHandler(hh)
reg.SettingsHandler().RegisterPublicRoutes(public)

View File

@ -8,6 +8,8 @@ import (
"strings"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/urfave/negroni"
"github.com/gobuffalo/httptest"
@ -28,7 +30,7 @@ func NewKratosServerWithCSRF(t *testing.T, reg driver.Registry) (public, admin *
func NewKratosServerWithCSRFAndRouters(t *testing.T, reg driver.Registry) (public, admin *httptest.Server, rp *x.RouterPublic, ra *x.RouterAdmin) {
rp, ra = x.NewRouterPublic(), x.NewRouterAdmin()
csrfHandler := x.NewTestCSRFHandler(rp, reg)
csrfHandler := nosurfx.NewTestCSRFHandler(rp, reg)
reg.WithCSRFHandler(csrfHandler)
ran := negroni.New()
ran.UseFunc(x.RedirectAdminMiddleware)
@ -36,7 +38,7 @@ func NewKratosServerWithCSRFAndRouters(t *testing.T, reg driver.Registry) (publi
rpn := negroni.New()
rpn.UseFunc(x.HTTPLoaderContextMiddleware(reg))
rpn.UseHandler(rp)
public = httptest.NewServer(x.NewTestCSRFHandler(rpn, reg))
public = httptest.NewServer(nosurfx.NewTestCSRFHandler(rpn, reg))
admin = httptest.NewServer(ran)
ctx := context.Background()

View File

@ -4,31 +4,91 @@
package request
import (
"encoding/json"
"fmt"
"net/http"
"github.com/hashicorp/go-retryablehttp"
)
type (
noopAuthStrategy struct{}
basicAuthStrategy struct {
user string
password string
}
apiKeyStrategy struct {
name string
value string
in string
}
AuthStrategy interface {
apply(req *retryablehttp.Request)
}
authStrategyFactory func(c json.RawMessage) (AuthStrategy, error)
)
var strategyFactories = map[string]authStrategyFactory{
"": newNoopAuthStrategy,
"api_key": newApiKeyStrategy,
"basic_auth": newBasicAuthStrategy,
}
func authStrategy(name string, config json.RawMessage) (AuthStrategy, error) {
strategyFactory, ok := strategyFactories[name]
if ok {
return strategyFactory(config)
func authStrategy(typ string, config map[string]any) (AuthStrategy, error) {
switch typ {
case "":
return NewNoopAuthStrategy(), nil
case "api_key":
name, ok := config["name"].(string)
if !ok {
return nil, fmt.Errorf("api_key auth strategy requires a string name")
}
value, ok := config["value"].(string)
if !ok {
return nil, fmt.Errorf("api_key auth strategy requires a string value")
}
in, _ := config["in"].(string) // in is optional
return NewAPIKeyStrategy(in, name, value), nil
case "basic_auth":
user, ok := config["user"].(string)
if !ok {
return nil, fmt.Errorf("basic_auth auth strategy requires a string user")
}
password, ok := config["password"].(string)
if !ok {
return nil, fmt.Errorf("basic_auth auth strategy requires a string password")
}
return NewBasicAuthStrategy(user, password), nil
}
return nil, fmt.Errorf("unsupported auth type: %s", name)
return nil, fmt.Errorf("unsupported auth type: %s", typ)
}
func NewNoopAuthStrategy() AuthStrategy {
return &noopAuthStrategy{}
}
func (c *noopAuthStrategy) apply(_ *retryablehttp.Request) {}
func NewBasicAuthStrategy(user, password string) AuthStrategy {
return &basicAuthStrategy{
user: user,
password: password,
}
}
func (c *basicAuthStrategy) apply(req *retryablehttp.Request) {
req.SetBasicAuth(c.user, c.password)
}
func NewAPIKeyStrategy(in, name, value string) AuthStrategy {
return &apiKeyStrategy{
in: in,
name: name,
value: value,
}
}
func (c *apiKeyStrategy) apply(req *retryablehttp.Request) {
switch c.in {
case "cookie":
req.AddCookie(&http.Cookie{Name: c.name, Value: c.value})
default:
// TODO add deprecation warning
fallthrough
case "header", "":
req.Header.Set(c.name, c.value)
}
}

View File

@ -1,81 +0,0 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package request
import (
"encoding/json"
"net/http"
"github.com/hashicorp/go-retryablehttp"
)
type (
noopAuthStrategy struct{}
basicAuthStrategy struct {
user string
password string
}
apiKeyStrategy struct {
name string
value string
in string
}
)
func newNoopAuthStrategy(_ json.RawMessage) (AuthStrategy, error) {
return &noopAuthStrategy{}, nil
}
func (c *noopAuthStrategy) apply(_ *retryablehttp.Request) {}
func newBasicAuthStrategy(raw json.RawMessage) (AuthStrategy, error) {
type config struct {
User string
Password string
}
var c config
if err := json.Unmarshal(raw, &c); err != nil {
return nil, err
}
return &basicAuthStrategy{
user: c.User,
password: c.Password,
}, nil
}
func (c *basicAuthStrategy) apply(req *retryablehttp.Request) {
req.SetBasicAuth(c.user, c.password)
}
func newApiKeyStrategy(raw json.RawMessage) (AuthStrategy, error) {
type config struct {
In string
Name string
Value string
}
var c config
if err := json.Unmarshal(raw, &c); err != nil {
return nil, err
}
return &apiKeyStrategy{
in: c.In,
name: c.Name,
value: c.Value,
}, nil
}
func (c *apiKeyStrategy) apply(req *retryablehttp.Request) {
switch c.in {
case "cookie":
req.AddCookie(&http.Cookie{Name: c.name, Value: c.value})
default:
req.Header.Set(c.name, c.value)
}
}

View File

@ -1,72 +0,0 @@
// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package request
import (
"net/http"
"testing"
"github.com/hashicorp/go-retryablehttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNoopAuthStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := noopAuthStrategy{}
auth.apply(&req)
assert.Empty(t, req.Header, "Empty auth strategy shall not modify any request headers")
}
func TestBasicAuthStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := basicAuthStrategy{
user: "test-user",
password: "test-pass",
}
auth.apply(&req)
assert.Len(t, req.Header, 1)
user, pass, _ := req.BasicAuth()
assert.Equal(t, "test-user", user)
assert.Equal(t, "test-pass", pass)
}
func TestApiKeyInHeaderStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := apiKeyStrategy{
in: "header",
name: "my-api-key-name",
value: "my-api-key-value",
}
auth.apply(&req)
require.Len(t, req.Header, 1)
actualValue := req.Header.Get("my-api-key-name")
assert.Equal(t, "my-api-key-value", actualValue)
}
func TestApiKeyInCookieStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := apiKeyStrategy{
in: "cookie",
name: "my-api-key-name",
value: "my-api-key-value",
}
auth.apply(&req)
cookies := req.Cookies()
assert.Len(t, cookies, 1)
assert.Equal(t, "my-api-key-name", cookies[0].Name)
assert.Equal(t, "my-api-key-value", cookies[0].Value)
}

View File

@ -4,53 +4,114 @@
package request
import (
"encoding/json"
"net/http"
"testing"
"github.com/hashicorp/go-retryablehttp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNoopAuthStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := noopAuthStrategy{}
auth.apply(&req)
assert.Empty(t, req.Header, "Empty auth strategy shall not modify any request headers")
}
func TestBasicAuthStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := basicAuthStrategy{
user: "test-user",
password: "test-pass",
}
auth.apply(&req)
assert.Len(t, req.Header, 1)
user, pass, _ := req.BasicAuth()
assert.Equal(t, "test-user", user)
assert.Equal(t, "test-pass", pass)
}
func TestApiKeyInHeaderStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := apiKeyStrategy{
in: "header",
name: "my-api-key-name",
value: "my-api-key-value",
}
auth.apply(&req)
require.Len(t, req.Header, 1)
actualValue := req.Header.Get("my-api-key-name")
assert.Equal(t, "my-api-key-value", actualValue)
}
func TestApiKeyInCookieStrategy(t *testing.T) {
req := retryablehttp.Request{Request: &http.Request{Header: map[string][]string{}}}
auth := apiKeyStrategy{
in: "cookie",
name: "my-api-key-name",
value: "my-api-key-value",
}
auth.apply(&req)
cookies := req.Cookies()
assert.Len(t, cookies, 1)
assert.Equal(t, "my-api-key-name", cookies[0].Name)
assert.Equal(t, "my-api-key-value", cookies[0].Value)
}
func TestAuthStrategy(t *testing.T) {
t.Parallel()
for _, tc := range map[string]struct {
name string
config string
config map[string]any
expected AuthStrategy
}{
"noop": {
name: "",
config: "",
config: map[string]any{},
expected: &noopAuthStrategy{},
},
"basic_auth": {
name: "basic_auth",
config: `{
"user": "test-api-user",
"password": "secret"
}`,
config: map[string]any{
"user": "test-api-user",
"password": "secret",
},
expected: &basicAuthStrategy{},
},
"api-key/header": {
name: "api_key",
config: `{
"in": "header",
"name": "my-api-key",
"value": "secret"
}`,
config: map[string]any{
"in": "header",
"name": "my-api-key",
"value": "secret",
},
expected: &apiKeyStrategy{},
},
"api-key/cookie": {
name: "api_key",
config: `{
"in": "cookie",
"name": "my-api-key",
"value": "secret"
}`,
config: map[string]any{
"in": "cookie",
"name": "my-api-key",
"value": "secret",
},
expected: &apiKeyStrategy{},
},
} {
t.Run(tc.name, func(t *testing.T) {
strategy, err := authStrategy(tc.name, json.RawMessage(tc.config))
strategy, err := authStrategy(tc.name, tc.config)
require.NoError(t, err)
assert.IsTypef(t, tc.expected, strategy, "auth strategy should be of the expected type")

View File

@ -58,7 +58,7 @@ func WithCache(cache *ristretto.Cache[[]byte, []byte]) BuilderOption {
}
}
func NewBuilder(ctx context.Context, config json.RawMessage, deps Dependencies, o ...BuilderOption) (_ *Builder, err error) {
func NewBuilder(ctx context.Context, c *Config, deps Dependencies, o ...BuilderOption) (_ *Builder, err error) {
_, span := deps.Tracer(ctx).Tracer().Start(ctx, "request.NewBuilder")
defer otelx.End(span, &err)
@ -67,11 +67,6 @@ func NewBuilder(ctx context.Context, config json.RawMessage, deps Dependencies,
f(&opts)
}
c := Config{}
if err := json.Unmarshal(config, &c); err != nil {
return nil, err
}
span.SetAttributes(
attribute.String("url", c.URL),
attribute.String("method", c.Method),
@ -82,27 +77,27 @@ func NewBuilder(ctx context.Context, config json.RawMessage, deps Dependencies,
return nil, err
}
c.header = make(http.Header, len(c.Headers))
for k, v := range c.Headers {
c.header.Add(k, v)
}
if c.header.Get("Content-Type") == "" {
c.header.Set("Content-Type", ContentTypeJSON)
}
c.auth, err = authStrategy(c.Auth.Type, c.Auth.Config)
if err != nil {
return nil, err
}
return &Builder{
r: r,
Config: &c,
Config: c,
deps: deps,
cache: opts.cache,
}, nil
}
func (b *Builder) addAuth() error {
authConfig := b.Config.Auth
strategy, err := authStrategy(authConfig.Type, authConfig.Config)
if err != nil {
return err
}
strategy.apply(b.r)
return nil
}
func (b *Builder) addBody(ctx context.Context, body interface{}) (err error) {
ctx, span := b.deps.Tracer(ctx).Tracer().Start(ctx, "request.Builder.addBody")
defer otelx.End(span, &err)
@ -111,8 +106,6 @@ func (b *Builder) addBody(ctx context.Context, body interface{}) (err error) {
return nil
}
contentType := b.r.Header.Get("Content-Type")
if b.Config.TemplateURI == "" {
return errors.New("got empty template path for request with body")
}
@ -122,11 +115,14 @@ func (b *Builder) addBody(ctx context.Context, body interface{}) (err error) {
return err
}
switch contentType {
switch b.r.Header.Get("Content-Type") {
case ContentTypeForm:
if err := b.addURLEncodedBody(ctx, tpl, body); err != nil {
return err
}
case "":
b.r.Header.Set("Content-Type", ContentTypeJSON)
fallthrough
case ContentTypeJSON:
if err := b.addJSONBody(ctx, tpl, body); err != nil {
return err
@ -217,10 +213,8 @@ func (b *Builder) addURLEncodedBody(ctx context.Context, jsonnetSnippet []byte,
}
func (b *Builder) BuildRequest(ctx context.Context, body interface{}) (*retryablehttp.Request, error) {
b.r.Header = b.Config.Header
if err := b.addAuth(); err != nil {
return nil, err
}
b.r.Header = b.Config.header
b.Config.auth.apply(b.r)
// According to the HTTP spec any request method, but TRACE is allowed to
// have a body. Even this is a bad practice for some of them, like for GET

View File

@ -8,7 +8,6 @@ import (
_ "embed"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"testing"
@ -31,76 +30,91 @@ type testRequestBody struct {
var testJSONNetTemplate []byte
func TestBuildRequest(t *testing.T) {
t.Parallel()
for _, tc := range []struct {
name string
method string
url string
authStrategy string
expectedHeader http.Header
bodyTemplateURI string
body *testRequestBody
expectedBody string
rawConfig string
name string
method string
url string
authStrategy AuthStrategy
expectedHeader http.Header
bodyTemplateURI string
body *testRequestBody
expectedJSONBody string
expectedRawBody string
config Config
}{
{
name: "POST request without auth",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint1",
authStrategy: "", // noop strategy
authStrategy: NewNoopAuthStrategy(),
bodyTemplateURI: "file://./stub/test_body.jsonnet",
body: &testRequestBody{
To: "+15056445993",
From: "+12288534869",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+12288534869\",\n \"to\": \"+15056445993\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint1",
"method": "POST",
"body": "file://./stub/test_body.jsonnet"
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+12288534869",
"to": "+15056445993"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint1",
Method: "POST",
TemplateURI: "file://./stub/test_body.jsonnet",
},
},
{
name: "POST request with legacy template path",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint1",
authStrategy: NewNoopAuthStrategy(),
bodyTemplateURI: "./stub/test_body.jsonnet",
body: &testRequestBody{
To: "+15056445993",
From: "+12288534869",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+12288534869\",\n \"to\": \"+15056445993\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint1",
"method": "POST",
"body": "./stub/test_body.jsonnet"
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+12288534869",
"to": "+15056445993"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint1",
Method: "POST",
TemplateURI: "./stub/test_body.jsonnet",
},
},
{
name: "POST request with base64 encoded template path",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint1",
authStrategy: NewNoopAuthStrategy(),
bodyTemplateURI: "base64://" + base64.StdEncoding.EncodeToString(testJSONNetTemplate),
body: &testRequestBody{
To: "+15056445993",
From: "+12288534869",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+12288534869\",\n \"to\": \"+15056445993\"\n}\n",
rawConfig: fmt.Sprintf(
`{
"url": "https://test.kratos.ory.sh/my_endpoint1",
"method": "POST",
"body": "base64://%s"
}`, base64.StdEncoding.EncodeToString(testJSONNetTemplate),
),
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+12288534869",
"to": "+15056445993"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint1",
Method: "POST",
TemplateURI: "base64://" + base64.StdEncoding.EncodeToString(testJSONNetTemplate),
},
},
{
name: "POST request with custom header",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint2",
authStrategy: "",
authStrategy: NewNoopAuthStrategy(),
expectedHeader: map[string][]string{"Custom-Header": {"test"}},
bodyTemplateURI: "file://./stub/test_body.jsonnet",
body: &testRequestBody{
@ -108,184 +122,201 @@ func TestBuildRequest(t *testing.T) {
From: "+15822228108",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+15822228108\",\n \"to\": \"+12127110378\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint2",
"method": "POST",
"headers": {
"Custom-Header": "test"
},
"body": "file://./stub/test_body.jsonnet"
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+15822228108",
"to": "+12127110378"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint2",
Method: "POST",
Headers: map[string]string{
"Custom-Header": "test",
},
TemplateURI: "file://./stub/test_body.jsonnet",
},
},
{
name: "GET request with body",
method: "GET",
url: "https://test.kratos.ory.sh/my_endpoint3",
authStrategy: "basic_auth",
authStrategy: NewBasicAuthStrategy("test-api-user", "secret"),
bodyTemplateURI: "file://./stub/test_body.jsonnet",
body: &testRequestBody{
To: "+14134242223",
From: "+13104661805",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+13104661805\",\n \"to\": \"+14134242223\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint3",
"method": "GET",
"auth": {
"type": "basic_auth",
"config": {
"user": "test-api-user",
"password": "secret"
}
},
"body": "file://./stub/test_body.jsonnet"
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+13104661805",
"to": "+14134242223"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint3",
Method: "GET",
Auth: AuthConfig{
Type: "basic_auth",
Config: map[string]any{
"user": "test-api-user",
"password": "secret",
},
},
TemplateURI: "file://./stub/test_body.jsonnet",
},
},
{
name: "GET request without body",
method: "GET",
url: "https://test.kratos.ory.sh/my_endpoint4",
authStrategy: "basic_auth",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint4",
"method": "GET",
"auth": {
"type": "basic_auth",
"config": {
"user": "test-api-user",
"password": "secret"
}
}
}`,
authStrategy: NewBasicAuthStrategy("test-api-user", "secret"),
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint4",
Method: "GET",
Auth: AuthConfig{
Type: "basic_auth",
Config: map[string]any{
"user": "test-api-user",
"password": "secret",
},
},
},
},
{
name: "DELETE request with body",
method: "DELETE",
url: "https://test.kratos.ory.sh/my_endpoint5",
authStrategy: "api_key",
authStrategy: NewAPIKeyStrategy("header", "my-api-key", "secret"),
bodyTemplateURI: "file://./stub/test_body.jsonnet",
body: &testRequestBody{
To: "+12235499085",
From: "+14253787846",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+14253787846\",\n \"to\": \"+12235499085\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint5",
"method": "DELETE",
"body": "file://./stub/test_body.jsonnet",
"auth": {
"type": "api_key",
"config": {
"in": "header",
"name": "my-api-key",
"value": "secret"
}
}
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+14253787846",
"to": "+12235499085"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint5",
Method: "DELETE",
TemplateURI: "file://./stub/test_body.jsonnet",
Auth: AuthConfig{
Type: "api_key",
Config: map[string]any{
"in": "header",
"name": "my-api-key",
"value": "secret",
},
},
},
},
{
name: "POST request with urlencoded body",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint6",
bodyTemplateURI: "file://./stub/test_body.jsonnet",
authStrategy: "api_key",
authStrategy: NewAPIKeyStrategy("cookie", "my-api-key", "secret"),
expectedHeader: map[string][]string{"Content-Type": {ContentTypeForm}},
body: &testRequestBody{
To: "+14134242223",
From: "+13104661805",
Body: "test-sms-body",
},
expectedBody: "body=test-sms-body&from=%2B13104661805&to=%2B14134242223",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint6",
"method": "POST",
"body": "file://./stub/test_body.jsonnet",
"headers": {
"Content-Type": "application/x-www-form-urlencoded"
expectedRawBody: "body=test-sms-body&from=%2B13104661805&to=%2B14134242223",
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint6",
Method: "POST",
TemplateURI: "file://./stub/test_body.jsonnet",
Headers: map[string]string{
"Content-Type": ContentTypeForm,
},
"auth": {
"type": "api_key",
"config": {
"in": "cookie",
"name": "my-api-key",
"value": "secret"
}
}
}`,
Auth: AuthConfig{
Type: "api_key",
Config: map[string]any{
"in": "cookie",
"name": "my-api-key",
"value": "secret",
},
},
},
},
{
name: "POST request with default body type",
method: "POST",
url: "https://test.kratos.ory.sh/my_endpoint7",
bodyTemplateURI: "file://./stub/test_body.jsonnet",
authStrategy: "basic_auth",
authStrategy: NewBasicAuthStrategy("test-api-user", "secret"),
expectedHeader: map[string][]string{"Content-Type": {ContentTypeJSON}},
body: &testRequestBody{
To: "+14134242223",
From: "+13104661805",
Body: "test-sms-body",
},
expectedBody: "{\n \"body\": \"test-sms-body\",\n \"from\": \"+13104661805\",\n \"to\": \"+14134242223\"\n}\n",
rawConfig: `{
"url": "https://test.kratos.ory.sh/my_endpoint7",
"method": "POST",
"body": "file://./stub/test_body.jsonnet",
"auth": {
"type": "basic_auth",
"config": {
"user": "test-api-user",
"password": "secret"
}
}
expectedJSONBody: `{
"body": "test-sms-body",
"from": "+13104661805",
"to": "+14134242223"
}`,
config: Config{
URL: "https://test.kratos.ory.sh/my_endpoint7",
Method: "POST",
TemplateURI: "file://./stub/test_body.jsonnet",
Auth: AuthConfig{
Type: "basic_auth",
Config: map[string]any{
"user": "test-api-user",
"password": "secret",
},
},
},
},
} {
t.Run(
"request-type="+tc.name, func(t *testing.T) {
rb, err := NewBuilder(context.Background(), json.RawMessage(tc.rawConfig), newTestDependencyProvider(t))
require.NoError(t, err)
t.Run("request-type="+tc.name, func(t *testing.T) {
t.Parallel()
assert.Equal(t, tc.bodyTemplateURI, rb.Config.TemplateURI)
assert.Equal(t, tc.authStrategy, rb.Config.Auth.Type)
req, err := rb.BuildRequest(context.Background(), tc.body)
require.NoError(t, err)
assert.Equal(t, tc.url, req.URL.String())
assert.Equal(t, tc.method, req.Method)
if tc.body != nil {
requestBody, err := req.BodyBytes()
require.NoError(t, err)
assert.Equal(t, tc.expectedBody, string(requestBody))
}
if tc.expectedHeader != nil {
mustContainHeader(t, tc.expectedHeader, req.Header)
}
},
)
}
t.Run(
"cancel request", func(t *testing.T) {
rb, err := NewBuilder(context.Background(), json.RawMessage(
`{
"url": "https://test.kratos.ory.sh/my_endpoint6",
"method": "POST",
"body": "file://./stub/cancel_body.jsonnet"
}`,
), newTestDependencyProvider(t))
rb, err := NewBuilder(context.Background(), &tc.config, newTestDependencyProvider(t))
require.NoError(t, err)
_, err = rb.BuildRequest(context.Background(), json.RawMessage(`{}`))
require.ErrorIs(t, err, ErrCancel)
},
)
assert.Equal(t, tc.bodyTemplateURI, rb.Config.TemplateURI)
assert.Equal(t, tc.authStrategy, rb.Config.auth)
req, err := rb.BuildRequest(context.Background(), tc.body)
require.NoError(t, err)
assert.Equal(t, tc.url, req.URL.String())
assert.Equal(t, tc.method, req.Method)
if tc.expectedJSONBody != "" {
requestBody, err := req.BodyBytes()
require.NoError(t, err)
assert.JSONEq(t, tc.expectedJSONBody, string(requestBody))
} else if tc.expectedRawBody != "" {
requestBody, err := req.BodyBytes()
require.NoError(t, err)
assert.Equal(t, tc.expectedRawBody, string(requestBody))
}
if tc.expectedHeader != nil {
mustContainHeader(t, tc.expectedHeader, req.Header)
}
})
}
t.Run("cancel request", func(t *testing.T) {
rb, err := NewBuilder(context.Background(), &Config{
URL: "https://test.kratos.ory.sh/my_endpoint6",
Method: "POST",
TemplateURI: "file://./stub/cancel_body.jsonnet",
}, newTestDependencyProvider(t))
require.NoError(t, err)
_, err = rb.BuildRequest(context.Background(), json.RawMessage(`{}`))
require.ErrorIs(t, err, ErrCancel)
})
}
type testDependencyProvider struct {

View File

@ -4,49 +4,30 @@
package request
import (
"encoding/json"
"net/http"
"github.com/tidwall/gjson"
)
type (
Auth struct {
Type string
Config json.RawMessage
AuthConfig = struct {
Type string `json:"type" koanf:"type"`
Config map[string]any `json:"config" koanf:"config"`
}
ResponseConfig = struct {
Parse bool `json:"parse" koanf:"parse"`
Ignore bool `json:"ignore" koanf:"ignore"`
}
Config struct {
Method string `json:"method"`
URL string `json:"url"`
TemplateURI string `json:"body"`
Header http.Header `json:"-"`
RawHeader json.RawMessage `json:"headers"`
Auth Auth `json:"auth"`
ID string `json:"id" koanf:"id"`
Method string `json:"method" koanf:"method"`
URL string `json:"url" koanf:"url"`
TemplateURI string `json:"body" koanf:"body"`
Headers map[string]string `json:"headers" koanf:"headers"`
Auth AuthConfig `json:"auth" koanf:"auth"`
EmitAnalyticsEvent *bool `json:"emit_analytics_event" koanf:"emit_analytics_event"`
CanInterrupt bool `json:"can_interrupt" koanf:"can_interrupt"`
Response ResponseConfig `json:"response" koanf:"response"`
auth AuthStrategy
header http.Header
}
)
func (c *Config) UnmarshalJSON(raw []byte) error {
type Alias Config
var a Alias
err := json.Unmarshal(raw, &a)
if err != nil {
return err
}
rawHeader := gjson.ParseBytes(a.RawHeader).Map()
a.Header = make(http.Header, len(rawHeader))
_, ok := rawHeader["Content-Type"]
if !ok {
a.Header.Set("Content-Type", ContentTypeJSON)
}
for key, value := range rawHeader {
a.Header.Set(key, value.String())
}
*c = Config(a)
return nil
}

View File

@ -13,6 +13,9 @@ import (
"os"
"strings"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
@ -28,7 +31,7 @@ type (
x.WriterProvider
x.LoggingProvider
IdentitySchemaProvider
x.CSRFProvider
nosurfx.CSRFProvider
config.Provider
x.TracingProvider
x.HTTPClientProvider
@ -59,8 +62,8 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(fmt.Sprintf("/%s/:id", SchemasPath), x.RedirectToPublicRoute(h.r))
admin.GET(fmt.Sprintf("/%s", SchemasPath), x.RedirectToPublicRoute(h.r))
admin.GET(fmt.Sprintf("/%s/:id", SchemasPath), redir.RedirectToPublicRoute(h.r))
admin.GET(fmt.Sprintf("/%s", SchemasPath), redir.RedirectToPublicRoute(h.r))
}
// Raw JSON Schema

View File

@ -7,6 +7,9 @@ import (
"encoding/json"
"net/http"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/x/stringsx"
"github.com/ory/kratos/driver/config"
@ -33,7 +36,7 @@ type (
}
Handler struct {
r handlerDependencies
csrf x.CSRFToken
csrf nosurfx.CSRFToken
}
)
@ -52,7 +55,7 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(public *x.RouterAdmin) {
public.GET(RouteGet, x.RedirectToPublicRoute(h.r))
public.GET(RouteGet, redir.RedirectToPublicRoute(h.r))
}
// swagger:parameters getFlowError

View File

@ -12,6 +12,8 @@ import (
"net/http/httptest"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/assertx"
"github.com/julienschmidt/httprouter"
@ -34,7 +36,7 @@ func TestHandler(t *testing.T) {
t.Run("case=public authorization", func(t *testing.T) {
router := x.NewRouterPublic()
ns := x.NewTestCSRFHandler(router, reg)
ns := nosurfx.NewTestCSRFHandler(router, reg)
h.RegisterPublicRoutes(router)
router.GET("/regen", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

View File

@ -8,6 +8,8 @@ import (
"net/http"
"net/url"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/driver/config"
"github.com/ory/x/urlx"
@ -20,7 +22,7 @@ type (
PersistenceProvider
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
config.Provider
}

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/ui/container"
@ -71,7 +73,7 @@ func (e *ReplacedError) EnhanceJSONError() interface{} {
func NewFlowReplacedError(message *text.Message) *ReplacedError {
return &ReplacedError{
DefaultError: x.ErrGone.WithID(text.ErrIDSelfServiceFlowReplaced).
DefaultError: nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowReplaced).
WithError("self-service flow replaced").
WithReason(message.Text),
}
@ -142,7 +144,7 @@ func NewFlowExpiredError(at time.Time) *ExpiredError {
return &ExpiredError{
ExpiredAt: at.UTC(),
Since: ago,
DefaultError: x.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
DefaultError: nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
WithError("self-service flow expired").
WithReasonf("The self-service flow expired %.2f minutes ago, initialize a new one.", ago.Minutes()),
}
@ -187,7 +189,7 @@ func NewBrowserLocationChangeRequiredError(redirectTo string) *BrowserLocationCh
}
}
func HandleHookError(_ http.ResponseWriter, r *http.Request, f Flow, traits identity.Traits, group node.UiNodeGroup, flowError error, logger x.LoggingProvider, csrf x.CSRFTokenGeneratorProvider) error {
func HandleHookError(_ http.ResponseWriter, r *http.Request, f Flow, traits identity.Traits, group node.UiNodeGroup, flowError error, logger x.LoggingProvider, csrf nosurfx.CSRFTokenGeneratorProvider) error {
if f != nil {
if traits != nil {
cont, err := container.NewFromStruct("", group, traits, "traits")

View File

@ -9,6 +9,8 @@ import (
"net/http"
"net/url"
"github.com/ory/kratos/x/redir"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
@ -44,5 +46,5 @@ type Flow interface {
}
type FlowWithRedirect interface {
SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []x.SecureRedirectOption)
SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []redir.SecureRedirectOption)
}

View File

@ -13,6 +13,8 @@ import (
"strings"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/tidwall/gjson"
@ -167,11 +169,11 @@ func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Reques
requestURL := x.RequestURL(r).String()
// Pre-validate the return to URL which is contained in the HTTP request.
_, err := x.SecureRedirectTo(r,
_, err := redir.SecureRedirectTo(r,
conf.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(requestURL),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
return nil, err
@ -290,13 +292,13 @@ func (f *Flow) GetUI() *container.Container {
return f.UI
}
func (f *Flow) SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []x.SecureRedirectOption) {
return []x.SecureRedirectOption{
x.SecureRedirectReturnTo(f.ReturnTo),
x.SecureRedirectUseSourceURL(f.RequestURL),
x.SecureRedirectAllowURLs(cfg.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
x.SecureRedirectAllowSelfServiceURLs(cfg.Config().SelfPublicURL(ctx)),
x.SecureRedirectOverrideDefaultReturnTo(cfg.Config().SelfServiceFlowLoginReturnTo(ctx, f.Active.String())),
func (f *Flow) SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []redir.SecureRedirectOption) {
return []redir.SecureRedirectOption{
redir.SecureRedirectReturnTo(f.ReturnTo),
redir.SecureRedirectUseSourceURL(f.RequestURL),
redir.SecureRedirectAllowURLs(cfg.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
redir.SecureRedirectAllowSelfServiceURLs(cfg.Config().SelfPublicURL(ctx)),
redir.SecureRedirectOverrideDefaultReturnTo(cfg.Config().SelfServiceFlowLoginReturnTo(ctx, f.Active.String())),
}
}

View File

@ -14,10 +14,6 @@ import (
"github.com/pkg/errors"
"go.opentelemetry.io/otel/attribute"
"github.com/ory/kratos/x/events"
"github.com/ory/x/otelx"
"github.com/ory/x/otelx/semconv"
"github.com/ory/herodot"
hydraclientgo "github.com/ory/hydra-client-go/v2"
"github.com/ory/kratos/driver/config"
@ -31,8 +27,13 @@ import (
"github.com/ory/kratos/text"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/events"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/nosurf"
"github.com/ory/x/decoderx"
"github.com/ory/x/otelx"
"github.com/ory/x/otelx/semconv"
"github.com/ory/x/sqlxx"
"github.com/ory/x/stringsx"
"github.com/ory/x/urlx"
@ -57,8 +58,8 @@ type (
session.HandlerProvider
session.ManagementProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.TracingProvider
config.Provider
ErrorHandlerProvider
@ -91,12 +92,12 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, redir.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
type FlowOption func(f *Flow)
@ -565,9 +566,9 @@ func (h *Handler) createBrowserLoginFlow(w http.ResponseWriter, r *http.Request,
return
}
returnTo, redirErr := x.SecureRedirectTo(r, h.d.Config().SelfServiceBrowserDefaultReturnTo(ctx),
x.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(ctx)),
x.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
returnTo, redirErr := redir.SecureRedirectTo(r, h.d.Config().SelfServiceBrowserDefaultReturnTo(ctx),
redir.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(ctx)),
redir.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
)
if redirErr != nil {
h.d.SelfServiceErrorManager().Forward(ctx, w, r, redirErr)
@ -667,7 +668,7 @@ func (h *Handler) getLoginFlow(w http.ResponseWriter, r *http.Request, _ httprou
//
// Resolves: https://github.com/ory/kratos/issues/1282
if ar.Type == flow.TypeBrowser && !nosurf.VerifyToken(h.d.GenerateCSRFToken(r), ar.CSRFToken) {
h.d.Writer().WriteError(w, r, x.CSRFErrorReason(r, h.d))
h.d.Writer().WriteError(w, r, nosurfx.CSRFErrorReason(r, h.d))
return
}
@ -675,13 +676,13 @@ func (h *Handler) getLoginFlow(w http.ResponseWriter, r *http.Request, _ httprou
if ar.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(ctx, h.d.Config(), RouteInitBrowserFlow, ar.ReturnTo)
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
WithReason("The login flow has expired. Redirect the user to the login flow init endpoint to initialize a new login flow.").
WithDetail("redirect_to", redirectURL.String()).
WithDetail("return_to", ar.ReturnTo)))
return
}
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
WithReason("The login flow has expired. Call the login flow init API endpoint to initialize a new login flow.").
WithDetail("api", urlx.AppendPaths(h.d.Config().SelfPublicURL(ctx), RouteInitAPIFlow).String())))
return

View File

@ -14,6 +14,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
@ -221,7 +223,7 @@ func TestFlowLifecycle(t *testing.T) {
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(context.Background(), &f))
hc := testhelpers.NewClientWithCookies(t)
res, err := hc.PostForm(ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})
res, err := hc.PostForm(ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {nosurfx.FakeCSRFToken}})
require.NoError(t, err)
firstSession := x.MustReadAll(res.Body)
require.NoError(t, res.Body.Close())
@ -229,7 +231,7 @@ func TestFlowLifecycle(t *testing.T) {
f = login.Flow{Type: tt, ExpiresAt: time.Now().Add(time.Minute), IssuedAt: time.Now(), UI: container.New(""), Refresh: true, RequestedAAL: "aal1"}
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(context.Background(), &f))
vv := testhelpers.EncodeFormAsJSON(t, tt == flow.TypeAPI, url.Values{"method": {"password"}, "password_identifier": {id2mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})
vv := testhelpers.EncodeFormAsJSON(t, tt == flow.TypeAPI, url.Values{"method": {"password"}, "password_identifier": {id2mail}, "password": {"foobar"}, "csrf_token": {nosurfx.FakeCSRFToken}})
req, err := http.NewRequest("POST", ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), strings.NewReader(vv))
require.NoError(t, err)
@ -284,7 +286,7 @@ func TestFlowLifecycle(t *testing.T) {
// Submit Login
hc := testhelpers.NewClientWithCookies(t)
res, err := hc.PostForm(ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})
res, err := hc.PostForm(ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {nosurfx.FakeCSRFToken}})
require.NoError(t, err)
// Check response and session cookie presence
@ -306,7 +308,7 @@ func TestFlowLifecycle(t *testing.T) {
f = login.Flow{Type: flow.TypeBrowser, ExpiresAt: time.Now().Add(time.Minute), IssuedAt: time.Now(), UI: container.New(""), Refresh: true, RequestedAAL: "aal1"}
require.NoError(t, reg.LoginFlowPersister().CreateLoginFlow(context.Background(), &f))
vv := testhelpers.EncodeFormAsJSON(t, false, url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {x.FakeCSRFToken}})
vv := testhelpers.EncodeFormAsJSON(t, false, url.Values{"method": {"password"}, "password_identifier": {id1mail}, "password": {"foobar"}, "csrf_token": {nosurfx.FakeCSRFToken}})
req, err = http.NewRequest("POST", ts.URL+login.RouteSubmitFlow+"?flow="+f.ID.String(), strings.NewReader(vv))
require.NoError(t, err)
@ -843,7 +845,7 @@ func TestGetFlow(t *testing.T) {
setupLoginUI(t, client)
body := testhelpers.EasyGetBody(t, client, public.URL+login.RouteInitBrowserFlow)
assert.EqualValues(t, x.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
assert.EqualValues(t, nosurfx.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
})
t.Run("case=expired", func(t *testing.T) {

View File

@ -10,6 +10,9 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/attribute"
@ -37,8 +40,8 @@ type (
}
HooksProvider interface {
PreLoginHooks(ctx context.Context) []PreHookExecutor
PostLoginHooks(ctx context.Context, credentialsType identity.CredentialsType) []PostHookExecutor
PreLoginHooks(ctx context.Context) ([]PreHookExecutor, error)
PostLoginHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]PostHookExecutor, error)
}
)
@ -50,7 +53,7 @@ type (
identity.ManagementProvider
session.ManagementProvider
session.PersistenceProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.LoggingProvider
x.TracingProvider
@ -148,13 +151,13 @@ func (e *HookExecutor) PostLoginHook(
c := e.d.Config()
// Verify the redirect URL before we do any other processing.
returnTo, err := x.SecureRedirectTo(r,
returnTo, err := redir.SecureRedirectTo(r,
c.SelfServiceBrowserDefaultReturnTo(ctx),
x.SecureRedirectReturnTo(f.ReturnTo),
x.SecureRedirectUseSourceURL(f.RequestURL),
x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
x.SecureRedirectOverrideDefaultReturnTo(c.SelfServiceFlowLoginReturnTo(ctx, f.Active.String())),
redir.SecureRedirectReturnTo(f.ReturnTo),
redir.SecureRedirectUseSourceURL(f.RequestURL),
redir.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
redir.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
redir.SecureRedirectOverrideDefaultReturnTo(c.SelfServiceFlowLoginReturnTo(ctx, f.Active.String())),
)
if err != nil {
return err
@ -177,14 +180,18 @@ func (e *HookExecutor) PostLoginHook(
WithField("identity_id", i.ID).
WithField("flow_method", f.Active).
Debug("Running ExecuteLoginPostHook.")
for k, executor := range e.d.PostLoginHooks(ctx, f.Active) {
hooks, err := e.d.PostLoginHooks(ctx, f.Active)
if err != nil {
return err
}
for k, executor := range hooks {
if err := executor.ExecuteLoginPostHook(w, r, g, f, s); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookExecutorNames(e.d.PostLoginHooks(ctx, f.Active))).
WithField("executors", PostHookExecutorNames(hooks)).
WithField("identity_id", i.ID).
WithField("flow_method", f.Active).
Debug("A ExecuteLoginPostHook hook aborted early.")
@ -200,7 +207,7 @@ func (e *HookExecutor) PostLoginHook(
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookExecutorNames(e.d.PostLoginHooks(ctx, f.Active))).
WithField("executors", PostHookExecutorNames(hooks)).
WithField("identity_id", i.ID).
WithField("flow_method", f.Active).
Debug("ExecuteLoginPostHook completed successfully.")
@ -377,13 +384,17 @@ func (e *HookExecutor) PostLoginHook(
span.SetAttributes(attribute.String("redirect_reason", "verification requested"))
}
x.ContentNegotiationRedirection(w, r, s, e.d.Writer(), finalReturnTo)
redir.ContentNegotiationRedirection(w, r, s, e.d.Writer(), finalReturnTo)
return nil
}
func (e *HookExecutor) PreLoginHook(w http.ResponseWriter, r *http.Request, a *Flow) error {
for _, executor := range e.d.PreLoginHooks(r.Context()) {
if err := executor.ExecuteLoginPreHook(w, r, a); err != nil {
hooks, err := e.d.PreLoginHooks(r.Context())
if err != nil {
return err
}
for _, h := range hooks {
if err := h.ExecuteLoginPreHook(w, r, a); err != nil {
return err
}
}

View File

@ -7,6 +7,9 @@ import (
"net/http"
"net/url"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"go.opentelemetry.io/otel/trace"
"github.com/pkg/errors"
@ -36,7 +39,7 @@ const (
type (
handlerDependencies interface {
x.WriterProvider
x.CSRFProvider
nosurfx.CSRFProvider
session.ManagementProvider
session.PersistenceProvider
errorx.ManagementProvider
@ -67,9 +70,9 @@ func (h *Handler) RegisterPublicRoutes(router *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.DELETE(RouteAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.DELETE(RouteAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
// Logout Flow
@ -153,11 +156,11 @@ func (h *Handler) createBrowserLogoutFlow(w http.ResponseWriter, r *http.Request
if requestURL.Query().Get("return_to") != "" {
// Pre-validate the return to URL which is contained in the HTTP request.
returnTo, err = x.SecureRedirectTo(r,
returnTo, err = redir.SecureRedirectTo(r,
h.d.Config().SelfServiceFlowLogoutRedirectURL(r.Context()),
x.SecureRedirectUseSourceURL(requestURL.String()),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL.String()),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err)
@ -354,10 +357,10 @@ func (h *Handler) updateLogoutFlow(w http.ResponseWriter, r *http.Request, ps ht
func (h *Handler) completeLogout(w http.ResponseWriter, r *http.Request) {
_ = h.d.CSRFHandler().RegenerateToken(w, r)
ret, err := x.SecureRedirectTo(r, h.d.Config().SelfServiceFlowLogoutRedirectURL(r.Context()),
x.SecureRedirectUseSourceURL(r.RequestURI),
x.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(r.Context())),
ret, err := redir.SecureRedirectTo(r, h.d.Config().SelfServiceFlowLogoutRedirectURL(r.Context()),
redir.SecureRedirectUseSourceURL(r.RequestURI),
redir.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(r.Context())),
)
if err != nil {
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err)

View File

@ -13,6 +13,8 @@ import (
"net/url"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/session"
"github.com/julienschmidt/httprouter"
@ -121,7 +123,7 @@ func TestLogout(t *testing.T) {
defer res.Body.Close()
assert.EqualValues(t, http.StatusForbidden, res.StatusCode)
body := x.MustReadAll(res.Body)
assert.EqualValues(t, x.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
assert.EqualValues(t, nosurfx.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
}
t.Run("type=browser", func(t *testing.T) {

View File

@ -6,12 +6,12 @@ package flow
import (
"net/http"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
)
func GetCSRFToken(reg interface {
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
}, w http.ResponseWriter, r *http.Request, p Type) string {
token := reg.GenerateCSRFToken(r)
if p != TypeBrowser {

View File

@ -7,6 +7,8 @@ import (
"net/http"
"net/url"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"go.opentelemetry.io/otel/trace"
@ -39,7 +41,7 @@ type (
errorx.ManagementProvider
x.WriterProvider
x.LoggingProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
config.Provider
StrategyProvider

View File

@ -10,6 +10,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"github.com/ory/x/ioutilx"
@ -74,7 +76,7 @@ func TestHandleError(t *testing.T) {
req := &http.Request{URL: urlx.ParseOrPanic("/")}
s, err := reg.GetActiveRecoveryStrategy(context.Background())
require.NoError(t, err)
f, err := recovery.NewFlow(conf, ttl, x.FakeCSRFToken, req, s, ft)
f, err := recovery.NewFlow(conf, ttl, nosurfx.FakeCSRFToken, req, s, ft)
require.NoError(t, err)
require.NoError(t, reg.RecoveryFlowPersister().CreateRecoveryFlow(context.Background(), f))
f, err = reg.RecoveryFlowPersister().GetRecoveryFlow(context.Background(), f.ID)
@ -332,7 +334,7 @@ func TestHandleError_WithContinueWith(t *testing.T) {
req := &http.Request{URL: urlx.ParseOrPanic("/")}
s, err := reg.GetActiveRecoveryStrategy(context.Background())
require.NoError(t, err)
f, err := recovery.NewFlow(conf, ttl, x.FakeCSRFToken, req, s, ft)
f, err := recovery.NewFlow(conf, ttl, nosurfx.FakeCSRFToken, req, s, ft)
require.NoError(t, err)
require.NoError(t, reg.RecoveryFlowPersister().CreateRecoveryFlow(context.Background(), f))
f, err = reg.RecoveryFlowPersister().GetRecoveryFlow(context.Background(), f.ID)

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
@ -118,11 +120,11 @@ func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Reques
// Pre-validate the return to URL which is contained in the HTTP request.
requestURL := x.RequestURL(r).String()
_, err := x.SecureRedirectTo(r,
_, err := redir.SecureRedirectTo(r,
conf.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(requestURL),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
return nil, err

View File

@ -7,6 +7,9 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/nosurf"
"github.com/ory/kratos/schema"
@ -49,9 +52,9 @@ type (
session.HandlerProvider
StrategyProvider
FlowPersistenceProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.CSRFProvider
nosurfx.CSRFProvider
config.Provider
ErrorHandlerProvider
HookExecutorProvider
@ -88,11 +91,11 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
// swagger:route GET /self-service/recovery/api frontend createNativeRecoveryFlow
@ -291,7 +294,7 @@ func (h *Handler) getRecoveryFlow(w http.ResponseWriter, r *http.Request, _ http
//
// Resolves: https://github.com/ory/kratos/issues/1282
if f.Type.IsBrowser() && !f.DangerousSkipCSRFCheck && !nosurf.VerifyToken(h.d.GenerateCSRFToken(r), f.CSRFToken) {
h.d.Writer().WriteError(w, r, x.CSRFErrorReason(r, h.d))
h.d.Writer().WriteError(w, r, nosurfx.CSRFErrorReason(r, h.d))
return
}
@ -299,7 +302,7 @@ func (h *Handler) getRecoveryFlow(w http.ResponseWriter, r *http.Request, _ http
if f.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(r.Context(), h.d.Config(), RouteInitBrowserFlow, f.ReturnTo)
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.
WithReason("The recovery flow has expired. Redirect the user to the recovery flow init endpoint to initialize a new recovery flow.").
WithDetail("redirect_to", redirectURL.String()).
WithDetail("return_to", f.ReturnTo)))

View File

@ -14,6 +14,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"github.com/ory/kratos/corpx"
@ -228,7 +230,7 @@ func TestGetFlow(t *testing.T) {
setupRecoveryTS(t, client)
body := testhelpers.EasyGetBody(t, client, public.URL+recovery.RouteInitBrowserFlow)
assert.EqualValues(t, x.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
assert.EqualValues(t, nosurfx.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
})
t.Run("case=valid", func(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
"fmt"
"net/http"
"github.com/ory/kratos/x/nosurfx"
"go.opentelemetry.io/otel/trace"
"github.com/ory/kratos/x/events"
@ -32,8 +34,8 @@ type (
PostHookExecutorFunc func(w http.ResponseWriter, r *http.Request, a *Flow, s *session.Session) error
HooksProvider interface {
PreRecoveryHooks(ctx context.Context) []PreHookExecutor
PostRecoveryHooks(ctx context.Context) []PostHookExecutor
PreRecoveryHooks(ctx context.Context) ([]PreHookExecutor, error)
PostRecoveryHooks(ctx context.Context) ([]PostHookExecutor, error)
}
)
@ -60,7 +62,7 @@ type (
identity.ValidationProvider
session.PersistenceProvider
HooksProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.LoggingProvider
x.WriterProvider
}
@ -89,7 +91,11 @@ func (e *HookExecutor) PostRecoveryHook(w http.ResponseWriter, r *http.Request,
}
logger.Debug("Running ExecutePostRecoveryHooks.")
for k, executor := range e.d.PostRecoveryHooks(r.Context()) {
hooks, err := e.d.PostRecoveryHooks(r.Context())
if err != nil {
return err
}
for k, executor := range hooks {
if err := executor.ExecutePostRecoveryHook(w, r, a, s); err != nil {
var traits identity.Traits
if s.Identity != nil {
@ -101,7 +107,7 @@ func (e *HookExecutor) PostRecoveryHook(w http.ResponseWriter, r *http.Request,
logger.
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookRecoveryExecutorNames(e.d.PostRecoveryHooks(r.Context()))).
WithField("executors", PostHookRecoveryExecutorNames(hooks)).
Debug("ExecutePostRecoveryHook completed successfully.")
}
@ -113,7 +119,11 @@ func (e *HookExecutor) PostRecoveryHook(w http.ResponseWriter, r *http.Request,
}
func (e *HookExecutor) PreRecoveryHook(w http.ResponseWriter, r *http.Request, a *Flow) error {
for _, executor := range e.d.PreRecoveryHooks(r.Context()) {
hooks, err := e.d.PreRecoveryHooks(r.Context())
if err != nil {
return err
}
for _, executor := range hooks {
if err := executor.ExecuteRecoveryPreHook(w, r, a); err != nil {
return err
}

View File

@ -10,6 +10,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow/recovery"
"github.com/ory/kratos/selfservice/strategy/code"
@ -35,7 +37,7 @@ func TestRecoveryExecutor(t *testing.T) {
newServer := func(t *testing.T, i *identity.Identity, ft flow.Type) *httptest.Server {
router := httprouter.New()
router.GET("/recovery/pre", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
a, err := recovery.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, s, ft)
a, err := recovery.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, s, ft)
require.NoError(t, err)
if testhelpers.SelfServiceHookErrorHandler(t, w, r, recovery.ErrHookAbortFlow, reg.RecoveryExecutor().PreRecoveryHook(w, r, a)) {
_, _ = w.Write([]byte("ok"))
@ -43,7 +45,7 @@ func TestRecoveryExecutor(t *testing.T) {
})
router.GET("/recovery/post", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
a, err := recovery.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, s, ft)
a, err := recovery.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, s, ft)
require.NoError(t, err)
s, err := testhelpers.NewActiveSession(r,
reg,

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
@ -135,11 +137,11 @@ func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Reques
// Pre-validate the return to URL which is contained in the HTTP request.
requestURL := x.RequestURL(r).String()
_, err := x.SecureRedirectTo(r,
_, err := redir.SecureRedirectTo(r,
conf.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(requestURL),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
return nil, err
@ -250,13 +252,13 @@ func (f *Flow) ContinueWith() []flow.ContinueWith {
return f.ContinueWithItems
}
func (f *Flow) SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []x.SecureRedirectOption) {
return []x.SecureRedirectOption{
x.SecureRedirectReturnTo(f.ReturnTo),
x.SecureRedirectUseSourceURL(f.RequestURL),
x.SecureRedirectAllowURLs(cfg.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
x.SecureRedirectAllowSelfServiceURLs(cfg.Config().SelfPublicURL(ctx)),
x.SecureRedirectOverrideDefaultReturnTo(cfg.Config().SelfServiceFlowRegistrationReturnTo(ctx, f.Active.String())),
func (f *Flow) SecureRedirectToOpts(ctx context.Context, cfg config.Provider) (opts []redir.SecureRedirectOption) {
return []redir.SecureRedirectOption{
redir.SecureRedirectReturnTo(f.ReturnTo),
redir.SecureRedirectUseSourceURL(f.RequestURL),
redir.SecureRedirectAllowURLs(cfg.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
redir.SecureRedirectAllowSelfServiceURLs(cfg.Config().SelfPublicURL(ctx)),
redir.SecureRedirectOverrideDefaultReturnTo(cfg.Config().SelfServiceFlowRegistrationReturnTo(ctx, f.Active.String())),
}
}

View File

@ -8,6 +8,9 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/attribute"
@ -49,8 +52,8 @@ type (
session.HandlerProvider
session.ManagementProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
StrategyProvider
HookExecutorProvider
FlowPersistenceProvider
@ -94,11 +97,11 @@ func (h *Handler) onAuthenticated(w http.ResponseWriter, r *http.Request, ps htt
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, x.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, redir.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
type FlowOption func(f *Flow)
@ -412,9 +415,9 @@ func (h *Handler) createBrowserRegistrationFlow(w http.ResponseWriter, r *http.R
return
}
returnTo, redirErr := x.SecureRedirectTo(r, h.d.Config().SelfServiceBrowserDefaultReturnTo(ctx),
x.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(ctx)),
x.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
returnTo, redirErr := redir.SecureRedirectTo(r, h.d.Config().SelfServiceBrowserDefaultReturnTo(ctx),
redir.SecureRedirectAllowSelfServiceURLs(h.d.Config().SelfPublicURL(ctx)),
redir.SecureRedirectAllowURLs(h.d.Config().SelfServiceBrowserAllowedReturnToDomains(ctx)),
)
if redirErr != nil {
h.d.SelfServiceErrorManager().Forward(ctx, w, r, redirErr)
@ -510,7 +513,7 @@ func (h *Handler) getRegistrationFlow(w http.ResponseWriter, r *http.Request, ps
//
// Resolves: https://github.com/ory/kratos/issues/1282
if ar.Type == flow.TypeBrowser && !nosurf.VerifyToken(h.d.GenerateCSRFToken(r), ar.CSRFToken) {
h.d.Writer().WriteError(w, r, x.CSRFErrorReason(r, h.d))
h.d.Writer().WriteError(w, r, nosurfx.CSRFErrorReason(r, h.d))
return
}
@ -518,13 +521,13 @@ func (h *Handler) getRegistrationFlow(w http.ResponseWriter, r *http.Request, ps
if ar.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(r.Context(), h.d.Config(), RouteInitBrowserFlow, ar.ReturnTo)
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
WithReason("The registration flow has expired. Redirect the user to the registration flow init endpoint to initialize a new registration flow.").
WithDetail("redirect_to", redirectURL.String()).
WithDetail("return_to", ar.ReturnTo)))
return
}
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.WithID(text.ErrIDSelfServiceFlowExpired).
WithReason("The registration flow has expired. Call the registration flow init API endpoint to initialize a new registration flow.").
WithDetail("api", urlx.AppendPaths(h.d.Config().SelfPublicURL(r.Context()), RouteInitAPIFlow).String())))
return

View File

@ -16,6 +16,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/go-faker/faker/v4"
"github.com/gofrs/uuid"
@ -355,7 +357,7 @@ func TestGetFlow(t *testing.T) {
_ = setupRegistrationUI(t, client)
body := testhelpers.EasyGetBody(t, client, public.URL+registration.RouteInitBrowserFlow)
assert.EqualValues(t, x.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
assert.EqualValues(t, nosurfx.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
})
t.Run("case=expired", func(t *testing.T) {

View File

@ -9,6 +9,9 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/pkg/errors"
"go.opentelemetry.io/otel/attribute"
@ -42,9 +45,9 @@ type (
PostHookPrePersistExecutorFunc func(w http.ResponseWriter, r *http.Request, a *Flow, i *identity.Identity) error
HooksProvider interface {
PreRegistrationHooks(ctx context.Context) []PreHookExecutor
PostRegistrationPrePersistHooks(ctx context.Context, credentialsType identity.CredentialsType) []PostHookPrePersistExecutor
PostRegistrationPostPersistHooks(ctx context.Context, credentialsType identity.CredentialsType) []PostHookPostPersistExecutor
PreRegistrationHooks(ctx context.Context) ([]PreHookExecutor, error)
PostRegistrationPrePersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]PostHookPrePersistExecutor, error)
PostRegistrationPostPersistHooks(ctx context.Context, credentialsType identity.CredentialsType) ([]PostHookPostPersistExecutor, error)
}
)
@ -81,7 +84,7 @@ type (
HooksProvider
FlowPersistenceProvider
hydra.Provider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.HTTPClientProvider
x.LoggingProvider
x.WriterProvider
@ -111,14 +114,18 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("Running PostRegistrationPrePersistHooks.")
for k, executor := range e.d.PostRegistrationPrePersistHooks(ctx, ct) {
preHooks, err := e.d.PostRegistrationPrePersistHooks(ctx, ct)
if err != nil {
return err
}
for k, executor := range preHooks {
if err := executor.ExecutePostRegistrationPrePersistHook(w, r, registrationFlow, i); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPrePersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(preHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("A ExecutePostRegistrationPrePersistHook hook aborted early.")
@ -129,7 +136,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPrePersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(preHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
WithError(err).
@ -142,7 +149,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
e.d.Logger().WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPrePersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(preHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("ExecutePostRegistrationPrePersistHook completed successfully.")
@ -187,12 +194,12 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
// Verify the redirect URL before we do any other processing.
c := e.d.Config()
returnTo, err := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(ctx),
x.SecureRedirectReturnTo(registrationFlow.ReturnTo),
x.SecureRedirectUseSourceURL(registrationFlow.RequestURL),
x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
x.SecureRedirectOverrideDefaultReturnTo(c.SelfServiceFlowRegistrationReturnTo(ctx, ct.String())),
returnTo, err := redir.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(ctx),
redir.SecureRedirectReturnTo(registrationFlow.ReturnTo),
redir.SecureRedirectUseSourceURL(registrationFlow.RequestURL),
redir.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
redir.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
redir.SecureRedirectOverrideDefaultReturnTo(c.SelfServiceFlowRegistrationReturnTo(ctx, ct.String())),
)
if err != nil {
return err
@ -232,14 +239,18 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("Running PostRegistrationPostPersistHooks.")
for k, executor := range e.d.PostRegistrationPostPersistHooks(ctx, ct) {
postHooks, err := e.d.PostRegistrationPostPersistHooks(ctx, ct)
if err != nil {
return err
}
for k, executor := range postHooks {
if err := executor.ExecutePostRegistrationPostPersistHook(w, r, registrationFlow, s); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPostPersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(postHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("A ExecutePostRegistrationPostPersistHook hook aborted early.")
@ -253,7 +264,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPostPersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(postHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
WithError(err).
@ -268,7 +279,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
e.d.Logger().WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", ExecutorNames(e.d.PostRegistrationPostPersistHooks(ctx, ct))).
WithField("executors", ExecutorNames(postHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", ct).
Debug("ExecutePostRegistrationPostPersistHook completed successfully.")
@ -325,7 +336,7 @@ func (e *HookExecutor) PostRegistrationHook(w http.ResponseWriter, r *http.Reque
}
span.SetAttributes(attribute.String("return_to", finalReturnTo))
x.ContentNegotiationRedirection(w, r, s.Declassified(), e.d.Writer(), finalReturnTo)
redir.ContentNegotiationRedirection(w, r, s.Declassified(), e.d.Writer(), finalReturnTo)
return nil
}
@ -338,7 +349,11 @@ func (e *HookExecutor) getDuplicateIdentifier(ctx context.Context, i *identity.I
}
func (e *HookExecutor) PreRegistrationHook(w http.ResponseWriter, r *http.Request, a *Flow) error {
for _, executor := range e.d.PreRegistrationHooks(r.Context()) {
hooks, err := e.d.PreRegistrationHooks(r.Context())
if err != nil {
return err
}
for _, executor := range hooks {
if err := executor.ExecuteRegistrationPreHook(w, r, a); err != nil {
return err
}

View File

@ -11,6 +11,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gobuffalo/httptest"
"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
@ -49,7 +51,7 @@ func TestRegistrationExecutor(t *testing.T) {
handleErr := testhelpers.SelfServiceHookRegistrationErrorHandler
router.GET("/registration/pre", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
f, err := registration.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, ft)
f, err := registration.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, ft)
require.NoError(t, err)
if handleErr(t, w, r, reg.RegistrationHookExecutor().PreRegistrationHook(w, r, f)) {
_, _ = w.Write([]byte("ok"))
@ -60,7 +62,7 @@ func TestRegistrationExecutor(t *testing.T) {
if i == nil {
i = testhelpers.SelfServiceHookFakeIdentity(t)
}
regFlow, err := registration.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, ft)
regFlow, err := registration.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, ft)
require.NoError(t, err)
regFlow.RequestURL = x.RequestURL(r).String()
for _, callback := range flowCallbacks {

View File

@ -9,6 +9,8 @@ import (
"net/http"
"strings"
"github.com/ory/kratos/x/nosurfx"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
@ -19,7 +21,6 @@ import (
"github.com/pkg/errors"
"github.com/ory/herodot"
"github.com/ory/kratos/x"
"github.com/ory/nosurf"
)
@ -70,7 +71,7 @@ func EnsureCSRF(
return nil
default:
if !nosurf.VerifyToken(generator(r), actual) {
return errors.WithStack(x.CSRFErrorReason(r, reg))
return errors.WithStack(nosurfx.CSRFErrorReason(r, reg))
}
}

View File

@ -12,6 +12,8 @@ import (
"net/url"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/stretchr/testify/assert"
"github.com/ory/herodot"
@ -20,25 +22,23 @@ import (
"github.com/ory/kratos/selfservice/flow"
"github.com/stretchr/testify/require"
"github.com/ory/kratos/x"
)
func TestVerifyRequest(t *testing.T) {
_, reg := internal.NewFastRegistryWithMocks(t)
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeBrowser, false, x.FakeCSRFTokenGenerator, "not_csrf_token"), x.ErrInvalidCSRFToken.Error())
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeBrowser, false, x.FakeCSRFTokenGenerator, x.FakeCSRFToken), nil)
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""))
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeBrowser, false, nosurfx.FakeCSRFTokenGenerator, "not_csrf_token"), nosurfx.ErrInvalidCSRFToken.Error())
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeBrowser, false, nosurfx.FakeCSRFTokenGenerator, nosurfx.FakeCSRFToken), nil)
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""))
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Origin": {"https://www.ory.sh"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), flow.ErrOriginHeaderNeedsBrowserFlow.Error())
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), flow.ErrOriginHeaderNeedsBrowserFlow.Error())
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"cookie=ory"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of cookie=ory")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of cookie=ory")
err := flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"cookie1=cookievalue", "cookie2=cookievalue"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, "")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, "")
var he herodot.DetailsCarrier
require.ErrorAs(t, err, &he)
cs, ok := he.Details()["found cookies"].([]string)
@ -48,17 +48,17 @@ func TestVerifyRequest(t *testing.T) {
// Cloudflare
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"__cflb=0pg1RtZzPoPDprTf8gX3TJm8XF5hKZ4pZV74UCe7", "_cfuvid=blub", "cf_clearance=bla"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), "should ignore Cloudflare cookies")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), "should ignore Cloudflare cookies")
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"__cflb=0pg1RtZzPoPDprTf8gX3TJm8XF5hKZ4pZV74UCe7; __cfruid=0pg1RtZzPoPDprTf8gX3TJm8XF5hKZ4pZV74UCe7"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), "should ignore Cloudflare cookies")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), "should ignore Cloudflare cookies")
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"__cflb=0pg1RtZzPoPDprTf8gX3TJm8XF5hKZ4pZV74UCe7; __cfruid=0pg1RtZzPoPDprTf8gX3TJm8XF5hKZ4pZV74UCe7; some_cookie=some_value"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of some_cookie")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of some_cookie")
require.EqualError(t, flow.EnsureCSRF(reg, &http.Request{
Header: http.Header{"Cookie": {"some_cookie=some_value"}},
}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of some_cookie")
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeAPI, false, x.FakeCSRFTokenGenerator, ""), "no cookie, no error")
}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), flow.ErrCookieHeaderNeedsBrowserFlow.Error(), "should error because of some_cookie")
require.NoError(t, flow.EnsureCSRF(reg, &http.Request{}, flow.TypeAPI, false, nosurfx.FakeCSRFTokenGenerator, ""), "no cookie, no error")
}
func TestMethodEnabledAndAllowed(t *testing.T) {

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/ory/kratos/text"
@ -142,11 +144,11 @@ func NewFlow(conf *config.Config, exp time.Duration, r *http.Request, i *identit
// Pre-validate the return to URL which is contained in the HTTP request.
requestURL := x.RequestURL(r).String()
_, err := x.SecureRedirectTo(r,
_, err := redir.SecureRedirectTo(r,
conf.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(requestURL),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
return nil, err

View File

@ -9,6 +9,9 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/x/otelx"
"github.com/julienschmidt/httprouter"
@ -48,7 +51,7 @@ func ContinuityKey(id string) string {
type (
handlerDependencies interface {
x.CSRFProvider
nosurfx.CSRFProvider
x.WriterProvider
x.LoggingProvider
x.TracingProvider
@ -70,7 +73,7 @@ type (
FlowPersistenceProvider
StrategyProvider
HookExecutorProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
schema.IdentitySchemaProvider
@ -81,7 +84,7 @@ type (
}
Handler struct {
d handlerDependencies
csrf x.CSRFToken
csrf nosurfx.CSRFToken
}
)
@ -98,7 +101,7 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
h.d.Writer().WriteError(w, r, session.NewErrNoActiveSessionFound())
} else {
loginFlowUrl := h.d.Config().SelfPublicURL(r.Context()).JoinPath(login.RouteInitBrowserFlow).String()
redirectUrl, err := x.TakeOverReturnToParameter(r.URL.String(), loginFlowUrl)
redirectUrl, err := redir.TakeOverReturnToParameter(r.URL.String(), loginFlowUrl)
if err != nil {
http.Redirect(w, r, h.d.Config().SelfServiceFlowLoginUI(r.Context()).String(), http.StatusSeeOther)
} else {
@ -115,13 +118,13 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, redir.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
func (h *Handler) NewFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, i *identity.Identity, ft flow.Type) (_ *Flow, err error) {
@ -437,13 +440,13 @@ func (h *Handler) getSettingsFlow(w http.ResponseWriter, r *http.Request, _ http
if pr.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(ctx, h.d.Config(), RouteInitBrowserFlow, pr.ReturnTo)
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.
WithReason("The settings flow has expired. Redirect the user to the settings flow init endpoint to initialize a new settings flow.").
WithDetail("redirect_to", redirectURL.String()).
WithDetail("return_to", pr.ReturnTo)))
return
}
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.
WithReason("The settings flow has expired. Call the settings flow init API endpoint to initialize a new settings flow.").
WithDetail("api", urlx.AppendPaths(h.d.Config().SelfPublicURL(ctx), RouteInitAPIFlow).String())))
return

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/text"
"github.com/ory/x/assertx"
@ -579,7 +581,7 @@ func TestHandler(t *testing.T) {
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, primaryUser, fmt.Sprintf(`{"method":"profile", "numby": 15, "csrf_token": "%s"}`, x.FakeCSRFToken))
actual, res := testhelpers.SettingsMakeRequest(t, false, true, &f, primaryUser, fmt.Sprintf(`{"method":"profile", "numby": 15, "csrf_token": "%s"}`, nosurfx.FakeCSRFToken))
require.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")
@ -591,7 +593,7 @@ func TestHandler(t *testing.T) {
var f kratos.SettingsFlow
require.NoError(t, json.Unmarshal(body, &f))
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, primaryUser, `method=profile&traits.numby=15&csrf_token=`+x.FakeCSRFToken)
actual, res := testhelpers.SettingsMakeRequest(t, false, false, &f, primaryUser, `method=profile&traits.numby=15&csrf_token=`+nosurfx.FakeCSRFToken)
assert.Equal(t, http.StatusOK, res.StatusCode)
require.Len(t, primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL+login.RouteGetFlow)), 1)
require.Contains(t, fmt.Sprintf("%v", primaryUser.Jar.Cookies(urlx.ParseOrPanic(publicTS.URL))), "ory_kratos_session")

View File

@ -9,6 +9,9 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/x/otelx"
"go.opentelemetry.io/otel/trace"
@ -51,9 +54,9 @@ type (
PostHookPostPersistExecutorFunc func(w http.ResponseWriter, r *http.Request, a *Flow, id *identity.Identity, s *session.Session) error
HooksProvider interface {
PreSettingsHooks(ctx context.Context) []PreHookExecutor
PostSettingsPrePersistHooks(ctx context.Context, settingsType string) []PostHookPrePersistExecutor
PostSettingsPostPersistHooks(ctx context.Context, settingsType string) []PostHookPostPersistExecutor
PreSettingsHooks(ctx context.Context) ([]PreHookExecutor, error)
PostSettingsPrePersistHooks(ctx context.Context, settingsType string) ([]PostHookPrePersistExecutor, error)
PostSettingsPostPersistHooks(ctx context.Context, settingsType string) ([]PostHookPostPersistExecutor, error)
}
executorDependencies interface {
@ -66,7 +69,7 @@ type (
HooksProvider
FlowPersistenceProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.LoggingProvider
x.WriterProvider
x.TracingProvider
@ -166,11 +169,11 @@ func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWrit
// Verify the redirect URL before we do any other processing.
c := e.d.Config()
returnTo, err := x.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(ctx),
x.SecureRedirectUseSourceURL(ctxUpdate.Flow.RequestURL),
x.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
x.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
x.SecureRedirectOverrideDefaultReturnTo(
returnTo, err := redir.SecureRedirectTo(r, c.SelfServiceBrowserDefaultReturnTo(ctx),
redir.SecureRedirectUseSourceURL(ctxUpdate.Flow.RequestURL),
redir.SecureRedirectAllowURLs(c.SelfServiceBrowserAllowedReturnToDomains(ctx)),
redir.SecureRedirectAllowSelfServiceURLs(c.SelfPublicURL(ctx)),
redir.SecureRedirectOverrideDefaultReturnTo(
e.d.Config().SelfServiceFlowSettingsReturnTo(ctx, settingsType,
ctxUpdate.Flow.AppendTo(e.d.Config().SelfServiceFlowSettingsUI(ctx)))),
)
@ -183,11 +186,15 @@ func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWrit
f(hookOptions)
}
for k, executor := range e.d.PostSettingsPrePersistHooks(ctx, settingsType) {
preHooks, err := e.d.PostSettingsPrePersistHooks(ctx, settingsType)
if err != nil {
return err
}
for k, executor := range preHooks {
logFields := logrus.Fields{
"executor": fmt.Sprintf("%T", executor),
"executor_position": k,
"executors": PostHookPrePersistExecutorNames(e.d.PostSettingsPrePersistHooks(ctx, settingsType)),
"executors": PostHookPrePersistExecutorNames(preHooks),
"identity_id": i.ID,
"flow_method": settingsType,
}
@ -253,14 +260,18 @@ func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWrit
return err
}
for k, executor := range e.d.PostSettingsPostPersistHooks(ctx, settingsType) {
postHooks, err := e.d.PostSettingsPostPersistHooks(ctx, settingsType)
if err != nil {
return err
}
for k, executor := range postHooks {
if err := executor.ExecuteSettingsPostPersistHook(w, r, ctxUpdate.Flow, i, ctxUpdate.Session); err != nil {
if errors.Is(err, ErrHookAbortFlow) {
e.d.Logger().
WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(ctx, settingsType))).
WithField("executors", PostHookPostPersistExecutorNames(postHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", settingsType).
Debug("A ExecuteSettingsPostPersistHook hook aborted early.")
@ -272,7 +283,7 @@ func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWrit
e.d.Logger().WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookPostPersistExecutorNames(e.d.PostSettingsPostPersistHooks(ctx, settingsType))).
WithField("executors", PostHookPostPersistExecutorNames(postHooks)).
WithField("identity_id", i.ID).
WithField("flow_method", settingsType).
Debug("ExecuteSettingsPostPersistHook completed successfully.")
@ -318,7 +329,7 @@ func (e *HookExecutor) PostSettingsHook(ctx context.Context, w http.ResponseWrit
return nil
}
x.ContentNegotiationRedirection(w, r, i.CopyWithoutCredentials(), e.d.Writer(), returnTo.String())
redir.ContentNegotiationRedirection(w, r, i.CopyWithoutCredentials(), e.d.Writer(), returnTo.String())
return nil
}
@ -326,7 +337,11 @@ func (e *HookExecutor) PreSettingsHook(ctx context.Context, w http.ResponseWrite
ctx, span := e.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.HookExecutor.PreSettingsHook")
defer otelx.End(span, &err)
for _, executor := range e.d.PreSettingsHooks(ctx) {
hooks, err := e.d.PreSettingsHooks(ctx)
if err != nil {
return err
}
for _, executor := range hooks {
if err := executor.ExecuteSettingsPreHook(w, r, a); err != nil {
return err
}

View File

@ -7,6 +7,8 @@ import (
"net/http"
"net/url"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"go.opentelemetry.io/otel/trace"
@ -35,8 +37,8 @@ type (
errorx.ManagementProvider
x.WriterProvider
x.LoggingProvider
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
config.Provider
FlowPersistenceProvider
StrategyProvider

View File

@ -10,6 +10,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"github.com/ory/x/jsonx"
@ -72,7 +74,7 @@ func TestHandleError(t *testing.T) {
req := &http.Request{URL: urlx.ParseOrPanic("/")}
strategy, err := reg.GetActiveVerificationStrategy(context.Background())
require.NoError(t, err)
f, err := verification.NewFlow(conf, ttl, x.FakeCSRFToken, req, strategy, ft)
f, err := verification.NewFlow(conf, ttl, nosurfx.FakeCSRFToken, req, strategy, ft)
require.NoError(t, err)
require.NoError(t, reg.VerificationFlowPersister().CreateVerificationFlow(context.Background(), f))
f, err = reg.VerificationFlowPersister().GetVerificationFlow(context.Background(), f.ID)

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
@ -131,11 +133,11 @@ func NewFlow(conf *config.Config, exp time.Duration, csrf string, r *http.Reques
// Pre-validate the return to URL which is contained in the HTTP request.
requestURL := x.RequestURL(r).String()
_, err := x.SecureRedirectTo(r,
_, err := redir.SecureRedirectTo(r,
conf.SelfServiceBrowserDefaultReturnTo(r.Context()),
x.SecureRedirectUseSourceURL(requestURL),
x.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
x.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
redir.SecureRedirectUseSourceURL(requestURL),
redir.SecureRedirectAllowURLs(conf.SelfServiceBrowserAllowedReturnToDomains(r.Context())),
redir.SecureRedirectAllowSelfServiceURLs(conf.SelfPublicURL(r.Context())),
)
if err != nil {
return nil, err
@ -272,9 +274,9 @@ func (f *Flow) ContinueURL(ctx context.Context, config *config.Config) *url.URL
verificationRequest := http.Request{URL: verificationRequestURL}
returnTo, err := x.SecureRedirectTo(&verificationRequest, flowContinueURL,
x.SecureRedirectAllowSelfServiceURLs(config.SelfPublicURL(ctx)),
x.SecureRedirectAllowURLs(config.SelfServiceBrowserAllowedReturnToDomains(ctx)),
returnTo, err := redir.SecureRedirectTo(&verificationRequest, flowContinueURL,
redir.SecureRedirectAllowSelfServiceURLs(config.SelfPublicURL(ctx)),
redir.SecureRedirectAllowURLs(config.SelfServiceBrowserAllowedReturnToDomains(ctx)),
)
if err != nil {
// an error occured return flow default, or global default return URL

View File

@ -7,6 +7,9 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/ory/kratos/hydra"
"github.com/ory/kratos/session"
"github.com/ory/nosurf"
@ -50,9 +53,9 @@ type (
session.PersistenceProvider
session.ManagementProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.CSRFProvider
nosurfx.CSRFProvider
x.LoggingProvider
FlowPersistenceProvider
@ -82,12 +85,12 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
}
func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
admin.GET(RouteInitBrowserFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteInitBrowserFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteInitAPIFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteGetFlow, redir.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, x.RedirectToPublicRoute(h.d))
admin.POST(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
admin.GET(RouteSubmitFlow, redir.RedirectToPublicRoute(h.d))
}
type FlowOption func(f *Flow)
@ -298,7 +301,7 @@ func (h *Handler) getVerificationFlow(w http.ResponseWriter, r *http.Request, _
//
// Resolves: https://github.com/ory/kratos/issues/1282
if req.Type == flow.TypeBrowser && !nosurf.VerifyToken(h.d.GenerateCSRFToken(r), req.CSRFToken) {
h.d.Writer().WriteError(w, r, x.CSRFErrorReason(r, h.d))
h.d.Writer().WriteError(w, r, nosurfx.CSRFErrorReason(r, h.d))
return
}
@ -306,13 +309,13 @@ func (h *Handler) getVerificationFlow(w http.ResponseWriter, r *http.Request, _
if req.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(r.Context(), h.d.Config(), RouteInitBrowserFlow, req.ReturnTo)
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.
WithReason("The verification flow has expired. Redirect the user to the verification flow init endpoint to initialize a new verification flow.").
WithDetail("redirect_to", redirectURL.String()).
WithDetail("return_to", req.ReturnTo)))
return
}
h.d.Writer().WriteError(w, r, errors.WithStack(x.ErrGone.
h.d.Writer().WriteError(w, r, errors.WithStack(nosurfx.ErrGone.
WithReason("The verification flow has expired. Call the verification flow init API endpoint to initialize a new verification flow.").
WithDetail("api", urlx.AppendPaths(h.d.Config().SelfPublicURL(r.Context()), RouteInitAPIFlow).String())))
return

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gobuffalo/httptest"
"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
@ -99,7 +101,7 @@ func TestGetFlow(t *testing.T) {
_ = setupVerificationUI(t, client)
body := testhelpers.EasyGetBody(t, client, public.URL+verification.RouteInitBrowserFlow)
assert.EqualValues(t, x.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
assert.EqualValues(t, nosurfx.ErrInvalidCSRFToken.ReasonField, gjson.GetBytes(body, "error.reason").String(), "%s", body)
})
t.Run("case=expired", func(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
"fmt"
"net/http"
"github.com/ory/kratos/x/nosurfx"
"go.opentelemetry.io/otel/trace"
"github.com/ory/kratos/x/events"
@ -32,8 +34,8 @@ type (
PostHookExecutorFunc func(w http.ResponseWriter, r *http.Request, a *Flow, i *identity.Identity) error
HooksProvider interface {
PostVerificationHooks(ctx context.Context) []PostHookExecutor
PreVerificationHooks(ctx context.Context) []PreHookExecutor
PostVerificationHooks(ctx context.Context) ([]PostHookExecutor, error)
PreVerificationHooks(ctx context.Context) ([]PreHookExecutor, error)
}
)
@ -60,7 +62,7 @@ type (
identity.ValidationProvider
session.PersistenceProvider
HooksProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFTokenGeneratorProvider
x.LoggingProvider
x.WriterProvider
}
@ -81,7 +83,11 @@ func NewHookExecutor(d executorDependencies) *HookExecutor {
}
func (e *HookExecutor) PreVerificationHook(w http.ResponseWriter, r *http.Request, a *Flow) error {
for _, executor := range e.d.PreVerificationHooks(r.Context()) {
hooks, err := e.d.PreVerificationHooks(r.Context())
if err != nil {
return err
}
for _, executor := range hooks {
if err := executor.ExecuteVerificationPreHook(w, r, a); err != nil {
return err
}
@ -95,19 +101,19 @@ func (e *HookExecutor) PostVerificationHook(w http.ResponseWriter, r *http.Reque
WithRequest(r).
WithField("identity_id", i.ID).
Debug("Running ExecutePostVerificationHooks.")
for k, executor := range e.d.PostVerificationHooks(r.Context()) {
hooks, err := e.d.PostVerificationHooks(r.Context())
if err != nil {
return err
}
for k, executor := range hooks {
if err := executor.ExecutePostVerificationHook(w, r, a, i); err != nil {
var traits identity.Traits
if i != nil {
traits = i.Traits
}
return flow.HandleHookError(w, r, a, traits, node.LinkGroup, err, e.d, e.d)
return flow.HandleHookError(w, r, a, i.Traits, node.LinkGroup, err, e.d, e.d)
}
e.d.Logger().WithRequest(r).
WithField("executor", fmt.Sprintf("%T", executor)).
WithField("executor_position", k).
WithField("executors", PostHookVerificationExecutorNames(e.d.PostVerificationHooks(r.Context()))).
WithField("executors", PostHookVerificationExecutorNames(hooks)).
WithField("identity_id", i.ID).
Debug("ExecutePostVerificationHook completed successfully.")
}

View File

@ -10,6 +10,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow/verification"
"github.com/gobuffalo/httptest"
@ -34,7 +36,7 @@ func TestVerificationExecutor(t *testing.T) {
router.GET("/verification/pre", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
strategy, err := reg.GetActiveVerificationStrategy(r.Context())
require.NoError(t, err)
a, err := verification.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, strategy, ft)
a, err := verification.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, strategy, ft)
require.NoError(t, err)
if testhelpers.SelfServiceHookErrorHandler(t, w, r, verification.ErrHookAbortFlow, reg.VerificationExecutor().PreVerificationHook(w, r, a)) {
_, _ = w.Write([]byte("ok"))
@ -44,7 +46,7 @@ func TestVerificationExecutor(t *testing.T) {
router.GET("/verification/post", func(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
strategy, err := reg.GetActiveVerificationStrategy(r.Context())
require.NoError(t, err)
a, err := verification.NewFlow(conf, time.Minute, x.FakeCSRFToken, r, strategy, ft)
a, err := verification.NewFlow(conf, time.Minute, nosurfx.FakeCSRFToken, r, strategy, ft)
require.NoError(t, err)
a.RequestURL = x.RequestURL(r).String()
if testhelpers.SelfServiceHookErrorHandler(t, w, r, verification.ErrHookAbortFlow, reg.VerificationExecutor().PostVerificationHook(w, r, a, i)) {

View File

@ -10,16 +10,18 @@ import (
"io"
"net/http"
"github.com/hashicorp/go-retryablehttp"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.11.0"
"go.opentelemetry.io/otel/trace"
grpccodes "google.golang.org/grpc/codes"
"github.com/ory/herodot"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/request"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/x"
"github.com/ory/x/otelx"
)
@ -27,25 +29,26 @@ import (
type (
PasswordMigration struct {
deps webHookDependencies
conf json.RawMessage
conf *request.Config
}
PasswordMigrationRequest struct {
Identifier string `json:"identifier"`
Password string `json:"password"`
Identifier string `json:"identifier"`
Password string `json:"password"`
Identity *identity.Identity `json:"-"`
}
PasswordMigrationResponse struct {
Status string `json:"status"`
}
)
func NewPasswordMigrationHook(deps webHookDependencies, conf json.RawMessage) *PasswordMigration {
func NewPasswordMigrationHook(deps webHookDependencies, conf *request.Config) *PasswordMigration {
return &PasswordMigration{deps: deps, conf: conf}
}
func (p *PasswordMigration) Execute(ctx context.Context, data *PasswordMigrationRequest) (err error) {
func (p *PasswordMigration) Execute(ctx context.Context, req *http.Request, flow flow.Flow, data *PasswordMigrationRequest) (err error) {
var (
httpClient = p.deps.HTTPClient(ctx)
emitEvent = gjson.GetBytes(p.conf, "emit_analytics_event").Bool() || !gjson.GetBytes(p.conf, "emit_analytics_event").Exists() // default true
emitEvent = p.conf.EmitAnalyticsEvent == nil || *p.conf.EmitAnalyticsEvent // default true
tracer = trace.SpanFromContext(ctx).TracerProvider().Tracer("kratos-webhooks")
)
@ -59,22 +62,46 @@ func (p *PasswordMigration) Execute(ctx context.Context, data *PasswordMigration
if err != nil {
return errors.WithStack(err)
}
req, err := builder.BuildRequest(ctx, nil) // passing a nil body here skips Jsonnet
if err != nil {
return errors.WithStack(err)
}
rawData, err := json.Marshal(data)
if err != nil {
return errors.WithStack(err)
}
if err = req.SetBody(rawData); err != nil {
return errors.WithStack(err)
var whReq *retryablehttp.Request
if p.conf.TemplateURI == "" {
whReq, err = builder.BuildRequest(ctx, nil) // passing a nil body here skips Jsonnet
if err != nil {
return err
}
rawData, err := json.Marshal(data)
if err != nil {
return errors.WithStack(err)
}
if err = whReq.SetBody(rawData); err != nil {
return errors.WithStack(err)
}
} else {
type templateContextMerged struct {
templateContext
Password string `json:"password"`
Identifier string `json:"identifier"`
}
whReq, err = builder.BuildRequest(ctx, templateContextMerged{
templateContext: templateContext{
Flow: flow,
RequestHeaders: req.Header,
RequestMethod: req.Method,
RequestURL: x.RequestURL(req).String(),
RequestCookies: cookies(req),
Identity: data.Identity,
},
Password: data.Password,
Identifier: data.Identifier,
})
if err != nil {
return err
}
}
p.deps.Logger().WithRequest(req.Request).Info("Dispatching password migration hook")
req = req.WithContext(ctx)
p.deps.Logger().WithRequest(whReq.Request).Info("Dispatching password migration hook")
whReq = whReq.WithContext(ctx)
resp, err := httpClient.Do(req)
resp, err := httpClient.Do(whReq)
if err != nil {
return herodot.DefaultError{
CodeField: http.StatusBadGateway,

View File

@ -20,6 +20,7 @@ import (
"github.com/ory/kratos/text"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/otelx"
)
@ -32,8 +33,8 @@ var (
type (
verifierDependencies interface {
config.Provider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
verification.StrategyProvider
verification.FlowPersistenceProvider
identity.PrivilegedPoolProvider

View File

@ -17,7 +17,6 @@ import (
"github.com/gofrs/uuid"
"github.com/hashicorp/go-retryablehttp"
"github.com/pkg/errors"
"github.com/tidwall/gjson"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
semconv "go.opentelemetry.io/otel/semconv/v1.11.0"
@ -90,7 +89,7 @@ type (
WebHook struct {
deps webHookDependencies
conf json.RawMessage
conf *request.Config
}
detailedMessage struct {
@ -120,7 +119,7 @@ func cookies(req *http.Request) map[string]string {
return cookies
}
func NewWebHook(r webHookDependencies, c json.RawMessage) *WebHook {
func NewWebHook(r webHookDependencies, c *request.Config) *WebHook {
return &WebHook{deps: r, conf: c}
}
@ -213,7 +212,7 @@ func (e *WebHook) ExecuteRegistrationPreHook(_ http.ResponseWriter, req *http.Re
}
func (e *WebHook) ExecutePostRegistrationPrePersistHook(_ http.ResponseWriter, req *http.Request, flow *registration.Flow, id *identity.Identity) error {
if !(gjson.GetBytes(e.conf, "can_interrupt").Bool() || gjson.GetBytes(e.conf, "response.parse").Bool()) {
if !(e.conf.CanInterrupt || e.conf.Response.Parse) {
return nil
}
@ -230,7 +229,7 @@ func (e *WebHook) ExecutePostRegistrationPrePersistHook(_ http.ResponseWriter, r
}
func (e *WebHook) ExecutePostRegistrationPostPersistHook(_ http.ResponseWriter, req *http.Request, flow *registration.Flow, session *session.Session) error {
if gjson.GetBytes(e.conf, "can_interrupt").Bool() || gjson.GetBytes(e.conf, "response.parse").Bool() {
if e.conf.CanInterrupt || e.conf.Response.Parse {
return nil
}
@ -263,7 +262,7 @@ func (e *WebHook) ExecuteSettingsPreHook(_ http.ResponseWriter, req *http.Reques
}
func (e *WebHook) ExecuteSettingsPostPersistHook(_ http.ResponseWriter, req *http.Request, flow *settings.Flow, id *identity.Identity, _ *session.Session) error {
if gjson.GetBytes(e.conf, "can_interrupt").Bool() || gjson.GetBytes(e.conf, "response.parse").Bool() {
if e.conf.CanInterrupt || e.conf.Response.Parse {
return nil
}
return otelx.WithSpan(req.Context(), "selfservice.hook.WebHook.ExecuteSettingsPostPersistHook", func(ctx context.Context) error {
@ -279,7 +278,7 @@ func (e *WebHook) ExecuteSettingsPostPersistHook(_ http.ResponseWriter, req *htt
}
func (e *WebHook) ExecuteSettingsPrePersistHook(_ http.ResponseWriter, req *http.Request, flow *settings.Flow, id *identity.Identity) error {
if !(gjson.GetBytes(e.conf, "can_interrupt").Bool() || gjson.GetBytes(e.conf, "response.parse").Bool()) {
if !(e.conf.CanInterrupt || e.conf.Response.Parse) {
return nil
}
return otelx.WithSpan(req.Context(), "selfservice.hook.WebHook.ExecuteSettingsPrePersistHook", func(ctx context.Context) error {
@ -297,11 +296,11 @@ func (e *WebHook) ExecuteSettingsPrePersistHook(_ http.ResponseWriter, req *http
func (e *WebHook) execute(ctx context.Context, data *templateContext) error {
var (
httpClient = e.deps.HTTPClient(ctx)
ignoreResponse = gjson.GetBytes(e.conf, "response.ignore").Bool()
canInterrupt = gjson.GetBytes(e.conf, "can_interrupt").Bool()
parseResponse = gjson.GetBytes(e.conf, "response.parse").Bool()
emitEvent = gjson.GetBytes(e.conf, "emit_analytics_event").Bool() || !gjson.GetBytes(e.conf, "emit_analytics_event").Exists() // default true
webhookID = gjson.GetBytes(e.conf, "id").Str
ignoreResponse = e.conf.Response.Ignore
canInterrupt = e.conf.CanInterrupt
parseResponse = e.conf.Response.Parse
emitEvent = e.conf.EmitAnalyticsEvent == nil || *e.conf.EmitAnalyticsEvent // default true
webhookID = e.conf.ID
// The trigger ID is a random ID. It can be used to correlate webhook requests across retries.
triggerID = x.NewUUID()
tracer = trace.SpanFromContext(ctx).TracerProvider().Tracer("kratos-webhooks")

View File

@ -33,6 +33,7 @@ import (
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/internal"
"github.com/ory/kratos/request"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
@ -50,6 +51,7 @@ import (
"github.com/ory/x/logrusx"
"github.com/ory/x/otelx"
"github.com/ory/x/otelx/semconv"
"github.com/ory/x/pointerx"
"github.com/ory/x/snapshotx"
)
@ -297,58 +299,43 @@ func TestWebHooks(t *testing.T) {
t.Run("uc="+tc.uc, func(t *testing.T) {
t.Parallel()
for _, auth := range []struct {
uc string
createAuthConfig func() string
expectedHeader func(header http.Header)
uc string
authConfig request.AuthConfig
expectedHeader func(header http.Header)
}{
{
uc: "no auth",
createAuthConfig: func() string { return "{}" },
expectedHeader: func(header http.Header) {},
uc: "no auth",
authConfig: request.AuthConfig{},
expectedHeader: func(header http.Header) {},
},
{
uc: "api key in header",
createAuthConfig: func() string {
return `{
"type": "api_key",
"config": {
"name": "My-Key",
"value": "My-Key-Value",
"in": "header"
}
}`
},
authConfig: request.AuthConfig{Type: "api_key", Config: map[string]any{
"name": "My-Key",
"value": "My-Key-Value",
"in": "header",
}},
expectedHeader: func(header http.Header) {
header.Set("My-Key", "My-Key-Value")
},
},
{
uc: "api key in cookie",
createAuthConfig: func() string {
return `{
"type": "api_key",
"config": {
"name": "My-Key",
"value": "My-Key-Value",
"in": "cookie"
}
}`
},
authConfig: request.AuthConfig{Type: "api_key", Config: map[string]any{
"name": "My-Key",
"value": "My-Key-Value",
"in": "cookie",
}},
expectedHeader: func(header http.Header) {
header.Set("Cookie", "My-Key=My-Key-Value")
},
},
{
uc: "basic auth",
createAuthConfig: func() string {
return `{
"type": "basic_auth",
"config": {
"user": "My-User",
"password": "Super-Secret"
}
}`
},
authConfig: request.AuthConfig{Type: "basic_auth", Config: map[string]any{
"user": "My-User",
"password": "Super-Secret",
}},
expectedHeader: func(header http.Header) {
header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("My-User:Super-Secret")))
},
@ -387,14 +374,13 @@ func TestWebHooks(t *testing.T) {
s := &session.Session{ID: x.NewUUID(), Identity: &identity.Identity{ID: x.NewUUID()}}
whr := &WebHookRequest{}
ts := newServer(webHookEndPoint(whr))
conf := json.RawMessage(fmt.Sprintf(`{
"url": "%s",
"method": "%s",
"body": "%s",
"auth": %s
}`, ts.URL+path, method, "file://./stub/test_body.jsonnet", auth.createAuthConfig()))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: method,
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
Auth: auth.authConfig,
})
err = tc.callWebHook(wh, req, f, s)
if method == "GARBAGE" {
@ -668,14 +654,13 @@ func TestWebHooks(t *testing.T) {
s := &session.Session{ID: x.NewUUID(), Identity: &identity.Identity{ID: x.NewUUID()}}
code, res := tc.webHookResponse()
ts := newServer(webHookHttpCodeWithBodyEndPoint(t, code, res))
conf := json.RawMessage(fmt.Sprintf(`{
"url": "%s",
"method": "%s",
"body": "%s",
"can_interrupt": true
}`, ts.URL+path, method, "file://./stub/test_body.jsonnet"))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: method,
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
CanInterrupt: true,
})
err := tc.callWebHook(wh, req, f, s)
if tc.expectedError == nil {
@ -705,8 +690,14 @@ func TestWebHooks(t *testing.T) {
URL: &url.URL{Path: "some_end_point"},
}
ts := newServer(webHookHttpCodeWithBodyEndPoint(t, responseCode, response))
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "POST", "body": "%s", "response": {"parse":true}}`, ts.URL+path, "file://./stub/test_body.jsonnet"))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "POST",
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
Response: request.ResponseConfig{
Parse: true,
},
})
in := &id
err := wh.ExecutePostRegistrationPrePersistHook(nil, req, f, in)
require.NoError(t, err)
@ -777,24 +768,6 @@ func TestWebHooks(t *testing.T) {
})
})
t.Run("must error when config is erroneous", func(t *testing.T) {
t.Parallel()
req := &http.Request{
Header: map[string][]string{"Some-Header": {"Some-Value"}},
Host: "www.ory.sh",
TLS: new(tls.ConnectionState),
URL: &url.URL{Path: "/some_end_point"},
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage("not valid json")
wh := hook.NewWebHook(&whDeps, conf)
err := wh.ExecuteLoginPreHook(nil, req, f)
assert.Error(t, err)
})
t.Run("cannot have parse and ignore both set", func(t *testing.T) {
t.Parallel()
ts := newServer(webHookHttpCodeEndPoint(200))
@ -807,8 +780,15 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "GET", "body": "./stub/test_body.jsonnet", "response": {"ignore": true, "parse": true}}`, ts.URL+path))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "GET",
URL: ts.URL + path,
TemplateURI: "./stub/test_body.jsonnet",
Response: request.ResponseConfig{
Ignore: true,
Parse: true,
},
})
err := wh.ExecuteLoginPreHook(nil, req, f)
assert.Error(t, err)
@ -821,7 +801,6 @@ func TestWebHooks(t *testing.T) {
{uc: "Post Settings Hook - parse true", parse: true},
{uc: "Post Settings Hook - parse false", parse: false},
} {
tc := tc
t.Run("uc="+tc.uc, func(t *testing.T) {
t.Parallel()
ts := newServer(webHookHttpCodeWithBodyEndPoint(t, 200, []byte(`{"identity":{"traits":{"email":"some@other-example.org"}}}`)))
@ -834,8 +813,14 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &settings.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "POST", "body": "%s", "response": {"parse":%t}}`, ts.URL+path, "file://./stub/test_body.jsonnet", tc.parse))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "POST",
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
Response: request.ResponseConfig{
Parse: tc.parse,
},
})
uuid := x.NewUUID()
in := &identity.Identity{ID: uuid}
s := &session.Session{ID: x.NewUUID(), Identity: in}
@ -865,12 +850,11 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{
"url": "%s",
"method": "%s",
"body": "%s"
}`, ts.URL+path, "POST", "file://./stub/bad_template.jsonnet"))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "POST",
URL: ts.URL + path,
TemplateURI: "file://./stub/bad_template.jsonnet",
})
err := wh.ExecuteLoginPreHook(nil, req, f)
assert.Error(t, err)
@ -886,8 +870,14 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "GET", "body": "file://./stub/bad_template.jsonnet", "response": {"ignore": true}}`, ts.URL+path))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "GET",
URL: ts.URL + path,
TemplateURI: "file://./stub/bad_template.jsonnet",
Response: request.ResponseConfig{
Ignore: true,
},
})
err := wh.ExecuteLoginPreHook(nil, req, f)
assert.NoError(t, err)
@ -904,12 +894,11 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(`{
"url": "https://i-do-not-exist/",
"method": "POST",
"body": "./stub/cancel_template.jsonnet"
}`)
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "POST",
URL: "https://i-do-not-exist/",
TemplateURI: "file://./stub/cancel_template.jsonnet",
})
err := wh.ExecuteLoginPreHook(nil, req, f)
assert.NoError(t, err)
@ -942,8 +931,14 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "GET", "body": "./stub/test_body.jsonnet", "response": {"ignore": true}}`, ts.URL+path))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "GET",
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
Response: request.ResponseConfig{
Ignore: true,
},
})
start := time.Now()
err := wh.ExecuteLoginPreHook(nil, req, f)
@ -975,8 +970,11 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{"url": "%s", "method": "GET", "body": "./stub/test_body.jsonnet"}`, ts.URL+path))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "GET",
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
})
err := wh.ExecuteLoginPreHook(nil, req, f)
require.Error(t, err)
@ -1010,12 +1008,11 @@ func TestWebHooks(t *testing.T) {
Method: http.MethodPost,
}
f := &login.Flow{ID: x.NewUUID()}
conf := json.RawMessage(fmt.Sprintf(`{
"url": "%s",
"method": "%s",
"body": "%s"
}`, ts.URL+path, "POST", "file://./stub/test_body.jsonnet"))
wh := hook.NewWebHook(&whDeps, conf)
wh := hook.NewWebHook(&whDeps, &request.Config{
Method: "POST",
URL: ts.URL + path,
TemplateURI: "file://./stub/test_body.jsonnet",
})
err := wh.ExecuteLoginPreHook(nil, req, f)
if tc.mustSuccess {
@ -1056,11 +1053,11 @@ func TestDisallowPrivateIPRanges(t *testing.T) {
t.Run("not allowed to call url", func(t *testing.T) {
t.Parallel()
wh := hook.NewWebHook(&whDeps, json.RawMessage(`{
"url": "https://localhost:1234/",
"method": "GET",
"body": "file://stub/test_body.jsonnet"
}`))
wh := hook.NewWebHook(&whDeps, &request.Config{
URL: "https://localhost:1234/",
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
})
err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s)
require.Error(t, err)
require.Contains(t, err.Error(), "is not a permitted destination")
@ -1068,11 +1065,11 @@ func TestDisallowPrivateIPRanges(t *testing.T) {
t.Run("allowed to call exempt url", func(t *testing.T) {
t.Parallel()
wh := hook.NewWebHook(&whDeps, json.RawMessage(`{
"url": "http://localhost/exception",
"method": "GET",
"body": "file://stub/test_body.jsonnet"
}`))
wh := hook.NewWebHook(&whDeps, &request.Config{
URL: "http://localhost/exception",
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
})
err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s)
require.Error(t, err, "the target does not exist and we still receive an error")
require.NotContains(t, err.Error(), "is not a permitted destination", "but the error is not related to the IP range.")
@ -1089,11 +1086,11 @@ func TestDisallowPrivateIPRanges(t *testing.T) {
}
s := &session.Session{ID: x.NewUUID(), Identity: &identity.Identity{ID: x.NewUUID()}}
f := &login.Flow{ID: x.NewUUID()}
wh := hook.NewWebHook(&whDeps, json.RawMessage(`{
"url": "https://www.google.com/",
"method": "GET",
"body": "http://192.168.178.0/test_body.jsonnet"
}`))
wh := hook.NewWebHook(&whDeps, &request.Config{
URL: "https://www.google.com/",
Method: "GET",
TemplateURI: "http://192.168.178.0/test_body.jsonnet",
})
err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s)
require.Error(t, err)
require.Contains(t, err.Error(), "is not a permitted destination")
@ -1144,15 +1141,14 @@ func TestAsyncWebhook(t *testing.T) {
}))
t.Cleanup(webhookReceiver.Close)
wh := hook.NewWebHook(&whDeps, json.RawMessage(fmt.Sprintf(`
{
"url": %q,
"method": "GET",
"body": "file://stub/test_body.jsonnet",
"response": {
"ignore": true
}
}`, webhookReceiver.URL)))
wh := hook.NewWebHook(&whDeps, &request.Config{
URL: webhookReceiver.URL,
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
Response: request.ResponseConfig{
Ignore: true,
},
})
err := wh.ExecuteLoginPostHook(nil, req, node.DefaultGroup, f, s)
require.NoError(t, err) // execution returns immediately for async webhook
select {
@ -1234,17 +1230,12 @@ func TestWebhookEvents(t *testing.T) {
t.Run("success", func(t *testing.T) {
whID := x.NewUUID()
wh := hook.NewWebHook(&whDeps, json.RawMessage(fmt.Sprintf(`
{
"id": %q,
"url": %q,
"method": "GET",
"body": "file://stub/test_body.jsonnet",
"response": {
"ignore": false,
"parse": false
}
}`, whID, webhookReceiver.URL+"/ok")))
wh := hook.NewWebHook(&whDeps, &request.Config{
ID: whID.String(),
URL: webhookReceiver.URL + "/ok",
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
})
recorder := tracetest.NewSpanRecorder()
tracer := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)).Tracer("test")
@ -1286,17 +1277,12 @@ func TestWebhookEvents(t *testing.T) {
t.Run("failed", func(t *testing.T) {
whID := x.NewUUID()
wh := hook.NewWebHook(&whDeps, json.RawMessage(fmt.Sprintf(`
{
"id": %q,
"url": %q,
"method": "GET",
"body": "file://stub/test_body.jsonnet",
"response": {
"ignore": false,
"parse": false
}
}`, whID, webhookReceiver.URL+"/fail")))
wh := hook.NewWebHook(&whDeps, &request.Config{
ID: whID.String(),
URL: webhookReceiver.URL + "/fail",
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
})
recorder := tracetest.NewSpanRecorder()
tracer := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)).Tracer("test")
@ -1347,17 +1333,12 @@ func TestWebhookEvents(t *testing.T) {
})
t.Run("event disabled", func(t *testing.T) {
wh := hook.NewWebHook(&whDeps, json.RawMessage(fmt.Sprintf(`
{
"url": %q,
"method": "GET",
"body": "file://stub/test_body.jsonnet",
"response": {
"ignore": false,
"parse": false
},
"emit_analytics_event": false
}`, webhookReceiver.URL+"/fail")))
wh := hook.NewWebHook(&whDeps, &request.Config{
URL: webhookReceiver.URL + "/fail",
Method: "GET",
TemplateURI: "file://stub/test_body.jsonnet",
EmitAnalyticsEvent: pointerx.Ptr(false),
})
recorder := tracetest.NewSpanRecorder()
tracer := sdktrace.NewTracerProvider(sdktrace.WithSpanProcessor(recorder)).Tracer("test")

View File

@ -10,6 +10,8 @@ import (
"sort"
"strings"
"github.com/ory/kratos/x/nosurfx"
"github.com/samber/lo"
"github.com/pkg/errors"
@ -64,8 +66,8 @@ type (
}
strategyDependencies interface {
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.LoggingProvider
x.TracingProvider

View File

@ -9,6 +9,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/ory/x/pointerx"
"github.com/gofrs/uuid"
@ -225,7 +227,7 @@ func (s *Strategy) recoveryIssueSession(w http.ResponseWriter, r *http.Request,
if returnToURL != nil {
returnTo = returnToURL.String()
}
sf.RequestURL, err = x.TakeOverReturnToParameter(f.RequestURL, sf.RequestURL, returnTo)
sf.RequestURL, err = redir.TakeOverReturnToParameter(f.RequestURL, sf.RequestURL, returnTo)
if err != nil {
return s.retryRecoveryFlow(w, r, f.Type, RetryWithError(err))
}

View File

@ -9,6 +9,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
@ -35,7 +37,7 @@ const (
func (s *Strategy) RegisterPublicRecoveryRoutes(public *x.RouterPublic) {
s.deps.CSRFHandler().IgnorePath(RouteAdminCreateRecoveryCode)
public.POST(RouteAdminCreateRecoveryCode, x.RedirectToAdminRoute(s.deps))
public.POST(RouteAdminCreateRecoveryCode, redir.RedirectToAdminRoute(s.deps))
}
func (s *Strategy) RegisterAdminRecoveryRoutes(admin *x.RouterAdmin) {

View File

@ -17,6 +17,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"github.com/ory/x/urlx"
@ -383,14 +385,14 @@ func TestVerification(t *testing.T) {
require.NoError(t, err)
body := string(ioutilx.MustReadAll(res.Body))
require.Len(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL)), 1)
assert.Contains(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL))[0].Name, x.CSRFTokenName)
assert.Contains(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL))[0].Name, nosurfx.CSRFTokenName)
actualBody, _ := submitVerificationCode(t, body, cl, code)
assert.EqualValues(t, "passed_challenge", gjson.Get(actualBody, "state").String())
})
newValidFlow := func(t *testing.T, fType flow.Type, requestURL string) (*verification.Flow, *code.VerificationCode, string) {
f, err := verification.NewFlow(conf, time.Hour, x.FakeCSRFToken, httptest.NewRequest("GET", requestURL, nil), code.NewStrategy(reg), fType)
f, err := verification.NewFlow(conf, time.Hour, nosurfx.FakeCSRFToken, httptest.NewRequest("GET", requestURL, nil), code.NewStrategy(reg), fType)
require.NoError(t, err)
f.State = flow.StateEmailSent
u, err := url.Parse(f.RequestURL)
@ -429,7 +431,7 @@ func TestVerification(t *testing.T) {
res, err := client.PostForm(action, url.Values{
"code": {rawCode},
"csrf_token": {x.FakeCSRFToken},
"csrf_token": {nosurfx.FakeCSRFToken},
})
require.NoError(t, err)
body := ioutilx.MustReadAll(res.Body)

View File

@ -14,14 +14,15 @@ import (
"github.com/ory/kratos/session"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/decoderx"
)
type dependencies interface {
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.TracingProvider
config.Provider

View File

@ -15,6 +15,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/strategy/oidc"
"github.com/ory/kratos/selfservice/strategy/idfirst"
@ -180,7 +182,7 @@ func TestCompleteLogin(t *testing.T) {
})
values := url.Values{
"csrf_token": {x.FakeCSRFToken},
"csrf_token": {nosurfx.FakeCSRFToken},
"identifier": {"identifier"},
"method": {"identifier_first"},
}
@ -236,7 +238,7 @@ func TestCompleteLogin(t *testing.T) {
actual, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusOK, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(actual), "%s", actual)
})
@ -246,7 +248,7 @@ func TestCompleteLogin(t *testing.T) {
actual, res := testhelpers.LoginMakeRequest(t, false, true, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusForbidden, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
})

View File

@ -16,6 +16,7 @@ import (
"github.com/ory/kratos/ui/container"
"github.com/ory/kratos/ui/node"
"github.com/ory/kratos/x"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/decoderx"
)
@ -38,8 +39,8 @@ type (
}
strategyDependencies interface {
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.LoggingProvider
x.TracingProvider

View File

@ -10,6 +10,8 @@ import (
"net/url"
"time"
"github.com/ory/kratos/x/redir"
"github.com/gobuffalo/pop/v6"
"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
@ -46,7 +48,7 @@ func (s *Strategy) RecoveryStrategyID() string {
func (s *Strategy) RegisterPublicRecoveryRoutes(public *x.RouterPublic) {
s.d.CSRFHandler().IgnorePath(RouteAdminCreateRecoveryLink)
public.POST(RouteAdminCreateRecoveryLink, x.RedirectToAdminRoute(s.d))
public.POST(RouteAdminCreateRecoveryLink, redir.RedirectToAdminRoute(s.d))
}
func (s *Strategy) RegisterAdminRecoveryRoutes(admin *x.RouterAdmin) {
@ -347,7 +349,7 @@ func (s *Strategy) recoveryIssueSession(ctx context.Context, w http.ResponseWrit
returnTo = returnToURL.String()
}
sf.RequestURL, err = x.TakeOverReturnToParameter(f.RequestURL, sf.RequestURL, returnTo)
sf.RequestURL, err = redir.TakeOverReturnToParameter(f.RequestURL, sf.RequestURL, returnTo)
if err != nil {
return s.retryRecoveryFlowWithError(w, r, flow.TypeBrowser, err)
}

View File

@ -15,6 +15,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
confighelpers "github.com/ory/kratos/driver/config/testhelpers"
"github.com/davecgh/go-spew/spew"
@ -649,7 +651,7 @@ func TestRecovery(t *testing.T) {
assert.Equal(t, http.StatusSeeOther, res.StatusCode)
require.Len(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL)), 2)
cookies := spew.Sdump(cl.Jar.Cookies(urlx.ParseOrPanic(public.URL)))
assert.Contains(t, cookies, x.CSRFTokenName)
assert.Contains(t, cookies, nosurfx.CSRFTokenName)
assert.Contains(t, cookies, "ory_kratos_session")
returnTo, err := res.Location()
require.NoError(t, err)

View File

@ -15,6 +15,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/urlx"
"github.com/ory/kratos/selfservice/strategy/link"
@ -346,7 +348,7 @@ func TestVerification(t *testing.T) {
body := string(ioutilx.MustReadAll(res.Body))
require.NoError(t, res.Body.Close())
require.Len(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL)), 1)
assert.Contains(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL))[0].Name, x.CSRFTokenName)
assert.Contains(t, cl.Jar.Cookies(urlx.ParseOrPanic(public.URL))[0].Name, nosurfx.CSRFTokenName)
actualRes, err := cl.Get(public.URL + verification.RouteGetFlow + "?id=" + gjson.Get(body, "id").String())
require.NoError(t, err)
@ -366,7 +368,7 @@ func TestVerification(t *testing.T) {
})
newValidFlow := func(t *testing.T, fType flow.Type, requestURL string) (*verification.Flow, *link.VerificationToken) {
f, err := verification.NewFlow(conf, time.Hour, x.FakeCSRFToken, httptest.NewRequest("GET", requestURL, nil), nil, fType)
f, err := verification.NewFlow(conf, time.Hour, nosurfx.FakeCSRFToken, httptest.NewRequest("GET", requestURL, nil), nil, fType)
require.NoError(t, err)
f.State = flow.StateEmailSent
require.NoError(t, reg.VerificationFlowPersister().CreateVerificationFlow(context.Background(), f))

View File

@ -14,6 +14,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow"
"github.com/gofrs/uuid"
@ -311,7 +313,7 @@ func TestCompleteLogin(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
})
t.Run("type=spa", func(t *testing.T) {
@ -321,7 +323,7 @@ func TestCompleteLogin(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), publicTS.URL+login.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
})
})
}

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/sqlcon"
"github.com/gofrs/uuid"
@ -191,7 +193,7 @@ func TestCompleteSettings(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
})
t.Run("type=spa", func(t *testing.T) {
@ -201,7 +203,7 @@ func TestCompleteSettings(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
})
})

View File

@ -7,6 +7,8 @@ import (
"context"
"encoding/json"
"github.com/ory/kratos/x/nosurfx"
"github.com/pkg/errors"
"github.com/ory/kratos/continuity"
@ -32,8 +34,8 @@ var (
type lookupStrategyDependencies interface {
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.TransactionPersistenceProvider
x.TracingProvider

View File

@ -16,6 +16,9 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/x/redir"
"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"
@ -69,8 +72,8 @@ type Dependencies interface {
x.LoggingProvider
x.CookieProvider
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.HTTPClientProvider
x.TracingProvider
@ -416,7 +419,7 @@ func (s *Strategy) alreadyAuthenticated(ctx context.Context, w http.ResponseWrit
} else if !isForced(f) {
returnTo := s.d.Config().SelfServiceBrowserDefaultReturnTo(ctx)
if redirecter, ok := f.(flow.FlowWithRedirect); ok {
r, err := x.SecureRedirectTo(r, returnTo, redirecter.SecureRedirectToOpts(ctx, s.d)...)
r, err := redir.SecureRedirectTo(r, returnTo, redirecter.SecureRedirectToOpts(ctx, s.d)...)
if err == nil {
returnTo = r
}
@ -662,7 +665,7 @@ func (s *Strategy) HandleError(ctx context.Context, w http.ResponseWriter, r *ht
if lf.Type == flow.TypeAPI {
returnTo := s.d.Config().SelfServiceBrowserDefaultReturnTo(ctx)
if redirecter, ok := f.(flow.FlowWithRedirect); ok {
secureReturnTo, err := x.SecureRedirectTo(r, returnTo, redirecter.SecureRedirectToOpts(ctx, s.d)...)
secureReturnTo, err := redir.SecureRedirectTo(r, returnTo, redirecter.SecureRedirectToOpts(ctx, s.d)...)
if err == nil {
returnTo = secureReturnTo
}

View File

@ -14,6 +14,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/snapshotx"
"github.com/ory/kratos/driver"
@ -278,7 +280,7 @@ func TestSettingsStrategy(t *testing.T) {
unlink := func(t *testing.T, agent, provider string) (body []byte, res *http.Response, req *kratos.SettingsFlow) {
req = nprSDK(t, agents[agent], "", time.Hour)
body, res = testhelpers.HTTPPostForm(t, agents[agent], action(req),
&url.Values{"csrf_token": {x.FakeCSRFToken}, "unlink": {provider}})
&url.Values{"csrf_token": {nosurfx.FakeCSRFToken}, "unlink": {provider}})
return
}
@ -364,7 +366,7 @@ func TestSettingsStrategy(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, time.Minute*5)
body, res := testhelpers.HTTPPostForm(t, agents[agent], action(req),
&url.Values{"csrf_token": {x.FakeCSRFToken}, "unlink": {provider}})
&url.Values{"csrf_token": {nosurfx.FakeCSRFToken}, "unlink": {provider}})
assert.Contains(t, res.Request.URL.String(), uiTS.URL+"/settings?flow="+req.Id)
assert.Equal(t, "success", gjson.GetBytes(body, "state").String())
@ -378,7 +380,7 @@ func TestSettingsStrategy(t *testing.T) {
link := func(t *testing.T, agent, provider string) (body []byte, res *http.Response, req *kratos.SettingsFlow) {
req = nprSDK(t, agents[agent], "", time.Hour)
body, res = testhelpers.HTTPPostForm(t, agents[agent], action(req),
&url.Values{"csrf_token": {x.FakeCSRFToken}, "link": {provider}})
&url.Values{"csrf_token": {nosurfx.FakeCSRFToken}, "link": {provider}})
return
}
@ -516,7 +518,7 @@ func TestSettingsStrategy(t *testing.T) {
}
values := &url.Values{}
values.Set("csrf_token", x.FakeCSRFToken)
values.Set("csrf_token", nosurfx.FakeCSRFToken)
values.Set("link", provider)
values.Set("upstream_parameters.login_hint", "foo@bar.com")
values.Set("upstream_parameters.hd", "bar.com")
@ -545,7 +547,7 @@ func TestSettingsStrategy(t *testing.T) {
}
values := &url.Values{}
values.Set("csrf_token", x.FakeCSRFToken)
values.Set("csrf_token", nosurfx.FakeCSRFToken)
values.Set("link", provider)
values.Set("upstream_parameters.lol", "invalid")
@ -601,7 +603,7 @@ func TestSettingsStrategy(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceSettingsPrivilegedAuthenticationAfter, time.Minute*5)
body, res := testhelpers.HTTPPostForm(t, agents[agent], action(req),
&url.Values{"csrf_token": {x.FakeCSRFToken}, "unlink": {provider}})
&url.Values{"csrf_token": {nosurfx.FakeCSRFToken}, "unlink": {provider}})
assert.Contains(t, res.Request.URL.String(), uiTS.URL+"/settings?flow="+req.Id)
assert.Equal(t, "success", gjson.GetBytes(body, "state").String())
@ -678,7 +680,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{
c: []oidc.Configuration{},
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
},
},
{
@ -686,14 +688,14 @@ func TestPopulateSettingsMethod(t *testing.T) {
{Provider: "generic", ID: "github"},
},
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("github", "github"),
},
},
{
c: defaultConfig,
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("facebook", "facebook"),
oidc.NewLinkNode("google", "google"),
oidc.NewLinkNode("github", "github"),
@ -702,7 +704,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{
c: defaultConfig,
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("facebook", "facebook"),
oidc.NewLinkNode("google", "google"),
oidc.NewLinkNode("github", "github"),
@ -712,7 +714,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{
c: defaultConfig,
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("facebook", "facebook"),
oidc.NewLinkNode("github", "github"),
},
@ -723,7 +725,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{
c: defaultConfig,
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("facebook", "facebook"),
oidc.NewLinkNode("github", "github"),
oidc.NewUnlinkNode("google", "google"),
@ -739,7 +741,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{
c: defaultConfig,
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("github", "github"),
oidc.NewUnlinkNode("google", "google"),
oidc.NewUnlinkNode("facebook", "facebook"),
@ -757,7 +759,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{Provider: "generic", ID: "labeled", Label: "Labeled"},
},
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewLinkNode("labeled", "Labeled"),
},
},
@ -767,7 +769,7 @@ func TestPopulateSettingsMethod(t *testing.T) {
{Provider: "generic", ID: "facebook"},
},
e: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
oidc.NewUnlinkNode("labeled", "Labeled"),
oidc.NewUnlinkNode("facebook", "facebook"),
},

View File

@ -12,6 +12,8 @@ import (
"net/url"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/strategy/passkey"
@ -142,10 +144,10 @@ func TestCompleteSettings(t *testing.T) {
}, id)
if spa {
assert.Contains(t, res.Request.URL.String(), fix.publicTS.URL+settings.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
} else {
assert.Contains(t, res.Request.URL.String(), fix.errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
}
}

View File

@ -8,6 +8,8 @@ import (
"encoding/json"
"strings"
"github.com/ory/kratos/x/nosurfx"
"github.com/pkg/errors"
"github.com/ory/kratos/continuity"
@ -27,8 +29,8 @@ import (
type strategyDependencies interface {
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.TracingProvider
config.Provider

View File

@ -96,8 +96,12 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow,
return nil, errors.WithStack(herodot.ErrInternalServerError.WithReasonf("Password migration hook is not enabled but password migration is requested."))
}
migrationHook := hook.NewPasswordMigrationHook(s.d, pwHook.Config)
err = migrationHook.Execute(ctx, &hook.PasswordMigrationRequest{Identifier: identifier, Password: p.Password})
migrationHook := hook.NewPasswordMigrationHook(s.d, &pwHook.Config)
err = migrationHook.Execute(ctx, r, f, &hook.PasswordMigrationRequest{
Identifier: identifier,
Password: p.Password,
Identity: i,
})
if err != nil {
return nil, s.handleLoginError(r, f, p, err)
}

View File

@ -20,6 +20,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/strategy/idfirst"
configtesthelpers "github.com/ory/kratos/driver/config/testhelpers"
@ -205,7 +207,7 @@ func TestCompleteLogin(t *testing.T) {
conf.MustSet(ctx, config.ViperKeySelfServiceLoginRequestLifespan, "10m")
})
values := url.Values{
"csrf_token": {x.FakeCSRFToken},
"csrf_token": {nosurfx.FakeCSRFToken},
"identifier": {"identifier"},
"password": {"password"},
}
@ -257,7 +259,7 @@ func TestCompleteLogin(t *testing.T) {
actual, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusOK, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(actual), "%s", actual)
})
@ -267,7 +269,7 @@ func TestCompleteLogin(t *testing.T) {
actual, res := testhelpers.LoginMakeRequest(t, false, true, f, browserClient, values.Encode())
assert.EqualValues(t, http.StatusForbidden, res.StatusCode)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken,
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken,
json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
})
@ -727,7 +729,7 @@ func TestCompleteLogin(t *testing.T) {
values := url.Values{
"method": {"password"}, "identifier": {identifier},
"password": {pwd}, "csrf_token": {x.FakeCSRFToken},
"password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken},
}.Encode()
body1, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)
@ -748,7 +750,7 @@ func TestCompleteLogin(t *testing.T) {
browserClient := testhelpers.NewClientWithCookies(t)
f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, false, false, false)
values := url.Values{"method": {"password"}, "identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)
@ -762,7 +764,7 @@ func TestCompleteLogin(t *testing.T) {
browserClient := testhelpers.NewClientWithCookies(t)
f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, true, false, false)
values := url.Values{"method": {"password"}, "identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, true, f, browserClient, values)
assert.EqualValues(t, http.StatusOK, res.StatusCode)
@ -789,7 +791,7 @@ func TestCompleteLogin(t *testing.T) {
browserClient := testhelpers.NewClientWithCookies(t)
f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, false, false, false)
values := url.Values{"method": {"password"}, "password_identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "password_identifier": {strings.ToUpper(identifier)}, "password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)
@ -804,7 +806,7 @@ func TestCompleteLogin(t *testing.T) {
browserClient := testhelpers.NewClientWithCookies(t)
f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, publicTS, false, false, false, false)
values := url.Values{"method": {"password"}, "identifier": {" " + identifier + " "}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "identifier": {" " + identifier + " "}, "password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)
@ -1165,6 +1167,70 @@ func TestCompleteLogin(t *testing.T) {
}
})
}
t.Run("case=custom hook payload", func(t *testing.T) {
var rawBody []byte
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var err error
rawBody, err = io.ReadAll(r.Body)
require.NoError(t, err)
_ = r.Body.Close()
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"password_match"}`))
}))
t.Cleanup(ts.Close)
require.NoError(t, reg.Config().Set(ctx, config.ViperKeyPasswordMigrationHook, map[string]any{
"config": map[string]any{
"url": ts.URL,
"body": "base64://" + base64.StdEncoding.EncodeToString([]byte(`function(ctx) ctx`)),
},
}))
identifier := x.NewUUID().String() + "@google.com"
identityID := x.NewUUID()
values := func(v url.Values) {
v.Set("identifier", identifier)
v.Set("method", identity.CredentialsTypePassword.String())
v.Set("password", x.NewUUID().String())
}
require.NoError(t, reg.PrivilegedIdentityPool().CreateIdentity(ctx, &identity.Identity{
ID: identityID,
SchemaID: "migration",
Traits: identity.Traits(fmt.Sprintf(`{"email":"%s"}`, identifier)),
Credentials: map[identity.CredentialsType]identity.Credentials{
identity.CredentialsTypePassword: {
Type: identity.CredentialsTypePassword,
Identifiers: []string{identifier},
Config: sqlxx.JSONRawMessage(`{"use_password_migration_hook": true}`),
},
},
VerifiableAddresses: []identity.VerifiableAddress{
{
ID: x.NewUUID(),
Value: identifier,
Verified: true,
IdentityID: identityID,
},
},
}))
browserClient := testhelpers.NewClientWithCookies(t)
body := testhelpers.SubmitLoginForm(t, false, browserClient, publicTS, values,
false, false, http.StatusOK, redirTS.URL)
assert.Equalf(t, identifier, gjson.Get(body, "identity.traits.email").String(), "%s", body)
for _, path := range []string{
"identifier", "password",
"identity", "identity.traits",
"flow", "flow.id",
"request_headers", "request_cookies", "request_method", "request_url",
} {
assert.Truef(t, gjson.GetBytes(rawBody, path).Exists(), "%s does not exist in %s", path, rawBody)
}
})
})
}

View File

@ -12,20 +12,16 @@ import (
"testing"
"time"
"golang.org/x/oauth2"
"github.com/gofrs/uuid"
"github.com/pkg/errors"
"github.com/phayes/freeport"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/oauth2"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest/v3/docker"
hydraclientgo "github.com/ory/hydra-client-go/v2"
"github.com/ory/x/logrusx"
"github.com/ory/x/resilience"
"github.com/ory/x/urlx"
"github.com/phayes/freeport"
)
type clientAppConfig struct {
@ -171,23 +167,15 @@ func newHydra(t *testing.T, loginUI string, consentUI string) (hydraAdmin string
Follow: true,
Container: hydraResource.Container.ID,
})
hl := logrusx.New("hydra-ready-check", "hydra-ready-check")
err = resilience.Retry(hl, time.Second*1, time.Second*5, func() error {
pr := hydraPublic + "/health/ready"
res, err := http.DefaultClient.Get(pr)
if err != nil || res.StatusCode != 200 {
return errors.Errorf("Hydra public is not ready at %s", pr)
}
require.EventuallyWithT(t, func(t *assert.CollectT) {
res, err := http.DefaultClient.Get(hydraPublic + "/health/ready")
require.NoError(t, err)
assert.Equal(t, 200, res.StatusCode)
ar := hydraAdmin + "/health/ready"
res, err = http.DefaultClient.Get(ar)
if err != nil && res.StatusCode != 200 {
return errors.Errorf("Hydra admin is not ready at %s", ar)
} else {
return nil
}
})
require.NoError(t, err)
res, err = http.DefaultClient.Get(hydraAdmin + "/health/ready")
require.NoError(t, err)
assert.Equal(t, 200, res.StatusCode)
}, 5*time.Second, time.Second)
t.Logf("Ory Hydra running at: %s %s", hydraPublic, hydraAdmin)

View File

@ -13,6 +13,8 @@ import (
"sync/atomic"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/julienschmidt/httprouter"
"github.com/tidwall/gjson"
"github.com/urfave/negroni"
@ -100,7 +102,7 @@ func TestOAuth2Provider(t *testing.T) {
lf := testhelpers.GetLoginFlow(t, c.browserClient, c.kratosPublicTS, flowID)
require.NotNil(t, lf)
values := url.Values{"method": {"password"}, "identifier": {c.identifier}, "password": {c.password}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "identifier": {c.identifier}, "password": {c.password}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
_, res := testhelpers.LoginMakeRequest(t, false, false, lf, c.browserClient, values)
assert.EqualValues(t, http.StatusOK, res.StatusCode)
return
@ -211,7 +213,7 @@ func TestOAuth2Provider(t *testing.T) {
loginToAccount := func(t *testing.T, browserClient *http.Client, identifier, pwd string) {
f := testhelpers.InitializeLoginFlowViaBrowser(t, browserClient, kratosPublicTS, false, false, false, false)
values := url.Values{"method": {"password"}, "identifier": {identifier}, "password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
values := url.Values{"method": {"password"}, "identifier": {identifier}, "password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)

View File

@ -14,6 +14,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/stretchr/testify/require"
"github.com/ory/x/snapshotx"
@ -676,7 +678,7 @@ func TestRegistration(t *testing.T) {
Action: conf.SelfPublicURL(ctx).String() + registration.RouteSubmitFlow + "?flow=" + f.Id,
Method: "POST",
Nodes: node.Nodes{
node.NewCSRFNode(x.FakeCSRFToken),
node.NewCSRFNode(nosurfx.FakeCSRFToken),
node.NewInputField("traits.username", nil, node.DefaultGroup, node.InputAttributeTypeText),
node.NewInputField("password", nil, node.PasswordGroup, node.InputAttributeTypePassword, node.WithRequiredInputAttribute, node.WithInputAttributes(func(a *node.InputAttributes) {
a.Autocomplete = node.InputAttributeAutocompleteNewPassword

View File

@ -13,6 +13,8 @@ import (
"strings"
"testing"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/internal/settingshelpers"
@ -275,7 +277,7 @@ func TestSettings(t *testing.T) {
assert.Equal(t, http.StatusOK, res.StatusCode)
assert.Contains(t, res.Request.URL.String(), conf.GetProvider(ctx).String(config.ViperKeySelfServiceErrorUI))
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken, json.RawMessage(actual), "%s", actual)
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken, json.RawMessage(actual), "%s", actual)
})
t.Run("case=should pass even without CSRF token/type=spa", func(t *testing.T) {
@ -288,7 +290,7 @@ func TestSettings(t *testing.T) {
assert.Equal(t, http.StatusForbidden, res.StatusCode)
assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken, json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken, json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
})
t.Run("case=should pass even without CSRF token/type=api", func(t *testing.T) {

View File

@ -8,6 +8,8 @@ import (
"encoding/json"
"strings"
"github.com/ory/kratos/x/nosurfx"
"github.com/go-playground/validator/v10"
"github.com/pkg/errors"
@ -37,8 +39,8 @@ var (
type registrationStrategyDependencies interface {
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.HTTPClientProvider
x.TracingProvider
jsonnetsecure.VMProvider

View File

@ -9,6 +9,8 @@ import (
"net/http"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/x/otelx"
"github.com/ory/jsonschema/v3"
@ -38,8 +40,8 @@ var _ settings.Strategy = new(Strategy)
type (
strategyDependencies interface {
x.CSRFProvider
x.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
x.WriterProvider
x.LoggingProvider
x.TracingProvider

View File

@ -17,6 +17,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow/registration"
"github.com/ory/kratos/selfservice/strategy/code"
"github.com/ory/kratos/selfservice/strategy/oidc"
@ -139,7 +141,7 @@ func TestStrategyTraits(t *testing.T) {
actual, res := testhelpers.SettingsMakeRequest(t, false, false, f, browserUser1,
url.Values{"traits.booly": {"true"}, "csrf_token": {"invalid"}, "method": {"profile"}}.Encode())
assert.EqualValues(t, http.StatusOK, res.StatusCode, "should return a 400 error because CSRF token is not set\n\t%s", actual)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken, json.RawMessage(actual), "%s", actual)
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken, json.RawMessage(actual), "%s", actual)
})
t.Run("description=should fail to post data if CSRF is invalid/type=spa", func(t *testing.T) {
@ -150,7 +152,7 @@ func TestStrategyTraits(t *testing.T) {
actual, res := testhelpers.SettingsMakeRequest(t, false, true, f, browserUser1,
testhelpers.EncodeFormAsJSON(t, true, url.Values{"traits.booly": {"true"}, "csrf_token": {"invalid"}, "method": {"profile"}}))
assert.EqualValues(t, http.StatusForbidden, res.StatusCode, "should return a 400 error because CSRF token is not set\n\t%s", actual)
assertx.EqualAsJSON(t, x.ErrInvalidCSRFToken, json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
assertx.EqualAsJSON(t, nosurfx.ErrInvalidCSRFToken, json.RawMessage(gjson.Get(actual, "error").Raw), "%s", actual)
})
t.Run("description=should not fail because of CSRF token but because of unprivileged/type=api", func(t *testing.T) {
@ -158,7 +160,7 @@ func TestStrategyTraits(t *testing.T) {
f := testhelpers.InitializeSettingsFlowViaAPI(t, apiUser1, publicTS)
actual, res := testhelpers.SettingsMakeRequest(t, true, false, f, apiUser1, `{"traits.booly":true,"method":"profile","csrf_token":"`+x.FakeCSRFToken+`"}`)
actual, res := testhelpers.SettingsMakeRequest(t, true, false, f, apiUser1, `{"traits.booly":true,"method":"profile","csrf_token":"`+nosurfx.FakeCSRFToken+`"}`)
require.Len(t, res.Cookies(), 1)
assert.Equal(t, "ory_kratos_continuity", res.Cookies()[0].Name)
assert.EqualValues(t, http.StatusForbidden, res.StatusCode)

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/x/assertx"
@ -410,7 +412,7 @@ func TestCompleteLogin(t *testing.T) {
}, id, "")
assert.Contains(t, res.Request.URL.String(), errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
})
t.Run("type=spa", func(t *testing.T) {
@ -420,7 +422,7 @@ func TestCompleteLogin(t *testing.T) {
}, id, "")
assert.Contains(t, res.Request.URL.String(), publicTS.URL+login.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
})
})
@ -435,7 +437,7 @@ func TestCompleteLogin(t *testing.T) {
cred, ok := id.GetCredentials(identity.CredentialsTypePassword)
require.True(t, ok)
values := url.Values{"method": {"password"}, "password_identifier": {cred.Identifiers[0]},
"password": {pwd}, "csrf_token": {x.FakeCSRFToken}}.Encode()
"password": {pwd}, "csrf_token": {nosurfx.FakeCSRFToken}}.Encode()
body, res := testhelpers.LoginMakeRequest(t, false, false, f, browserClient, values)
require.Contains(t, res.Request.URL.Path, "login", "%s", res.Request.URL.String())

View File

@ -12,6 +12,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/gofrs/uuid"
"github.com/ory/kratos/selfservice/flow"
@ -127,7 +129,7 @@ func TestCompleteSettings(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
})
t.Run("type=spa", func(t *testing.T) {
@ -137,7 +139,7 @@ func TestCompleteSettings(t *testing.T) {
}, id)
assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
})
})

View File

@ -7,6 +7,8 @@ import (
"context"
"encoding/json"
"github.com/ory/kratos/x/nosurfx"
"github.com/pkg/errors"
"github.com/pquerna/otp"
@ -33,8 +35,8 @@ var (
type totpStrategyDependencies interface {
x.LoggingProvider
x.WriterProvider
x.CSRFTokenGeneratorProvider
x.CSRFProvider
nosurfx.CSRFTokenGeneratorProvider
nosurfx.CSRFProvider
x.TracingProvider
config.Provider

View File

@ -13,6 +13,8 @@ import (
"testing"
"time"
"github.com/ory/kratos/x/nosurfx"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/x/snapshotx"
@ -240,10 +242,10 @@ func TestCompleteSettings(t *testing.T) {
}, id)
if spa {
assert.Contains(t, res.Request.URL.String(), publicTS.URL+settings.RouteSubmitFlow)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "error.reason").String(), body)
} else {
assert.Contains(t, res.Request.URL.String(), errTS.URL)
assert.Equal(t, x.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
assert.Equal(t, nosurfx.ErrInvalidCSRFToken.Reason(), gjson.Get(body, "reason").String(), body)
}
}

Some files were not shown because too many files have changed in this diff Show More