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:
548
session/manager_http.go
Normal file
548
session/manager_http.go
Normal file
@@ -0,0 +1,548 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package session
|
||||
|
||||
import (
|
||||
"context"
|
||||
stderrors "errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"github.com/ory/kratos/x/nosurfx"
|
||||
"github.com/ory/kratos/x/redir"
|
||||
"github.com/ory/x/logrusx"
|
||||
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/ory/kratos/x/events"
|
||||
|
||||
"github.com/ory/kratos/selfservice/flow"
|
||||
"github.com/ory/kratos/selfservice/sessiontokenexchange"
|
||||
"github.com/ory/kratos/ui/node"
|
||||
"github.com/ory/x/otelx"
|
||||
|
||||
"github.com/ory/x/randx"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
|
||||
"github.com/ory/x/urlx"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/ory/kratos/driver/config"
|
||||
|
||||
"github.com/ory/x/sqlcon"
|
||||
|
||||
"github.com/ory/herodot"
|
||||
|
||||
"github.com/ory/kratos/identity"
|
||||
"github.com/ory/kratos/x"
|
||||
"github.com/ory/kratos/x/transaction"
|
||||
)
|
||||
|
||||
func ErrNoAALAvailable() *herodot.DefaultError {
|
||||
return herodot.ErrForbidden().WithReasonf("Unable to detect available authentication methods. Perform account recovery or contact support.")
|
||||
}
|
||||
|
||||
type (
|
||||
managerHTTPDependencies interface {
|
||||
config.Provider
|
||||
identity.PoolProvider
|
||||
identity.PrivilegedPoolProvider
|
||||
identity.ManagementProvider
|
||||
x.CookieProvider
|
||||
logrusx.Provider
|
||||
nosurfx.CSRFProvider
|
||||
otelx.Provider
|
||||
transaction.PersistenceProvider
|
||||
PersistenceProvider
|
||||
sessiontokenexchange.PersistenceProvider
|
||||
}
|
||||
ManagerHTTP struct {
|
||||
cookieName func(ctx context.Context) string
|
||||
r managerHTTPDependencies
|
||||
}
|
||||
)
|
||||
|
||||
func NewManagerHTTP(r managerHTTPDependencies) *ManagerHTTP {
|
||||
return &ManagerHTTP{
|
||||
r: r,
|
||||
cookieName: func(ctx context.Context) string {
|
||||
return r.Config().SessionName(ctx)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type options struct {
|
||||
requestURL string
|
||||
upsertAAL bool
|
||||
}
|
||||
|
||||
type ManagerOptions func(*options)
|
||||
|
||||
// WithRequestURL passes along query parameters from the requestURL to the new URL (if any exist)
|
||||
func WithRequestURL(requestURL string) ManagerOptions {
|
||||
return func(opts *options) {
|
||||
opts.requestURL = requestURL
|
||||
}
|
||||
}
|
||||
|
||||
// UpsertAAL will update the available AAL of the identity if it was previoulsy unset. This is used to migrate
|
||||
// identities from older versions of Ory Kratos.
|
||||
func UpsertAAL(opts *options) {
|
||||
opts.upsertAAL = true
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) UpsertAndIssueCookie(ctx context.Context, w http.ResponseWriter, r *http.Request, ss *Session) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.UpsertAndIssueCookie")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if err := s.r.SessionPersister().UpsertSession(ctx, ss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := s.IssueCookie(ctx, w, r, ss); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) RefreshCookie(ctx context.Context, w http.ResponseWriter, r *http.Request, session *Session) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.RefreshCookie")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
// If it is a session token there is nothing to do.
|
||||
_, cookieErr := r.Cookie(s.cookieName(r.Context()))
|
||||
if errors.Is(cookieErr, http.ErrNoCookie) {
|
||||
return nil
|
||||
}
|
||||
|
||||
cookie, err := s.getCookie(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
expiresAt := getCookieExpiry(cookie)
|
||||
if expiresAt == nil || expiresAt.Before(session.ExpiresAt) {
|
||||
if err := s.IssueCookie(ctx, w, r, session); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) IssueCookie(ctx context.Context, w http.ResponseWriter, r *http.Request, session *Session) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.IssueCookie")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
cookie, err := s.r.CookieManager(ctx).Get(r, s.cookieName(ctx))
|
||||
// Fix for https://github.com/ory/kratos/issues/1695
|
||||
if err != nil && cookie == nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
if s.r.Config().SessionPath(ctx) != "" {
|
||||
cookie.Options.Path = s.r.Config().SessionPath(ctx)
|
||||
}
|
||||
|
||||
if domain := s.r.Config().SessionDomain(ctx); domain != "" {
|
||||
cookie.Options.Domain = domain
|
||||
}
|
||||
|
||||
if alias := s.r.Config().SelfPublicURL(ctx); s.r.Config().SelfPublicURL(ctx).String() != alias.String() {
|
||||
// If a domain alias is detected use that instead.
|
||||
cookie.Options.Domain = alias.Hostname()
|
||||
cookie.Options.Path = alias.Path
|
||||
}
|
||||
|
||||
// Rotate the anti-CSRF token unless the request was already authenticated
|
||||
// as the same identity: regenerate when there is no prior active session or
|
||||
// when it belongs to a different identity. This decision only needs the
|
||||
// prior session's identity ID, so we deliberately do not expand the
|
||||
// identity. Expanding it here loads the full identity graph, which on a
|
||||
// multi-region deployment can trigger a slow cross-region read (observed at
|
||||
// ~2.4s on the login path) purely to read a single UUID.
|
||||
var priorIdentityID uuid.UUID
|
||||
hasPriorSession := false
|
||||
if token := s.extractToken(r.WithContext(ctx)); token != "" {
|
||||
if prior, ferr := s.r.SessionPersister().GetSessionByToken(ctx, token, ExpandNothing, identity.ExpandNothing); ferr == nil && prior.IsActive() {
|
||||
priorIdentityID = prior.IdentityID
|
||||
hasPriorSession = true
|
||||
}
|
||||
}
|
||||
if !hasPriorSession || priorIdentityID != session.IdentityID {
|
||||
_ = s.r.CSRFHandler().RegenerateToken(w, r)
|
||||
}
|
||||
|
||||
if s.r.Config().SessionSameSiteMode(ctx) != 0 {
|
||||
cookie.Options.SameSite = s.r.Config().SessionSameSiteMode(ctx)
|
||||
}
|
||||
|
||||
cookie.Options.MaxAge = 0
|
||||
if s.r.Config().SessionPersistentCookie(ctx) {
|
||||
if session.ExpiresAt.IsZero() {
|
||||
cookie.Options.MaxAge = int(s.r.Config().OrganizationSessionLifespan(ctx, session.OrganizationID()).Seconds())
|
||||
} else {
|
||||
cookie.Options.MaxAge = int(time.Until(session.ExpiresAt).Seconds())
|
||||
}
|
||||
}
|
||||
|
||||
cookie.Values["session_token"] = session.Token
|
||||
cookie.Values["expires_at"] = session.ExpiresAt.UTC().Format(time.RFC3339Nano)
|
||||
cookie.Values["nonce"] = randx.MustString(8, randx.Alpha) // Guarantee new kratos session identifier
|
||||
|
||||
if err := cookie.Save(r, w); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getCookieExpiry(s *sessions.Session) *time.Time {
|
||||
expiresAt, ok := s.Values["expires_at"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
n, err := time.Parse(time.RFC3339Nano, expiresAt)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return &n
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) getCookie(r *http.Request) (*sessions.Session, error) {
|
||||
return s.r.CookieManager(r.Context()).Get(r, s.cookieName(r.Context()))
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) extractToken(r *http.Request) string {
|
||||
ctx, span := s.r.Tracer(r.Context()).Tracer().Start(r.Context(), "sessions.ManagerHTTP.extractToken")
|
||||
defer span.End()
|
||||
|
||||
if token := r.Header.Get("X-Session-Token"); len(token) > 0 {
|
||||
return token
|
||||
}
|
||||
|
||||
cookie, err := s.getCookie(r.WithContext(ctx))
|
||||
if err != nil {
|
||||
token, _ := bearerTokenFromRequest(r.WithContext(ctx))
|
||||
return token
|
||||
}
|
||||
|
||||
token, ok := cookie.Values["session_token"].(string)
|
||||
if ok {
|
||||
return token
|
||||
}
|
||||
|
||||
token, _ = bearerTokenFromRequest(r.WithContext(ctx))
|
||||
return token
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) FetchFromRequestContext(ctx context.Context, r *http.Request) (_ *Session, err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.FetchFromRequestContext")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if sess, ok := ctx.Value(sessionInContextKey).(*Session); ok {
|
||||
return sess, nil
|
||||
}
|
||||
|
||||
// Don't reduce the expansion here unless you want bad performance down the line (because we
|
||||
// constantly are unsure if we have the full data fetched or not).
|
||||
return s.FetchFromRequest(ctx, r, ExpandEverything, identity.ExpandEverything)
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) FetchFromRequest(ctx context.Context, r *http.Request, sessionExpand Expandables, identityExpand identity.Expandables) (_ *Session, err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.FetchFromRequest",
|
||||
trace.WithAttributes(
|
||||
attribute.StringSlice("session.expand", sessionExpand.ToEager()),
|
||||
attribute.StringSlice("identity.expand", identityExpand.ToEager()),
|
||||
))
|
||||
defer endSpanIgnoreNoActiveSession(span, &err)
|
||||
|
||||
token := s.extractToken(r.WithContext(ctx))
|
||||
if token == "" {
|
||||
return nil, errors.WithStack(NewErrNoCredentialsForSession())
|
||||
}
|
||||
|
||||
se, err := s.r.SessionPersister().GetSessionByToken(ctx, token, sessionExpand, identityExpand)
|
||||
if err != nil {
|
||||
if errors.Is(err, herodot.ErrNotFound()) || errors.Is(err, sqlcon.ErrNoRows()) {
|
||||
return nil, errors.WithStack(NewErrNoActiveSessionFound())
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
trace.SpanFromContext(ctx).AddEvent(events.NewSessionChecked(ctx, se.ID, se.IdentityID))
|
||||
|
||||
if !se.IsActive() {
|
||||
return nil, errors.WithStack(NewErrNoActiveSessionFound())
|
||||
}
|
||||
|
||||
return se, nil
|
||||
}
|
||||
|
||||
// endSpanIgnoreNoActiveSession ends the span without recording ErrNoActiveSessionFound as a span
|
||||
// error, because a request without an active session is an expected condition on these paths.
|
||||
func endSpanIgnoreNoActiveSession(span trace.Span, err *error) {
|
||||
if _, ok := stderrors.AsType[*ErrNoActiveSessionFound](*err); ok {
|
||||
span.End()
|
||||
} else {
|
||||
otelx.End(span, err)
|
||||
}
|
||||
}
|
||||
|
||||
// SessionActiveForRequest reports whether the request carries a token for an
|
||||
// active session, without loading the identity. It returns nil when an active
|
||||
// session exists and the same errors as FetchFromRequest otherwise, so callers
|
||||
// that only branch on the error keep identical behavior while skipping the
|
||||
// cross-region identity load.
|
||||
func (s *ManagerHTTP) SessionActiveForRequest(ctx context.Context, r *http.Request) error {
|
||||
_, err := s.FetchFromRequest(ctx, r, ExpandNothing, identity.ExpandNothing)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) PurgeFromRequest(ctx context.Context, w http.ResponseWriter, r *http.Request) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.PurgeFromRequest")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if token, ok := bearerTokenFromRequest(r); ok {
|
||||
_, err := s.r.SessionPersister().RevokeSessionByToken(ctx, token)
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
cookie, _ := s.r.CookieManager(r.Context()).Get(r, s.cookieName(ctx))
|
||||
token, ok := cookie.Values["session_token"].(string)
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := s.r.SessionPersister().RevokeSessionByToken(ctx, token); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
cookie.Options.MaxAge = -1
|
||||
if err := cookie.Save(r, w); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) DoesSessionSatisfy(ctx context.Context, sess *Session, requestedAAL string, opts ...ManagerOptions) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.DoesSessionSatisfy")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
sess.SetAuthenticatorAssuranceLevel()
|
||||
|
||||
// If we already have AAL2 there is no need to check further because it is the highest AAL.
|
||||
if sess.AuthenticatorAssuranceLevel == identity.AuthenticatorAssuranceLevel2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
managerOpts := &options{}
|
||||
for _, o := range opts {
|
||||
o(managerOpts)
|
||||
}
|
||||
|
||||
loginURL := urlx.AppendPaths(s.r.Config().SelfPublicURL(ctx), "/self-service/login/browser")
|
||||
query := url.Values{
|
||||
"aal": {"aal2"},
|
||||
}
|
||||
|
||||
// return to the requestURL if it was set
|
||||
if managerOpts.requestURL != "" {
|
||||
query.Set("return_to", managerOpts.requestURL)
|
||||
}
|
||||
|
||||
loginURL.RawQuery = query.Encode()
|
||||
|
||||
switch requestedAAL {
|
||||
case string(identity.AuthenticatorAssuranceLevel1):
|
||||
if sess.AuthenticatorAssuranceLevel >= identity.AuthenticatorAssuranceLevel1 {
|
||||
return nil
|
||||
}
|
||||
return NewErrAALNotSatisfied(loginURL.String())
|
||||
case config.HighestAvailableAAL:
|
||||
if sess.AuthenticatorAssuranceLevel == identity.AuthenticatorAssuranceLevel2 {
|
||||
// The session has AAL2, nothing to check.
|
||||
return nil
|
||||
}
|
||||
|
||||
// The session is AAL1, we asked for `highest_available` AAL, so the only thing we can do
|
||||
// is actually check what authentication methods the identity has.
|
||||
if sess.Identity == nil {
|
||||
// This is nil if the session did not expand the identity field.
|
||||
sess.Identity, err = s.r.IdentityPool().GetIdentity(ctx, sess.IdentityID, identity.ExpandNothing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if aal, ok := sess.Identity.InternalAvailableAAL.ToAAL(); ok && aal == identity.AuthenticatorAssuranceLevel2 {
|
||||
// Identity gives us AAL2, but the session is still AAL1. We need to upgrade the session.
|
||||
return NewErrAALNotSatisfied(loginURL.String())
|
||||
}
|
||||
|
||||
// Identity AAL is not 2, we refresh:
|
||||
|
||||
// The identity was apparently fetched without credentials. Let's hydrate them.
|
||||
if len(sess.Identity.Credentials) == 0 {
|
||||
if err := s.r.PrivilegedIdentityPool().HydrateIdentityAssociations(ctx, sess.Identity, identity.ExpandCredentials); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Great, now we determine the identity's available AAL
|
||||
if err := sess.Identity.SetAvailableAAL(ctx, s.r.IdentityManager()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// We override the result with our newly computed values
|
||||
available, valid := sess.Identity.InternalAvailableAAL.ToAAL()
|
||||
if !valid {
|
||||
// Unlikely to happen because SetAvailableAAL will either return an error, or a valid value - but not no error and an invalid value.
|
||||
return errors.WithStack(x.PseudoPanic.WithReasonf("Unable to determine available authentication methods for session: %s", sess.ID))
|
||||
}
|
||||
|
||||
switch available {
|
||||
case identity.NoAuthenticatorAssuranceLevel:
|
||||
// The identity has AAL0, the session has AAL1, we're good.
|
||||
return nil
|
||||
case identity.AuthenticatorAssuranceLevel1:
|
||||
// The identity has AAL1, the session has AAL1, we're good.
|
||||
return nil
|
||||
case identity.AuthenticatorAssuranceLevel2:
|
||||
// The identity has AAL2, the session has AAL1, we need to upgrade the session.
|
||||
|
||||
// Since we ended up here, it also means that `sess.Identity.InternalAvailableAAL` was `aal1` and is now `aal2`.
|
||||
// Let's update the database.
|
||||
if managerOpts.upsertAAL {
|
||||
if err := s.r.PrivilegedIdentityPool().UpdateIdentityColumns(ctx, sess.Identity, "available_aal"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NewErrAALNotSatisfied(loginURL.String())
|
||||
}
|
||||
|
||||
return errors.Errorf("requested unknown aal: %s", requestedAAL)
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) SessionAddAuthenticationMethods(ctx context.Context, sid uuid.UUID, ams ...AuthenticationMethod) (err error) {
|
||||
ctx, span := s.r.Tracer(ctx).Tracer().Start(ctx, "sessions.ManagerHTTP.SessionAddAuthenticationMethods")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
// Since we added the method, it also means that we have authenticated it
|
||||
sess, err := s.r.SessionPersister().GetSession(ctx, sid, ExpandNothing)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, m := range ams {
|
||||
sess.CompletedLoginForMethod(m)
|
||||
}
|
||||
// Completing an authentication method is an authentication event, so the
|
||||
// session's authenticated_at is refreshed as well. This matches login
|
||||
// flows, which set authenticated_at when a second factor completes.
|
||||
sess.AuthenticatedAt = time.Now().UTC()
|
||||
sess.SetAuthenticatorAssuranceLevel()
|
||||
return s.r.SessionPersister().UpsertSession(ctx, sess)
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) MaybeRedirectAPICodeFlow(w http.ResponseWriter, r *http.Request, f flow.Flow, sessionID uuid.UUID, uiNode node.UiNodeGroup) (handled bool, err error) {
|
||||
ctx, span := s.r.Tracer(r.Context()).Tracer().Start(r.Context(), "sessions.ManagerHTTP.MaybeRedirectAPICodeFlow")
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if uiNode != node.OpenIDConnectGroup {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
code, ok, _ := s.r.SessionTokenExchangePersister().CodeForFlow(ctx, f.GetID())
|
||||
if !ok {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
returnTo := s.r.Config().SelfServiceBrowserDefaultReturnTo(ctx)
|
||||
if redirecter, ok := f.(flow.FlowWithRedirect); ok {
|
||||
r, err := redir.SecureRedirectTo(r, returnTo, redirecter.SecureRedirectToOpts(ctx, s.r)...)
|
||||
if err == nil {
|
||||
returnTo = r
|
||||
}
|
||||
}
|
||||
|
||||
if err = s.r.SessionTokenExchangePersister().UpdateSessionOnExchanger(ctx, f.GetID(), sessionID); err != nil {
|
||||
return false, errors.WithStack(err)
|
||||
}
|
||||
|
||||
q := returnTo.Query()
|
||||
q.Set("code", code.ReturnToCode)
|
||||
returnTo.RawQuery = q.Encode()
|
||||
http.Redirect(w, r, returnTo.String(), http.StatusSeeOther)
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) ActivateSession(r *http.Request, session *Session, i *identity.Identity, authenticatedAt time.Time) (err error) {
|
||||
ctx, span := s.r.Tracer(r.Context()).Tracer().Start(r.Context(), "sessions.ManagerHTTP.ActivateSession", trace.WithAttributes(
|
||||
attribute.String("session.id", session.ID.String()),
|
||||
attribute.String("identity.id", session.ID.String()),
|
||||
attribute.String("authenticated_at", session.ID.String()),
|
||||
))
|
||||
defer otelx.End(span, &err)
|
||||
|
||||
if i == nil {
|
||||
return errors.WithStack(x.PseudoPanic.WithReasonf("Identity must not be nil when activating a session."))
|
||||
}
|
||||
|
||||
if !i.IsActive() {
|
||||
return errors.WithStack(ErrIdentityDisabled().WithDetail("identity_id", i.ID))
|
||||
}
|
||||
|
||||
if err := s.r.IdentityManager().RefreshAvailableAAL(ctx, i); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
session.Identity = i
|
||||
session.IdentityID = i.ID
|
||||
|
||||
session.Active = true
|
||||
session.IssuedAt = authenticatedAt
|
||||
session.ExpiresAt = authenticatedAt.Add(s.r.Config().OrganizationSessionLifespan(ctx, session.OrganizationID()))
|
||||
session.AuthenticatedAt = authenticatedAt
|
||||
|
||||
session.SetSessionDeviceInformation(r.WithContext(ctx))
|
||||
session.SetAuthenticatorAssuranceLevel()
|
||||
|
||||
span.SetAttributes(
|
||||
attribute.String("identity.available_aal", session.Identity.InternalAvailableAAL.String),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ManagerHTTP) IsPrivileged(ctx context.Context, session *Session) bool {
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if session.Identity == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if session.AuthenticatedAt.IsZero() {
|
||||
return false
|
||||
}
|
||||
|
||||
privilegedSessionLifespan := s.r.Config().SelfServiceFlowSettingsPrivilegedSessionMaxAge(ctx)
|
||||
if privilegedSessionLifespan <= 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
return session.AuthenticatedAt.Add(privilegedSessionLifespan).After(time.Now())
|
||||
}
|
||||
Reference in New Issue
Block a user