Skip to content

Commit

Permalink
refactor: profile settings method is now API-able
Browse files Browse the repository at this point in the history
  • Loading branch information
aeneasr committed Aug 26, 2020
1 parent fda17ca commit c5f361f
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 287 deletions.
12 changes: 10 additions & 2 deletions selfservice/flow/settings/error.go
Expand Up @@ -21,7 +21,6 @@ import (

var (
ErrHookAbortRequest = errors.New("aborted settings hook execution")
ErrRequestNeedsReAuthentication = herodot.ErrForbidden.WithReasonf("The login session is too old and thus not allowed to update these fields. Please re-authenticate.")
)

type (
Expand All @@ -45,8 +44,17 @@ type (
*herodot.DefaultError
ago time.Duration
}

FlowNeedsReAuth struct {
*herodot.DefaultError
}
)

func NewFlowNeedsReAuth() *FlowNeedsReAuth {
return &FlowNeedsReAuth{DefaultError: herodot.ErrForbidden.
WithReasonf("The login session is too old and thus not allowed to update these fields. Please re-authenticate.")}
}

func NewFlowExpiredError(at time.Time) *FlowExpiredError {
ago := time.Since(at)
return &FlowExpiredError{
Expand Down Expand Up @@ -127,7 +135,7 @@ func (s *ErrorHandler) WriteFlowError(
return
}

if errors.Is(err, ErrRequestNeedsReAuthentication) {
if e := new(FlowNeedsReAuth); errors.As(err, &e) {
s.reauthenticate(w, r, f, err)
return
}
Expand Down
2 changes: 1 addition & 1 deletion selfservice/flow/settings/error_test.go
Expand Up @@ -229,7 +229,7 @@ func TestHandleError(t *testing.T) {
t.Cleanup(reset)

settingsFlow = &settings.Flow{Type: flow.TypeBrowser}
flowError = settings.ErrRequestNeedsReAuthentication
flowError = settings.NewFlowNeedsReAuth()
flowMethod = settings.StrategyProfile

res, err := ts.Client().Get(ts.URL + "/error")
Expand Down
2 changes: 1 addition & 1 deletion selfservice/flow/settings/handler.go
Expand Up @@ -77,7 +77,7 @@ func (h *Handler) RegisterPublicRoutes(public *x.RouterPublic) {
public.GET(RouteInitBrowserFlow, h.d.SessionHandler().IsAuthenticated(h.initBrowserFlow, redirect))
public.GET(RouteInitAPIFlow, h.d.SessionHandler().IsAuthenticated(h.initApiFlow, nil))

public.GET(RouteGetFlow, h.d.SessionHandler().IsAuthenticated(h.fetchPublicFLow, nil))
public.GET(RouteGetFlow, h.d.SessionHandler().IsAuthenticated(h.fetchPublicFLow, OnUnauthenticated(h.c, h.d)))
}

func (h *Handler) RegisterAdminRoutes(admin *x.RouterAdmin) {
Expand Down
2 changes: 1 addition & 1 deletion selfservice/flow/settings/handler_test.go
Expand Up @@ -140,7 +140,7 @@ func TestHandler(t *testing.T) {
})

t.Run("description=should fail to post data if CSRF is missing", func(t *testing.T) {
f := testhelpers.GetSettingsMethodConfig(t, primaryUser, publicTS, settings.StrategyProfile)
f := testhelpers.GetSettingsFlowMethodConfigDeprecated(t, primaryUser, publicTS, settings.StrategyProfile)
res, err := primaryUser.PostForm(pointerx.StringR(f.Action), url.Values{})
require.NoError(t, err)
assert.EqualValues(t, 400, res.StatusCode, "should return a 400 error because CSRF token is not set")
Expand Down
9 changes: 7 additions & 2 deletions selfservice/flow/settings/hook.go
Expand Up @@ -132,7 +132,7 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request,
if err := e.d.IdentityManager().Update(r.Context(), i, options...); err != nil {
if errors.Is(err, identity.ErrProtectedFieldModified) {
e.d.Logger().WithError(err).Debug("Modifying protected field requires re-authentication.")
return errors.WithStack(ErrRequestNeedsReAuthentication)
return errors.WithStack(NewFlowNeedsReAuth())
}
return err
}
Expand Down Expand Up @@ -189,7 +189,12 @@ func (e *HookExecutor) PostSettingsHook(w http.ResponseWriter, r *http.Request,
Debug("Completed all PostSettingsPrePersistHooks and PostSettingsPostPersistHooks.")

if ctxUpdate.Flow.Type == flow.TypeAPI {
e.d.Writer().Write(w, r, &APIFlowResponse{Flow: ctxUpdate.Flow, Identity: i})
updatedFlow, err := e.d.SettingsFlowPersister().GetSettingsFlow(r.Context(),ctxUpdate.Flow.ID)
if err != nil {
return err
}

e.d.Writer().Write(w, r, &APIFlowResponse{Flow: updatedFlow, Identity: i})
return nil
}

Expand Down
57 changes: 37 additions & 20 deletions selfservice/flow/settings/strategy_helper.go
Expand Up @@ -7,14 +7,17 @@ import (
"time"

"github.com/gofrs/uuid"
"github.com/julienschmidt/httprouter"
"github.com/pkg/errors"

"github.com/ory/x/sqlcon"

"github.com/ory/herodot"

"github.com/ory/kratos/continuity"
"github.com/ory/kratos/driver/configuration"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/session"
"github.com/ory/kratos/x"
)
Expand All @@ -24,16 +27,22 @@ var (
)

type UpdatePayload interface {
GetRequestID() uuid.UUID
SetRequestID(id uuid.UUID)
GetFlowID() uuid.UUID
SetFlowID(id uuid.UUID)
}

type UpdateContext struct {
Valid bool
Session *session.Session
Flow *Flow
}

func (c UpdateContext) GetSessionIdentity() *identity.Identity {
if c.Session == nil {
return nil
}
return c.Session.Identity
}

func PrepareUpdate(d interface {
x.LoggingProvider
continuity.ManagementProvider
Expand All @@ -45,13 +54,12 @@ func PrepareUpdate(d interface {
return new(UpdateContext), err
}

rid, err := GetRequestID(r)
rid, err := GetFlowID(r)
if err != nil {
return new(UpdateContext), err
}

payload.SetRequestID(rid)

payload.SetFlowID(rid)
req, err := d.SettingsFlowPersister().GetSettingsFlow(r.Context(), rid)
if errors.Is(err, sqlcon.ErrNoRows) {
return new(UpdateContext), errors.WithStack(herodot.ErrNotFound.WithReasonf("The settings request could not be found. Please restart the flow."))
Expand All @@ -63,30 +71,26 @@ func PrepareUpdate(d interface {
return new(UpdateContext), err
}

c := &UpdateContext{Session: ss, Flow: req, Valid: true}
if _, err := d.ContinuityManager().Continue(
r.Context(), w, r, name,
ContinuityOptions(payload, ss.Identity)...); err == nil {
c.Valid = true
if payload.GetRequestID() == rid {
c := &UpdateContext{Session: ss, Flow: req}
if req.Type == flow.TypeAPI {
return c, nil
}

if _, err := d.ContinuityManager().Continue(r.Context(), w, r, name, ContinuityOptions(payload, ss.Identity)...); err == nil {
if payload.GetFlowID() == rid {
return c, ErrContinuePreviousAction
}
d.Logger().
WithField("package", pkgName).
WithField("stack_trace", fmt.Sprintf("%s", debug.Stack())).
WithField("expected_request_id", payload.GetRequestID()).
WithField("expected_request_id", payload.GetFlowID()).
WithField("actual_request_id", rid).
Debug("Flow ID from continuity manager does not match Flow ID from request.")
return c, nil
} else if !errors.Is(err, &continuity.ErrNotResumable) {
return new(UpdateContext), err
}

if err := r.ParseForm(); err != nil {
return new(UpdateContext), errors.WithStack(err)
}

c.Valid = true
return c, nil
}

Expand All @@ -98,10 +102,23 @@ func ContinuityOptions(p interface{}, i *identity.Identity) []continuity.Manager
}
}

func GetRequestID(r *http.Request) (uuid.UUID, error) {
rid := x.ParseUUID(r.URL.Query().Get("request"))
func GetFlowID(r *http.Request) (uuid.UUID, error) {
rid := x.ParseUUID(r.URL.Query().Get("flow"))
if rid == uuid.Nil {
return rid, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing or malformed."))
}
return rid, nil
}

func OnUnauthenticated(c configuration.Provider, reg interface {
x.WriterProvider
}) func(http.ResponseWriter, *http.Request, httprouter.Params) {
return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
handler := session.RedirectOnUnauthenticated(c.SelfServiceFlowLoginUI().String())
if x.IsJSONRequest(r) {
handler = session.RespondWithJSONErrorOnAuthenticated(reg.Writer(), herodot.ErrUnauthorized.WithReasonf("A valid ORY Session Cookie or ORY Session Token is missing."))
}

handler(w, r, ps)
}
}
13 changes: 13 additions & 0 deletions selfservice/strategy/profile/.schema/settings.schema.json
@@ -0,0 +1,13 @@
{
"$id": "https://schemas.ory.sh/kratos/selfservice/strategy/profile/settings.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"required": ["traits"],
"properties": {
"traits": {},
"csrf_token": {
"type": "string",
"minLength": 1
}
}
}
15 changes: 15 additions & 0 deletions selfservice/strategy/profile/schema.go
@@ -0,0 +1,15 @@
package profile

import (
"github.com/markbates/pkger"

"github.com/ory/kratos/x"
)

var _ = pkger.Dir("/selfservice/strategy/profile/.schema")

var settingsSchema []byte

func init() {
settingsSchema = x.MustPkgerRead(pkger.Open("/selfservice/strategy/password/.schema/settings.schema.json"))
}

0 comments on commit c5f361f

Please sign in to comment.