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:
160
selfservice/hook/session_issuer.go
Normal file
160
selfservice/hook/session_issuer.go
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package hook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/kratos/hydra"
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/kratos/x/events"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
"github.com/ory/kratos/selfservice/flow"
|
||||
"github.com/ory/kratos/selfservice/flow/registration"
|
||||
"github.com/ory/kratos/selfservice/sessiontokenexchange"
|
||||
"github.com/ory/kratos/session"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/x/httpx"
|
||||
"github.com/ory/x/otelx"
|
||||
)
|
||||
|
||||
var _ registration.PostHookPostPersistExecutor = new(SessionIssuer)
|
||||
|
||||
type (
|
||||
sessionIssuerDependencies interface {
|
||||
session.ManagementProvider
|
||||
session.PersistenceProvider
|
||||
sessiontokenexchange.PersistenceProvider
|
||||
config.Provider
|
||||
httpx.WriterProvider
|
||||
hydra.Provider
|
||||
}
|
||||
SessionIssuerProvider interface {
|
||||
HookSessionIssuer() *SessionIssuer
|
||||
}
|
||||
SessionIssuer struct {
|
||||
r sessionIssuerDependencies
|
||||
}
|
||||
)
|
||||
|
||||
func NewSessionIssuer(r sessionIssuerDependencies) *SessionIssuer {
|
||||
return &SessionIssuer{r: r}
|
||||
}
|
||||
|
||||
func (e *SessionIssuer) ExecutePostRegistrationPostPersistHook(w http.ResponseWriter, r *http.Request, a *registration.Flow, s *session.Session) error {
|
||||
return otelx.WithSpan(r.Context(), "selfservice.hook.SessionIssuer.ExecutePostRegistrationPostPersistHook", func(ctx context.Context) error {
|
||||
return e.executePostRegistrationPostPersistHook(w, r.WithContext(ctx), a, s)
|
||||
})
|
||||
}
|
||||
|
||||
func (e *SessionIssuer) executePostRegistrationPostPersistHook(w http.ResponseWriter, r *http.Request, a *registration.Flow, s *session.Session) error {
|
||||
if a.Type == flow.TypeAPI {
|
||||
// We don't want to redirect with the code, if the flow was submitted with an ID token.
|
||||
// This is the case for Sign in with native Apple SDK or Google SDK.
|
||||
if s.AuthenticatedVia(identity.CredentialsTypeOIDC) && a.IDToken == "" {
|
||||
if handled, err := e.r.SessionManager().MaybeRedirectAPICodeFlow(w, r, a, s.ID, node.OpenIDConnectGroup); err != nil {
|
||||
return errors.WithStack(err)
|
||||
} else if handled {
|
||||
// The redirect response has already been written. Abort the flow
|
||||
// so the executor does not run its tail block, which would call
|
||||
// MaybeRedirectAPICodeFlow a second time and issue a redundant
|
||||
// (and slow) write to the GLOBAL session_token_exchanges table.
|
||||
return errors.WithStack(registration.ErrHookAbortFlow)
|
||||
}
|
||||
}
|
||||
|
||||
a.AddContinueWith(flow.NewContinueWithSetToken(s.Token))
|
||||
e.r.Writer().Write(w, r, ®istration.APIFlowResponse{
|
||||
Session: s,
|
||||
Token: s.Token,
|
||||
Identity: s.Identity,
|
||||
ContinueWith: a.ContinueWithItems,
|
||||
})
|
||||
|
||||
trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginSucceeded(r.Context(), &events.LoginSucceededOpts{
|
||||
SessionID: s.ID,
|
||||
IdentityID: s.Identity.ID,
|
||||
FlowID: a.ID,
|
||||
FlowType: string(a.Type),
|
||||
Method: a.Active.String(),
|
||||
}))
|
||||
|
||||
return errors.WithStack(registration.ErrHookAbortFlow)
|
||||
}
|
||||
|
||||
// cookie is issued both for browser and for SPA flows
|
||||
if err := e.r.SessionManager().IssueCookie(r.Context(), w, r, s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
trace.SpanFromContext(r.Context()).AddEvent(events.NewLoginSucceeded(r.Context(), &events.LoginSucceededOpts{
|
||||
SessionID: s.ID,
|
||||
IdentityID: s.Identity.ID,
|
||||
FlowID: a.ID,
|
||||
FlowType: string(a.Type),
|
||||
Method: a.Active.String(),
|
||||
}))
|
||||
|
||||
// SPA flows additionally send the session
|
||||
if x.IsJSONRequest(r) {
|
||||
if err := e.acceptLoginChallenge(r.Context(), a, s, s.Identity); err != nil {
|
||||
return err
|
||||
}
|
||||
e.r.Writer().Write(w, r, ®istration.APIFlowResponse{
|
||||
Session: s,
|
||||
Identity: s.Identity,
|
||||
ContinueWith: a.ContinueWithItems,
|
||||
})
|
||||
return errors.WithStack(registration.ErrHookAbortFlow)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (e *SessionIssuer) acceptLoginChallenge(ctx context.Context, registrationFlow *registration.Flow, s *session.Session, i *identity.Identity) error {
|
||||
// If Kratos is used as a Hydra login provider, we need to redirect back to Hydra by using the continue_with items
|
||||
// with the post login challenge URL as the body.
|
||||
// We only do this if the flow did not create a verification flow (e.g. verification is disabled or not active due to it being a code flow).
|
||||
// Since the session issuer hook must be the last hook in the flow, we can safely assume that the verification flow was already added (if it was)
|
||||
if registrationFlow.OAuth2LoginChallenge != "" && !willVerificationFollow(registrationFlow) {
|
||||
postChallengeURL, err := e.r.Hydra().AcceptLoginRequest(ctx,
|
||||
hydra.AcceptLoginRequestParams{
|
||||
LoginChallenge: string(registrationFlow.OAuth2LoginChallenge),
|
||||
IdentityID: i.ID.String(),
|
||||
SessionID: s.ID.String(),
|
||||
AuthenticationMethods: s.AMR,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cw := []flow.ContinueWith{}
|
||||
for _, i := range registrationFlow.ContinueWithItems {
|
||||
// Filter any continueWithRedirectBrowserTo items out of the list
|
||||
// We will add a new one at the end of the flow
|
||||
// as the OAuth2 login challenge should be the last step in the flow
|
||||
if i.GetAction() != string(flow.ContinueWithActionRedirectBrowserToString) {
|
||||
cw = append(cw, i)
|
||||
}
|
||||
}
|
||||
registrationFlow.ContinueWithItems = append(cw, flow.NewContinueWithRedirectBrowserTo(postChallengeURL))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// willVerificationFollow returns true if the flow's continue with items contain a verification UI.
|
||||
func willVerificationFollow(f *registration.Flow) bool {
|
||||
for _, i := range f.ContinueWithItems {
|
||||
if i.GetAction() == string(flow.ContinueWithActionShowVerificationUIString) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
Reference in New Issue
Block a user