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

709 lines
26 KiB
Go

// Copyright © 2023 Ory Corp
// SPDX-License-Identifier: Apache-2.0
package settings
import (
"context"
"net/http"
"net/url"
"github.com/pkg/errors"
"github.com/ory/herodot"
"github.com/ory/kratos/continuity"
"github.com/ory/kratos/driver/config"
"github.com/ory/kratos/identity"
"github.com/ory/kratos/schema"
"github.com/ory/kratos/selfservice/errorx"
"github.com/ory/kratos/selfservice/flow"
"github.com/ory/kratos/selfservice/flow/login"
"github.com/ory/kratos/session"
"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/kratos/x/redir"
"github.com/ory/nosurf"
"github.com/ory/x/clock"
"github.com/ory/x/httprouterx"
"github.com/ory/x/httpx"
"github.com/ory/x/logrusx"
"github.com/ory/x/otelx"
"github.com/ory/x/sqlcon"
"github.com/ory/x/urlx"
)
const (
RouteInitBrowserFlow = "/self-service/settings/browser"
RouteInitAPIFlow = "/self-service/settings/api"
RouteGetFlow = "/self-service/settings/flows"
RouteSubmitFlow = "/self-service/settings"
// RouteWellKnownChangePassword implements the W3C "change password URL"
// well-known location. Browsers and password managers use it to deep-link
// users to the page where they can change their password.
//
// See https://w3c.github.io/webappsec-change-password-url/.
RouteWellKnownChangePassword = "/.well-known/change-password"
ContinuityPrefix = "ory_kratos_settings"
)
func ContinuityKey(id string) string {
return ContinuityPrefix + "_" + id
}
type (
handlerDependencies interface {
clock.Provider
nosurfx.CSRFProvider
httpx.WriterProvider
logrusx.Provider
otelx.Provider
config.Provider
session.HandlerProvider
session.ManagementProvider
identity.ValidationProvider
identity.ManagementProvider
identity.PrivilegedPoolProvider
errorx.ManagementProvider
continuity.ManagementProvider
x.CookieProvider
ErrorHandlerProvider
FlowPersistenceProvider
StrategyProvider
HookExecutorProvider
nosurfx.CSRFTokenGeneratorProvider
schema.IdentitySchemaProvider
login.HandlerProvider
}
HandlerProvider interface {
SettingsHandler() *Handler
}
Handler struct {
d handlerDependencies
csrf nosurfx.CSRFToken
}
)
func NewHandler(d handlerDependencies) *Handler { return &Handler{d: d, csrf: nosurf.Token} }
func (h *Handler) RegisterPublicRoutes(public *httprouterx.RouterPublic) {
h.d.CSRFHandler().IgnorePath(RouteInitAPIFlow)
h.d.CSRFHandler().IgnorePath(RouteSubmitFlow)
public.GET(RouteInitBrowserFlow, h.d.SessionHandler().IsAuthenticated(h.createBrowserSettingsFlow, func(w http.ResponseWriter, r *http.Request) {
if x.IsJSONRequest(r) {
h.d.Writer().WriteError(w, r, session.NewErrNoActiveSessionFound())
} else {
loginFlowUrl := h.d.Config().SelfPublicURL(r.Context()).JoinPath(login.RouteInitBrowserFlow).String()
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 {
http.Redirect(w, r, redirectUrl, http.StatusSeeOther)
}
}
}))
public.GET(RouteInitAPIFlow, h.d.SessionHandler().IsAuthenticated(h.createNativeSettingsFlow, nil))
public.GET(RouteGetFlow, h.d.SessionHandler().IsAuthenticated(h.getSettingsFlow, OnUnauthenticated(h.d)))
public.POST(RouteSubmitFlow, h.d.SessionHandler().IsAuthenticated(h.updateSettingsFlow, OnUnauthenticated(h.d)))
public.GET(RouteSubmitFlow, h.d.SessionHandler().IsAuthenticated(h.updateSettingsFlow, OnUnauthenticated(h.d)))
public.GET(RouteWellKnownChangePassword, h.redirectToChangePassword)
}
// swagger:route GET /.well-known/change-password frontend getWellKnownChangePassword
//
// # Change Password URL
//
// This endpoint implements the W3C "change password URL" well-known location by
// redirecting the browser to the configured settings UI. Password managers follow
// this redirect to take users straight to the page where they can change their
// password.
//
// Schemes: http, https
//
// Responses:
// 303: emptyResponse
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: kratos-public-high
func (h *Handler) redirectToChangePassword(w http.ResponseWriter, r *http.Request) {
to := h.d.Config().SelfServiceFlowSettingsUI(r.Context()).String()
if to == "" {
to = h.d.Config().SelfPublicURL(r.Context()).JoinPath(RouteInitBrowserFlow).String()
}
http.Redirect(w, r, to, http.StatusSeeOther)
}
func (h *Handler) RegisterAdminRoutes(admin *httprouterx.RouterAdmin) {
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))
}
func (h *Handler) NewFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, i *identity.Identity, s *session.Session, ft flow.Type) (_ *Flow, err error) {
ctx, span := h.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.Handler.NewFlow")
defer otelx.End(span, &err)
f, err := NewFlow(h.d, r, i, ft)
if err != nil {
return nil, err
}
if err := h.d.SettingsHookExecutor().PreSettingsHook(ctx, w, r, f, s); err != nil {
return nil, err
}
filters := PrepareOrganizations(r, f, i, h.d.Config().Organizations(ctx))
cookieStore := continuity.NewCookieReferenceStore(h.d.ContinuityCookieManager(ctx))
for _, strategy := range h.d.SettingsStrategies(ctx, filters...) {
if err := h.d.ContinuityManager().Abort(ctx, w, r, ContinuityKey(strategy.SettingsStrategyID()), cookieStore); err != nil {
return nil, err
}
if err := strategy.PopulateSettingsMethod(ctx, r, i, f); err != nil {
return nil, err
}
}
ds, err := h.d.Config().IdentityTraitsSchemaURL(ctx, i.SchemaID)
if err != nil {
return nil, err
}
if err := sortNodes(r.Context(), f.UI.Nodes, ds.String()); err != nil {
return nil, err
}
if err := h.d.SettingsFlowPersister().CreateSettingsFlow(r.Context(), f); err != nil {
return nil, err
}
return f, nil
}
func (h *Handler) FromOldFlow(ctx context.Context, w http.ResponseWriter, r *http.Request, i *identity.Identity, s *session.Session, of Flow) (*Flow, error) {
nf, err := h.NewFlow(ctx, w, r, i, s, of.Type)
if err != nil {
return nil, err
}
nf.RequestURL = of.RequestURL
return nf, nil
}
// Create Native Settings Flow Parameters
//
// swagger:parameters createNativeSettingsFlow
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type createNativeSettingsFlow struct {
// The Session Token of the Identity performing the settings flow.
//
// in: header
SessionToken string `json:"X-Session-Token"`
// An optional organization ID that scopes the settings flow to providers of that organization.
// This parameter is only effective in the Ory Network.
//
// required: false
// in: query
Organization string `json:"organization"`
}
// swagger:route GET /self-service/settings/api frontend createNativeSettingsFlow
//
// # Create Settings Flow for Native Apps
//
// This endpoint initiates a settings flow for API clients such as mobile devices, smart TVs, and so on.
// You must provide a valid Ory Kratos Session Token for this endpoint to respond with HTTP 200 OK.
//
// To fetch an existing settings flow call `/self-service/settings/flows?flow=<flow_id>`.
//
// You MUST NOT use this endpoint in client-side (Single Page Apps, ReactJS, AngularJS) nor server-side (Java Server
// Pages, NodeJS, PHP, Golang, ...) browser applications. Using this endpoint in these applications will make
// you vulnerable to a variety of CSRF attacks.
//
// Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator
// Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn
// credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user
// to sign in with the second factor or change the configuration.
//
// In the case of an error, the `error.id` of the JSON response body can be one of:
//
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
// - `session_inactive`: No Ory Session was found - sign in a user first.
//
// This endpoint MUST ONLY be used in scenarios such as native mobile apps (React Native, Objective C, Swift, Java, ...).
//
// More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings).
//
// Schemes: http, https
//
// Responses:
// 200: settingsFlow
// 400: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: kratos-public-medium
func (h *Handler) createNativeSettingsFlow(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s, err := h.d.SessionManager().FetchFromRequestContext(ctx, r)
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
if err := h.d.SessionManager().DoesSessionSatisfy(ctx, s, h.d.Config().SelfServiceSettingsRequiredAAL(ctx)); err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
f, err := h.NewFlow(ctx, w, r, s.Identity, s, flow.TypeAPI)
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
h.d.Writer().Write(w, r, f)
}
// Create Browser Settings Flow Parameters
//
// swagger:parameters createBrowserSettingsFlow
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type createBrowserSettingsFlow struct {
// The URL to return the browser to after the flow was completed.
//
// in: query
ReturnTo string `json:"return_to"`
// HTTP Cookies
//
// When using the SDK in a browser app, on the server side you must include the HTTP Cookie Header
// sent by the client to your server here. This ensures that CSRF and session cookies are respected.
//
// in: header
// name: Cookie
Cookies string `json:"Cookie"`
// An optional organization ID that scopes the settings flow to providers of that organization.
// This parameter is only effective in the Ory Network.
//
// required: false
// in: query
Organization string `json:"organization"`
}
// swagger:route GET /self-service/settings/browser frontend createBrowserSettingsFlow
//
// # Create Settings Flow for Browsers
//
// This endpoint initializes a browser-based user settings flow. Once initialized, the browser will be redirected to
// `selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid
// Ory Kratos Session Cookie is included in the request, a login flow will be initialized.
//
// If this endpoint is opened as a link in the browser, it will be redirected to
// `selfservice.flows.settings.ui_url` with the flow ID set as the query parameter `?flow=`. If no valid user session
// was set, the browser will be redirected to the login endpoint.
//
// If this endpoint is called via an AJAX request, the response contains the settings flow without any redirects
// or a 401 forbidden error if no valid session was set.
//
// Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator
// Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn
// credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user
// to sign in with the second factor (happens automatically for server-side browser flows) or change the configuration.
//
// If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the
// case of an error, the `error.id` of the JSON response body can be one of:
//
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
// - `session_inactive`: No Ory Session was found - sign in a user first.
// - `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration!
//
// This endpoint is NOT INTENDED for clients that do not have a browser (Chrome, Firefox, ...) as cookies are needed.
//
// More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings).
//
// Schemes: http, https
//
// Responses:
// 200: settingsFlow
// 303: emptyResponse
// 400: errorGeneric
// 401: errorGeneric
// 403: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: kratos-public-medium
func (h *Handler) createBrowserSettingsFlow(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
s, err := h.d.SessionManager().FetchFromRequestContext(ctx, r)
if err != nil {
h.d.SelfServiceErrorManager().Forward(ctx, w, r, err)
return
}
var managerOptions []session.ManagerOptions
requestURL := x.RequestURL(r)
if requestURL.Query().Get("return_to") != "" {
managerOptions = append(managerOptions, session.WithRequestURL(requestURL.String()))
}
if err := h.d.SessionManager().DoesSessionSatisfy(ctx, s, h.d.Config().SelfServiceSettingsRequiredAAL(ctx), managerOptions...); err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, nil, err)
return
}
f, err := h.NewFlow(ctx, w, r, s.Identity, s, flow.TypeBrowser)
if err != nil {
h.d.SelfServiceErrorManager().Forward(ctx, w, r, err)
return
}
redirTo := f.AppendTo(h.d.Config().SelfServiceFlowSettingsUI(ctx)).String()
x.SendFlowCompletedAsRedirectOrJSON(w, r, h.d.Writer(), f, redirTo)
}
// Get Settings Flow
//
// swagger:parameters getSettingsFlow
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type getSettingsFlow struct {
// ID is the Settings Flow ID
//
// The value for this parameter comes from `flow` URL Query parameter sent to your
// application (e.g. `/settings?flow=abcde`).
//
// required: true
// in: query
ID string `json:"id"`
// The Session Token
//
// When using the SDK in an app without a browser, please include the
// session token here.
//
// in: header
SessionToken string `json:"X-Session-Token"`
// HTTP Cookies
//
// When using the SDK in a browser app, on the server side you must include the HTTP Cookie Header
// sent by the client to your server here. This ensures that CSRF and session cookies are respected.
//
// in: header
// name: Cookie
Cookies string `json:"Cookie"`
}
// swagger:route GET /self-service/settings/flows frontend getSettingsFlow
//
// # Get Settings Flow
//
// When accessing this endpoint through Ory Kratos' Public API you must ensure that either the Ory Kratos Session Cookie
// or the Ory Kratos Session Token are set.
//
// Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator
// Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn
// credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user
// to sign in with the second factor or change the configuration.
//
// You can access this endpoint without credentials when using Ory Kratos' Admin API.
//
// If this endpoint is called via an AJAX request, the response contains the flow without a redirect. In the
// case of an error, the `error.id` of the JSON response body can be one of:
//
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
// - `session_inactive`: No Ory Session was found - sign in a user first.
// - `security_identity_mismatch`: The flow was interrupted with `session_refresh_required` but apparently some other
// identity logged in instead.
//
// More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings).
//
// Produces:
// - application/json
//
// Schemes: http, https
//
// Responses:
// 200: settingsFlow
// 401: errorGeneric
// 403: errorGeneric
// 404: errorGeneric
// 410: errorGeneric
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: kratos-public-high
func (h *Handler) getSettingsFlow(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
rid := x.ParseUUID(r.URL.Query().Get("id"))
pr, err := h.d.SettingsFlowPersister().GetSettingsFlow(ctx, rid)
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
sess, err := h.d.SessionManager().FetchFromRequestContext(ctx, r)
if err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
if pr.IdentityID != sess.Identity.ID {
h.d.Writer().WriteError(w, r, errors.WithStack(herodot.ErrForbidden().
WithID(text.ErrIDInitiatedBySomeoneElse).
WithReasonf("The request was made for another identity and has been blocked for security reasons.")))
return
}
// we cannot redirect back to the request URL (/self-service/settings/flows?id=...) since it would just redirect
// to a page displaying raw JSON to the client (browser), which is not what we want.
// Let's rather carry over the flow ID as a query parameter and redirect to the settings UI URL.
requestURL := urlx.CopyWithQuery(h.d.Config().SelfServiceFlowSettingsUI(ctx), url.Values{"flow": {rid.String()}})
if err := h.d.SessionManager().DoesSessionSatisfy(ctx, sess, h.d.Config().SelfServiceSettingsRequiredAAL(ctx), session.WithRequestURL(requestURL.String())); err != nil {
h.d.Writer().WriteError(w, r, err)
return
}
if pr.ExpiresAt.Before(h.d.Clock().Now().UTC()) {
if pr.Type == flow.TypeBrowser {
redirectURL := flow.GetFlowExpiredRedirectURL(ctx, h.d.Config(), RouteInitBrowserFlow, pr.ReturnTo)
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(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
}
h.d.Writer().Write(w, r, pr)
}
// Update Settings Flow Parameters
//
// swagger:parameters updateSettingsFlow
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type updateSettingsFlow struct {
// The Settings Flow ID
//
// The value for this parameter comes from `flow` URL Query parameter sent to your
// application (e.g. `/settings?flow=abcde`).
//
// required: true
// in: query
Flow string `json:"flow"`
// in: body
// required: true
Body updateSettingsFlowBody
// The Session Token of the Identity performing the settings flow.
//
// in: header
SessionToken string `json:"X-Session-Token"`
// HTTP Cookies
//
// When using the SDK in a browser app, on the server side you must include the HTTP Cookie Header
// sent by the client to your server here. This ensures that CSRF and session cookies are respected.
//
// in: header
// name: Cookie
Cookies string `json:"Cookie"`
}
// Update Settings Flow Request Body
//
// swagger:model updateSettingsFlowBody
//
//nolint:deadcode,unused
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
type updateSettingsFlowBody struct{}
// swagger:route POST /self-service/settings frontend updateSettingsFlow
//
// # Complete Settings Flow
//
// Use this endpoint to complete a settings flow by sending an identity's updated password. This endpoint
// behaves differently for API and browser flows.
//
// API-initiated flows expect `application/json` to be sent in the body and respond with
// - HTTP 200 and an application/json body with the session token on success;
// - HTTP 303 redirect to a fresh settings flow if the original flow expired with the appropriate error messages set;
// - HTTP 400 on form validation errors.
// - HTTP 401 when the endpoint is called without a valid session token.
// - HTTP 403 when `selfservice.flows.settings.privileged_session_max_age` was reached or the session's AAL is too low.
// Implies that the user needs to re-authenticate.
//
// Browser flows without HTTP Header `Accept` or with `Accept: text/*` respond with
// - a HTTP 303 redirect to the post/after settings URL or the `return_to` value if it was set and if the flow succeeded;
// - a HTTP 303 redirect to the Settings UI URL with the flow ID containing the validation errors otherwise.
// - a HTTP 303 redirect to the login endpoint when `selfservice.flows.settings.privileged_session_max_age` was reached or the session's AAL is too low.
//
// Browser flows with HTTP Header `Accept: application/json` respond with
// - HTTP 200 and a application/json body with the signed in identity and a `Set-Cookie` header on success;
// - HTTP 303 redirect to a fresh login flow if the original flow expired with the appropriate error messages set;
// - HTTP 401 when the endpoint is called without a valid session cookie.
// - HTTP 403 when the page is accessed without a session cookie or the session's AAL is too low.
// - HTTP 400 on form validation errors.
//
// Depending on your configuration this endpoint might return a 403 error if the session has a lower Authenticator
// Assurance Level (AAL) than is possible for the identity. This can happen if the identity has password + webauthn
// credentials (which would result in AAL2) but the session has only AAL1. If this error occurs, ask the user
// to sign in with the second factor (happens automatically for server-side browser flows) or change the configuration.
//
// If this endpoint is called with a `Accept: application/json` HTTP header, the response contains the flow without a redirect. In the
// case of an error, the `error.id` of the JSON response body can be one of:
//
// - `session_refresh_required`: The identity requested to change something that needs a privileged session. Redirect
// the identity to the login init endpoint with query parameters `?refresh=true&return_to=<the-current-browser-url>`,
// or initiate a refresh login flow otherwise.
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
// - `session_inactive`: No Ory Session was found - sign in a user first.
// - `security_identity_mismatch`: The flow was interrupted with `session_refresh_required` but apparently some other
// identity logged in instead.
// - `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration!
// - `browser_location_change_required`: Usually sent when an AJAX request indicates that the browser needs to open a specific URL.
// Most likely used in Social Sign In flows.
//
// More information can be found at [Ory Kratos User Settings & Profile Management Documentation](../self-service/flows/user-settings).
//
// Consumes:
// - application/json
// - application/x-www-form-urlencoded
//
// Produces:
// - application/json
//
// Security:
// sessionToken:
//
// Schemes: http, https
//
// Responses:
// 200: settingsFlow
// 303: emptyResponse
// 400: settingsFlow
// 401: errorGeneric
// 403: errorGeneric
// 410: errorGeneric
// 422: errorBrowserLocationChangeRequired
// default: errorGeneric
//
// Extensions:
// x-ory-ratelimit-bucket: kratos-public-low
func (h *Handler) updateSettingsFlow(w http.ResponseWriter, r *http.Request) {
var (
err error
ctx = r.Context()
)
ctx, span := h.d.Tracer(ctx).Tracer().Start(ctx, "selfservice.flow.settings.Handler.updateSettingsFlow")
defer otelx.End(span, &err)
rid, err := GetFlowID(r)
if err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, nil, err)
return
}
f, err := h.d.SettingsFlowPersister().GetSettingsFlow(ctx, rid)
if errors.Is(err, sqlcon.ErrNoRows()) {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, nil, errors.WithStack(herodot.ErrNotFound().WithReasonf("The settings request could not be found. Please restart the flow.")))
return
} else if err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, nil, nil, nil, err)
return
}
ss, err := h.d.SessionManager().FetchFromRequestContext(ctx, r)
if err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, nil, nil, err)
return
}
requestURL := x.RequestURL(r).String()
if err := h.d.SessionManager().DoesSessionSatisfy(ctx, ss, h.d.Config().SelfServiceSettingsRequiredAAL(ctx), session.WithRequestURL(requestURL)); err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, nil, nil, err)
return
}
if err := f.Valid(h.d.Clock(), ss); err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, ss, err)
return
}
var s string
var updateContext *UpdateContext
for _, strat := range h.d.AllSettingsStrategies() {
uc, err := strat.Settings(ctx, w, r, f, ss)
if errors.Is(err, flow.ErrStrategyNotResponsible) {
continue
} else if errors.Is(err, flow.ErrCompletedByStrategy) {
return
} else if err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, strat.NodeGroup(), f, ss.Identity, ss, err)
return
}
s = strat.SettingsStrategyID()
updateContext = uc
break
}
if updateContext == nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, ss, errors.WithStack(schema.NewNoSettingsStrategyResponsible()))
return
}
i, err := updateContext.GetIdentityToUpdate()
if err != nil {
// An identity to update must always be present.
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, ss, err)
return
}
if err := h.d.SettingsHookExecutor().PostSettingsHook(ctx, w, r, s, updateContext, i); err != nil {
h.d.SettingsFlowErrorHandler().WriteFlowError(ctx, w, r, node.DefaultGroup, f, ss.Identity, ss, err)
return
}
}