snapshot of https://github.com/ory/kratos.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run tests and lints (push) Failing after 29s
CI / Build CLI docs (push) Has been skipped
CI / Run Playwright end-to-end tests (mysql) (push) Failing after 3s
CI / Run Playwright end-to-end tests (postgres) (push) Failing after 2s
CI / Run Playwright end-to-end tests (sqlite) (push) Failing after 3s
CodeQL / Analyze (go) (push) Failing after 4s
CodeQL / Analyze (javascript) (push) Failing after 3s
Docker Image Scanners / scanners (push) Failing after 4s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Run end-to-end tests (postgres) (push) Failing after 19s
CI / Run end-to-end tests (sqlite) (push) Failing after 2s
CI / Run Playwright end-to-end tests (cockroach) (push) Failing after 2s
CI / Draft newsletter (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
760
selfservice/flow/registration/handler.go
Normal file
760
selfservice/flow/registration/handler.go
Normal file
@@ -0,0 +1,760 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package registration
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
hydraclientgo "github.com/ory/hydra-client-go/v2"
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/hydra"
|
||||
"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/sessiontokenexchange"
|
||||
"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/events"
|
||||
"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/semconv"
|
||||
"github.com/ory/x/sqlcon"
|
||||
"github.com/ory/x/sqlxx"
|
||||
"github.com/ory/x/urlx"
|
||||
)
|
||||
|
||||
const (
|
||||
RouteInitBrowserFlow = "/self-service/registration/browser"
|
||||
RouteInitAPIFlow = "/self-service/registration/api"
|
||||
|
||||
RouteGetFlow = "/self-service/registration/flows"
|
||||
|
||||
RouteSubmitFlow = "/self-service/registration"
|
||||
)
|
||||
|
||||
type (
|
||||
handlerDependencies interface {
|
||||
clock.Provider
|
||||
config.Provider
|
||||
errorx.ManagementProvider
|
||||
hydra.Provider
|
||||
session.HandlerProvider
|
||||
session.ManagementProvider
|
||||
httpx.WriterProvider
|
||||
nosurfx.CSRFTokenGeneratorProvider
|
||||
nosurfx.CSRFProvider
|
||||
StrategyProvider
|
||||
HookExecutorProvider
|
||||
FlowPersistenceProvider
|
||||
ErrorHandlerProvider
|
||||
sessiontokenexchange.PersistenceProvider
|
||||
logrusx.Provider
|
||||
}
|
||||
HandlerProvider interface {
|
||||
RegistrationHandler() *Handler
|
||||
}
|
||||
Handler struct {
|
||||
d handlerDependencies
|
||||
}
|
||||
)
|
||||
|
||||
func NewHandler(d handlerDependencies) *Handler { return &Handler{d: d} }
|
||||
|
||||
func (h *Handler) RegisterPublicRoutes(public *httprouterx.RouterPublic) {
|
||||
h.d.CSRFHandler().IgnorePath(RouteInitAPIFlow)
|
||||
h.d.CSRFHandler().IgnorePath(RouteSubmitFlow)
|
||||
|
||||
public.GET(RouteInitBrowserFlow, h.createBrowserRegistrationFlow)
|
||||
public.GET(RouteInitAPIFlow, h.d.SessionHandler().IsNotAuthenticated(h.createNativeRegistrationFlow,
|
||||
session.RespondWithJSONErrorOnAuthenticated(h.d.Writer(), errors.WithStack(ErrAlreadyLoggedIn()))))
|
||||
|
||||
public.GET(RouteGetFlow, h.getRegistrationFlow)
|
||||
|
||||
public.POST(RouteSubmitFlow, h.d.SessionHandler().IsNotAuthenticated(h.updateRegistrationFlow, h.onAuthenticated))
|
||||
public.GET(RouteSubmitFlow, h.d.SessionHandler().IsNotAuthenticated(h.updateRegistrationFlow, h.onAuthenticated))
|
||||
}
|
||||
|
||||
func (h *Handler) onAuthenticated(w http.ResponseWriter, r *http.Request) {
|
||||
handler := session.RedirectOnAuthenticated(h.d)
|
||||
if x.IsJSONRequest(r) {
|
||||
handler = session.RespondWithJSONErrorOnAuthenticated(h.d.Writer(), ErrAlreadyLoggedIn())
|
||||
}
|
||||
|
||||
handler(w, r)
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
type FlowOption func(f *Flow)
|
||||
|
||||
func WithFlowReturnTo(returnTo string) FlowOption {
|
||||
return func(f *Flow) {
|
||||
f.ReturnTo = returnTo
|
||||
}
|
||||
}
|
||||
|
||||
func WithFlowOAuth2LoginChallenge(loginChallenge string) FlowOption {
|
||||
return func(f *Flow) {
|
||||
f.OAuth2LoginChallenge = sqlxx.NullString(loginChallenge)
|
||||
}
|
||||
}
|
||||
|
||||
func WithFlowIdentitySchema(schema string) FlowOption {
|
||||
return func(f *Flow) {
|
||||
f.IdentitySchema = flow.IdentitySchema(schema)
|
||||
}
|
||||
}
|
||||
|
||||
func WithFlowAccountLinking() FlowOption {
|
||||
return func(f *Flow) {
|
||||
f.MarkAsAccountLinking()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) NewRegistrationFlow(w http.ResponseWriter, r *http.Request, ft flow.Type, opts ...FlowOption) (*Flow, error) {
|
||||
f, err := NewFlow(h.d, r, ft)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(f)
|
||||
}
|
||||
|
||||
// Flows that were converted from a login flow for account linking may be
|
||||
// created even when registration is disabled. PostRegistrationHook
|
||||
// guarantees they can only link to an existing identity, never create one.
|
||||
if !h.d.Config().SelfServiceFlowRegistrationEnabled(r.Context()) && !f.IsAccountLinking() {
|
||||
return nil, errors.WithStack(ErrRegistrationDisabled())
|
||||
}
|
||||
|
||||
if ft == flow.TypeAPI && r.URL.Query().Get("return_session_token_exchange_code") == "true" {
|
||||
e, err := h.d.SessionTokenExchangePersister().CreateSessionTokenExchanger(r.Context(), f.ID)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(herodot.ErrInternalServerError().WithWrap(err))
|
||||
}
|
||||
f.SessionTokenExchangeCode = e.InitCode
|
||||
}
|
||||
|
||||
for _, s := range h.d.RegistrationStrategies(r.Context(), PrepareOrganizations(r, f)...) {
|
||||
var populateErr error
|
||||
|
||||
switch strategy := s.(type) {
|
||||
case FormHydrator:
|
||||
if h.d.Config().SelfServiceFlowRegistrationTwoSteps(r.Context()) {
|
||||
populateErr = strategy.PopulateRegistrationMethodProfile(r, f)
|
||||
} else {
|
||||
populateErr = strategy.PopulateRegistrationMethod(r, f)
|
||||
}
|
||||
case UnifiedFormHydrator:
|
||||
populateErr = strategy.PopulateRegistrationMethod(r, f)
|
||||
default:
|
||||
populateErr = errors.WithStack(x.PseudoPanic.WithReasonf("A registration strategy was expected to implement one of the interfaces UnifiedFormHydrator or FormHydrator but did not."))
|
||||
}
|
||||
|
||||
if populateErr != nil {
|
||||
return nil, populateErr
|
||||
}
|
||||
}
|
||||
|
||||
ds, err := f.IdentitySchema.URL(r.Context(), h.d.Config())
|
||||
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.RegistrationExecutor().PreRegistrationHook(w, r, f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := h.d.RegistrationFlowPersister().CreateRegistrationFlow(r.Context(), f); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
span := trace.SpanFromContext(r.Context())
|
||||
span.AddEvent(events.NewRegistrationInitiated(r.Context(), f.ID, string(ft), f.OrganizationID))
|
||||
|
||||
return f, nil
|
||||
}
|
||||
|
||||
func (h *Handler) FromOldFlow(w http.ResponseWriter, r *http.Request, of Flow) (*Flow, error) {
|
||||
nf, err := h.NewRegistrationFlow(w, r, of.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nf.RequestURL = of.RequestURL
|
||||
nf.IdentitySchema = of.IdentitySchema
|
||||
return nf, nil
|
||||
}
|
||||
|
||||
// swagger:route GET /self-service/registration/api frontend createNativeRegistrationFlow
|
||||
//
|
||||
// # Create Registration Flow for Native Apps
|
||||
//
|
||||
// This endpoint initiates a registration flow for API clients such as mobile devices, smart TVs, and so on.
|
||||
//
|
||||
// If a valid provided session cookie or session token is provided, a 400 Bad Request error
|
||||
// will be returned unless the URL query parameter `?refresh=true` is set.
|
||||
//
|
||||
// To fetch an existing registration flow call `/self-service/registration/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.
|
||||
//
|
||||
// In the case of an error, the `error.id` of the JSON response body can be one of:
|
||||
//
|
||||
// - `session_already_available`: The user is already signed in.
|
||||
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
|
||||
//
|
||||
// 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 Login](https://www.ory.com/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.com/docs/kratos/self-service/flows/user-registration).
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: registrationFlow
|
||||
// 400: errorGeneric
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: kratos-public-medium
|
||||
func (h *Handler) createNativeRegistrationFlow(w http.ResponseWriter, r *http.Request) {
|
||||
a, err := h.NewRegistrationFlow(w, r, flow.TypeAPI)
|
||||
if err != nil {
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
h.d.Writer().Write(w, r, a)
|
||||
}
|
||||
|
||||
// Create Native Registration Flow Parameters
|
||||
//
|
||||
// swagger:parameters createNativeRegistrationFlow
|
||||
//
|
||||
//nolint:deadcode,unused
|
||||
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
||||
type createNativeRegistrationFlow struct {
|
||||
// EnableSessionTokenExchangeCode requests the login flow to include a code that can be used to retrieve the session token
|
||||
// after the login flow has been completed.
|
||||
//
|
||||
// in: query
|
||||
EnableSessionTokenExchangeCode bool `json:"return_session_token_exchange_code"`
|
||||
|
||||
// The URL to return the browser to after the flow was completed.
|
||||
//
|
||||
// in: query
|
||||
ReturnTo string `json:"return_to"`
|
||||
|
||||
// An optional organization ID that should be used to register this user.
|
||||
// This parameter is only effective in the Ory Network.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
Organization string `json:"organization"`
|
||||
|
||||
// An optional identity schema to use for the registration flow.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
IdentitySchema string `json:"identity_schema"`
|
||||
}
|
||||
|
||||
// Create Browser Registration Flow Parameters
|
||||
//
|
||||
// swagger:parameters createBrowserRegistrationFlow
|
||||
//
|
||||
//nolint:deadcode,unused
|
||||
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
||||
type createBrowserRegistrationFlow struct {
|
||||
// The URL to return the browser to after the flow was completed.
|
||||
//
|
||||
// in: query
|
||||
ReturnTo string `json:"return_to"`
|
||||
|
||||
// Ory OAuth 2.0 Login Challenge.
|
||||
//
|
||||
// If set will cooperate with Ory OAuth2 and OpenID to act as an OAuth2 server / OpenID Provider.
|
||||
//
|
||||
// The value for this parameter comes from `login_challenge` URL Query parameter sent to your
|
||||
// application (e.g. `/registration?login_challenge=abcde`).
|
||||
//
|
||||
// This feature is compatible with Ory Hydra when not running on the Ory Network.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
LoginChallenge string `json:"login_challenge"`
|
||||
|
||||
// The URL to return the browser to after the verification flow was completed.
|
||||
//
|
||||
// After the registration flow is completed, the user will be sent a verification email.
|
||||
// Upon completing the verification flow, this URL will be used to override the default
|
||||
// `selfservice.flows.verification.after.default_redirect_to` value.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
AfterVerificationReturnTo string `json:"after_verification_return_to"`
|
||||
|
||||
// An optional organization ID that should be used to register this user.
|
||||
// This parameter is only effective in the Ory Network.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
Organization string `json:"organization"`
|
||||
|
||||
// An optional identity schema to use for the registration flow.
|
||||
//
|
||||
// required: false
|
||||
// in: query
|
||||
IdentitySchema string `json:"identity_schema"`
|
||||
}
|
||||
|
||||
// swagger:route GET /self-service/registration/browser frontend createBrowserRegistrationFlow
|
||||
//
|
||||
// # Create Registration Flow for Browsers
|
||||
//
|
||||
// This endpoint initializes a browser-based user registration flow. This endpoint will set the appropriate
|
||||
// cookies and anti-CSRF measures required for browser-based flows.
|
||||
//
|
||||
// If this endpoint is opened as a link in the browser, it will be redirected to
|
||||
// `selfservice.flows.registration.ui_url` with the flow ID set as the query parameter `?flow=`. If a valid user session
|
||||
// exists already, the browser will be redirected to `urls.default_redirect_url`.
|
||||
//
|
||||
// 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:
|
||||
//
|
||||
// - `session_already_available`: The user is already signed in.
|
||||
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
|
||||
// - `security_identity_mismatch`: The requested `?return_to` address is not allowed to be used. Adjust this in the configuration!
|
||||
//
|
||||
// If this endpoint is called via an AJAX request, the response contains the registration flow without a redirect.
|
||||
//
|
||||
// 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 Login](https://www.ory.com/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.com/docs/kratos/self-service/flows/user-registration).
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 200: registrationFlow
|
||||
// 303: emptyResponse
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: kratos-public-medium
|
||||
func (h *Handler) createBrowserRegistrationFlow(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
a, err := h.NewRegistrationFlow(w, r, flow.TypeBrowser)
|
||||
if err != nil {
|
||||
h.d.SelfServiceErrorManager().Forward(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
hydraLoginRequest *hydraclientgo.OAuth2LoginRequest
|
||||
hydraLoginChallenge sqlxx.NullString
|
||||
)
|
||||
if r.URL.Query().Has("login_challenge") {
|
||||
var err error
|
||||
hydraLoginChallenge, err = hydra.GetLoginChallengeID(h.d.Config(), r)
|
||||
if err != nil {
|
||||
h.d.SelfServiceErrorManager().Forward(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
hydraLoginRequest, err = h.d.Hydra().GetLoginRequest(ctx, hydraLoginChallenge.String())
|
||||
if err != nil {
|
||||
h.d.SelfServiceErrorManager().Forward(ctx, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// on OAuth2 flows, we need to use the RequestURL
|
||||
// as the ReturnTo URL.
|
||||
// This is because a user might want to switch between
|
||||
// different flows, such as login to registration and login to recovery.
|
||||
// After completing a complex flow, such as recovery, we want the user
|
||||
// to be redirected back to the original OAuth2 login flow.
|
||||
if hydraLoginRequest.RequestUrl != "" && h.d.Config().OAuth2ProviderOverrideReturnTo(r.Context()) {
|
||||
q := r.URL.Query()
|
||||
// replace the return_to query parameter
|
||||
q.Set("return_to", hydraLoginRequest.RequestUrl)
|
||||
r.URL.RawQuery = q.Encode()
|
||||
}
|
||||
}
|
||||
|
||||
// Only session columns (identity ID, session ID, AMR) are read below, so skip loading the
|
||||
// devices and the identity.
|
||||
if sess, err := h.d.SessionManager().FetchFromRequest(ctx, r, session.ExpandNothing, identity.ExpandNothing); err == nil {
|
||||
if hydraLoginRequest != nil {
|
||||
if hydraLoginRequest.GetSkip() {
|
||||
rt, err := h.d.Hydra().AcceptLoginRequest(r.Context(),
|
||||
hydra.AcceptLoginRequestParams{
|
||||
LoginChallenge: string(hydraLoginChallenge),
|
||||
IdentityID: sess.IdentityID.String(),
|
||||
SessionID: sess.ID.String(),
|
||||
AuthenticationMethods: sess.AMR,
|
||||
})
|
||||
if err != nil {
|
||||
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err)
|
||||
return
|
||||
}
|
||||
returnTo, err := url.Parse(rt)
|
||||
if err != nil {
|
||||
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, errors.WithStack(herodot.ErrInternalServerError().WithReasonf("Unable to parse URL: %s", rt)))
|
||||
return
|
||||
}
|
||||
x.SendFlowCompletedAsRedirectOrJSON(w, r, h.d.Writer(), err, returnTo.String())
|
||||
return
|
||||
}
|
||||
|
||||
// hydra indicates that we cannot skip the login request
|
||||
// so we must perform the login flow.
|
||||
// we directly go to the login handler from here
|
||||
// copy over any query parameters, such as `return_to` and `login_challenge`
|
||||
loginURL := urlx.CopyWithQuery(urlx.AppendPaths(h.d.Config().SelfPublicURL(ctx), "/self-service/login/browser"), x.RequestURL(r).Query())
|
||||
http.Redirect(
|
||||
w,
|
||||
r,
|
||||
loginURL.String(),
|
||||
http.StatusFound,
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if x.IsJSONRequest(r) {
|
||||
h.d.Writer().WriteError(w, r, errors.WithStack(ErrAlreadyLoggedIn()))
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, returnTo.String(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
redirTo := a.AppendTo(h.d.Config().SelfServiceFlowRegistrationUI(ctx)).String()
|
||||
x.SendFlowCompletedAsRedirectOrJSON(w, r, h.d.Writer(), a, redirTo)
|
||||
}
|
||||
|
||||
// Get Registration Flow Parameters
|
||||
//
|
||||
// swagger:parameters getRegistrationFlow
|
||||
//
|
||||
//nolint:deadcode,unused
|
||||
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
||||
type getRegistrationFlow struct {
|
||||
// The Registration Flow ID
|
||||
//
|
||||
// The value for this parameter comes from `flow` URL Query parameter sent to your
|
||||
// application (e.g. `/registration?flow=abcde`).
|
||||
//
|
||||
// required: true
|
||||
// in: query
|
||||
ID string `json:"id"`
|
||||
|
||||
// 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/registration/flows frontend getRegistrationFlow
|
||||
//
|
||||
// # Get Registration Flow
|
||||
//
|
||||
// This endpoint returns a registration flow's context with, for example, error details and other information.
|
||||
//
|
||||
// Browser flows expect the anti-CSRF cookie to be included in the request's HTTP Cookie Header.
|
||||
// For AJAX requests you must ensure that cookies are included in the request or requests will fail.
|
||||
//
|
||||
// If you use the browser-flow for server-side apps, the services need to run on a common top-level-domain
|
||||
// and you need to forward the incoming HTTP Cookie header to this endpoint:
|
||||
//
|
||||
// ```js
|
||||
// // pseudo-code example
|
||||
// router.get('/registration', async function (req, res) {
|
||||
// const flow = await client.getRegistrationFlow(req.header('cookie'), req.query['flow'])
|
||||
//
|
||||
// res.render('registration', flow)
|
||||
// })
|
||||
// ```
|
||||
//
|
||||
// This request may fail due to several reasons. The `error.id` can be one of:
|
||||
//
|
||||
// - `session_already_available`: The user is already signed in.
|
||||
// - `self_service_flow_expired`: The flow is expired and you should request a new one.
|
||||
//
|
||||
// More information can be found at [Ory Kratos User Login](https://www.ory.com/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.com/docs/kratos/self-service/flows/user-registration).
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Responses:
|
||||
// 200: registrationFlow
|
||||
// 403: errorGeneric
|
||||
// 404: errorGeneric
|
||||
// 410: errorGeneric
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: kratos-public-high
|
||||
func (h *Handler) getRegistrationFlow(w http.ResponseWriter, r *http.Request) {
|
||||
ar, err := h.d.RegistrationFlowPersister().GetRegistrationFlow(r.Context(), x.ParseUUID(r.URL.Query().Get("id")))
|
||||
if err != nil {
|
||||
// A flow that does not exist reports a disabled registration to not
|
||||
// leak flow existence. Any other error is returned as-is, so that an
|
||||
// infrastructure failure is not misreported as a disabled
|
||||
// registration.
|
||||
if errors.Is(err, sqlcon.ErrNoRows()) && !h.d.Config().SelfServiceFlowRegistrationEnabled(r.Context()) {
|
||||
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, errors.WithStack(ErrRegistrationDisabled()))
|
||||
return
|
||||
}
|
||||
|
||||
h.d.Writer().WriteError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// When registration is disabled, only flows that were converted from a
|
||||
// login flow for account linking may be fetched.
|
||||
if !ar.IsAccountLinking() && !h.d.Config().SelfServiceFlowRegistrationEnabled(r.Context()) {
|
||||
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, errors.WithStack(ErrRegistrationDisabled()))
|
||||
return
|
||||
}
|
||||
|
||||
// Browser flows must include the CSRF token
|
||||
//
|
||||
// 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, nosurfx.CSRFErrorReason(r, h.d))
|
||||
return
|
||||
}
|
||||
|
||||
if ar.ExpiresAt.Before(h.d.Clock().Now()) {
|
||||
if ar.Type == flow.TypeBrowser {
|
||||
redirectURL := flow.GetFlowExpiredRedirectURL(r.Context(), h.d.Config(), RouteInitBrowserFlow, ar.ReturnTo)
|
||||
|
||||
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(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
|
||||
}
|
||||
|
||||
if ar.OAuth2LoginChallenge != "" {
|
||||
hlr, err := h.d.Hydra().GetLoginRequest(r.Context(), string(ar.OAuth2LoginChallenge))
|
||||
if err != nil {
|
||||
// We don't redirect back to the third party on errors because Hydra doesn't
|
||||
// give us the 3rd party return_uri when it redirects to the login UI.
|
||||
h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err)
|
||||
return
|
||||
}
|
||||
ar.HydraLoginRequest = hlr
|
||||
}
|
||||
|
||||
h.d.Writer().Write(w, r, ar)
|
||||
}
|
||||
|
||||
// Update Registration Flow Parameters
|
||||
//
|
||||
// swagger:parameters updateRegistrationFlow
|
||||
//
|
||||
//nolint:deadcode,unused
|
||||
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
||||
type updateRegistrationFlow struct {
|
||||
// The Registration Flow ID
|
||||
//
|
||||
// The value for this parameter comes from `flow` URL Query parameter sent to your
|
||||
// application (e.g. `/registration?flow=abcde`).
|
||||
//
|
||||
// required: true
|
||||
// in: query
|
||||
Flow string `json:"flow"`
|
||||
|
||||
// in: body
|
||||
// required: true
|
||||
Body updateRegistrationFlowBody
|
||||
|
||||
// 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 Registration Request Body
|
||||
//
|
||||
// swagger:model updateRegistrationFlowBody
|
||||
//
|
||||
//nolint:deadcode,unused
|
||||
//lint:ignore U1000 Used to generate Swagger and OpenAPI definitions
|
||||
type updateRegistrationFlowBody struct{}
|
||||
|
||||
// swagger:route POST /self-service/registration frontend updateRegistrationFlow
|
||||
//
|
||||
// # Update Registration Flow
|
||||
//
|
||||
// Use this endpoint to complete a registration flow by sending an identity's traits and password. This endpoint
|
||||
// behaves differently for API and browser flows.
|
||||
//
|
||||
// API flows expect `application/json` to be sent in the body and respond with
|
||||
// - HTTP 200 and a application/json body with the created identity success - if the session hook is configured the
|
||||
// `session` and `session_token` will also be included;
|
||||
// - HTTP 410 if the original flow expired with the appropriate error messages set and optionally a `use_flow_id` parameter in the body;
|
||||
// - HTTP 400 on form validation errors.
|
||||
//
|
||||
// Browser flows expect a Content-Type of `application/x-www-form-urlencoded` or `application/json` to be sent in the body and respond with
|
||||
// - a HTTP 303 redirect to the post/after registration URL or the `return_to` value if it was set and if the registration succeeded;
|
||||
// - a HTTP 303 redirect to the registration UI URL with the flow ID containing the validation errors otherwise.
|
||||
//
|
||||
// Browser flows with an accept header of `application/json` will not redirect but instead 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 400 on form validation errors.
|
||||
//
|
||||
// If this endpoint is called with `Accept: application/json` in the 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_already_available`: The user is already signed in.
|
||||
// - `security_csrf_violation`: Unable to fetch the flow because a CSRF violation occurred.
|
||||
// - `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 Login](https://www.ory.com/docs/kratos/self-service/flows/user-login) and [User Registration Documentation](https://www.ory.com/docs/kratos/self-service/flows/user-registration).
|
||||
//
|
||||
// Schemes: http, https
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
// - application/x-www-form-urlencoded
|
||||
//
|
||||
// Produces:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 200: successfulNativeRegistration
|
||||
// 303: emptyResponse
|
||||
// 400: registrationFlow
|
||||
// 410: errorGeneric
|
||||
// 422: errorBrowserLocationChangeRequired
|
||||
// default: errorGeneric
|
||||
//
|
||||
// Extensions:
|
||||
// x-ory-ratelimit-bucket: kratos-public-low
|
||||
func (h *Handler) updateRegistrationFlow(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
ctx = semconv.ContextWithAttributes(
|
||||
ctx,
|
||||
attribute.String(events.AttributeKeySelfServiceStrategyUsed.String(), "registration"),
|
||||
attribute.String(events.AttributeKeySelfServiceFlowName.String(), "registration"),
|
||||
)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
rid, err := flow.GetFlowID(r)
|
||||
if err != nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, nil, "", node.DefaultGroup, err)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := h.d.RegistrationFlowPersister().GetRegistrationFlow(r.Context(), rid)
|
||||
if err != nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, nil, "", node.DefaultGroup, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.d.SessionManager().SessionActiveForRequest(r.Context(), r); err == nil {
|
||||
if f.Type == flow.TypeBrowser {
|
||||
http.Redirect(w, r, h.d.Config().SelfServiceBrowserDefaultReturnTo(r.Context()).String(), http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
h.d.Writer().WriteError(w, r, errors.WithStack(ErrAlreadyLoggedIn()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := f.Valid(h.d.Clock()); err != nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, f, "", node.DefaultGroup, err)
|
||||
return
|
||||
}
|
||||
|
||||
i := identity.NewIdentity(f.IdentitySchema.ID(ctx, h.d.Config()))
|
||||
var s Strategy
|
||||
for _, ss := range h.d.AllRegistrationStrategies() {
|
||||
if err := ss.Register(w, r, f, i); errors.Is(err, flow.ErrStrategyNotResponsible) {
|
||||
continue
|
||||
} else if errors.Is(err, flow.ErrCompletedByStrategy) {
|
||||
return
|
||||
} else if err != nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, f, ss.ID(), ss.NodeGroup(), err)
|
||||
return
|
||||
}
|
||||
|
||||
s = ss
|
||||
break
|
||||
}
|
||||
|
||||
if s == nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, f, "", node.DefaultGroup, errors.WithStack(schema.NewNoRegistrationStrategyResponsible()))
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.d.RegistrationExecutor().PostRegistrationHook(w, r, f, i, session.AuthenticationMethod{
|
||||
Method: s.ID(),
|
||||
AAL: identity.AuthenticatorAssuranceLevel1,
|
||||
}); err != nil {
|
||||
h.d.RegistrationFlowErrorHandler().WriteFlowError(w, r, f, s.ID(), s.NodeGroup(), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user