// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package vault

import (
	"context"
	"crypto/hmac"
	"encoding/base64"
	"errors"
	"fmt"
	"maps"
	"net/textproto"
	"os"
	paths "path"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/armon/go-metrics"
	"github.com/golang/protobuf/proto"
	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/go-multierror"
	"github.com/hashicorp/go-secure-stdlib/strutil"
	"github.com/hashicorp/go-sockaddr"
	"github.com/hashicorp/go-uuid"
	"github.com/hashicorp/vault/command/server"
	"github.com/hashicorp/vault/helper/constants"
	"github.com/hashicorp/vault/helper/identity"
	"github.com/hashicorp/vault/helper/identity/mfa"
	"github.com/hashicorp/vault/helper/metricsutil"
	"github.com/hashicorp/vault/helper/namespace"
	"github.com/hashicorp/vault/http/priority"
	"github.com/hashicorp/vault/internalshared/configutil"
	"github.com/hashicorp/vault/sdk/framework"
	"github.com/hashicorp/vault/sdk/helper/consts"
	"github.com/hashicorp/vault/sdk/helper/errutil"
	"github.com/hashicorp/vault/sdk/helper/jsonutil"
	"github.com/hashicorp/vault/sdk/helper/policyutil"
	"github.com/hashicorp/vault/sdk/helper/wrapping"
	"github.com/hashicorp/vault/sdk/logical"
	"github.com/hashicorp/vault/vault/quotas"
	"github.com/hashicorp/vault/vault/tokens"
	uberAtomic "go.uber.org/atomic"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

const (
	replTimeout                           = 1 * time.Second
	EnvVaultDisableLocalAuthMountEntities = "VAULT_DISABLE_LOCAL_AUTH_MOUNT_ENTITIES"
	// base path to store locked users
	coreLockedUsersPath = "core/login/lockedUsers/"
)

var (
	// DefaultMaxRequestDuration is the amount of time we'll wait for a request
	// to complete, unless overridden on a per-handler basis
	DefaultMaxRequestDuration = 90 * time.Second

	ErrNoApplicablePolicies    = errors.New("no applicable policies")
	ErrPolicyNotExistInTypeMap = errors.New("policy does not exist in type map")

	egpDebugLogging bool
)

// HandlerProperties is used to seed configuration into a vaulthttp.Handler.
// It's in this package to avoid a circular dependency
type HandlerProperties struct {
	Core                  *Core
	ListenerConfig        *configutil.Listener
	DisablePrintableCheck bool
	RecoveryMode          bool
	RecoveryToken         *uberAtomic.String

	// RequestIDGenerator is primary used for testing purposes to allow tests to
	// control the request IDs deterministically. In production code (i.e. if this
	// is nil) the handler will generate UUIDs.
	RequestIDGenerator func() (string, error)
}

// fetchEntityAndDerivedPolicies returns the entity object for the given entity
// ID. If the entity is merged into a different entity object, the entity into
// which the given entity ID is merged into will be returned. This function
// also returns the cumulative list of policies that the entity is entitled to
// if skipDeriveEntityPolicies is set to false. This list includes the policies from the
// entity itself and from all the groups in which the given entity ID is a member of.
func (c *Core) fetchEntityAndDerivedPolicies(ctx context.Context, tokenNS *namespace.Namespace, entityID string, skipDeriveEntityPolicies bool) (*identity.Entity, map[string][]string, error) {
	if entityID == "" || c.identityStore == nil {
		return nil, nil, nil
	}

	entity, err := c.fetchEntity(entityID, false)
	if entity == nil && err == nil {
		return nil, nil, nil
	}
	if err != nil {
		return nil, nil, err
	}

	policies := make(map[string][]string)
	if !skipDeriveEntityPolicies {
		// c.logger.Debug("entity successfully fetched; adding entity policies to token's policies to create ACL")

		// Attach the policies on the entity
		if len(entity.Policies) != 0 {
			policies[entity.NamespaceID] = append(policies[entity.NamespaceID], entity.Policies...)
		}

		groupPolicies, err := c.identityStore.groupPoliciesByEntityID(entity.ID)
		if err != nil {
			c.logger.Error("failed to fetch group policies", "error", err)
			return nil, nil, err
		}

		policiesByNS, err := c.filterGroupPoliciesByNS(ctx, tokenNS, groupPolicies)
		if err != nil {
			return nil, nil, err
		}
		for nsID, pss := range policiesByNS {
			policies[nsID] = append(policies[nsID], pss...)
		}
	}

	return entity, policies, err
}

// fetchEntity returns the entity object for the given entity
// ID. If the entity is merged into a different entity object, the entity into
// which the given entity ID is merged into will be returned.
func (c *Core) fetchEntity(entityID string, clone bool) (*identity.Entity, error) {
	if entityID == "" || c.identityStore == nil {
		return nil, nil
	}
	txn := c.identityStore.db.Txn(false)

	return c.identityStore.fetchEntityInTxn(txn, entityID, clone)
}

// filterGroupPoliciesByNS takes a context, token namespace, and a map of
// namespace IDs to slices of group policy names and returns a similar map,
// but filtered down to the policies that should apply to the token based on the
// relationship between the namespace of the token and the namespace of the
// policy.
func (c *Core) filterGroupPoliciesByNS(ctx context.Context, tokenNS *namespace.Namespace, groupPolicies map[string][]string) (map[string][]string, error) {
	policies := make(map[string][]string)

	policyApplicationMode, err := c.GetGroupPolicyApplicationMode(ctx)
	if err != nil {
		return nil, err
	}

	for nsID, nsPolicies := range groupPolicies {
		filteredPolicies, err := c.getApplicableGroupPolicies(ctx, tokenNS, nsID, nsPolicies, policyApplicationMode)
		if err != nil && err != ErrNoApplicablePolicies {
			return nil, err
		}
		filteredPolicies = strutil.RemoveDuplicates(filteredPolicies, false)
		if len(filteredPolicies) != 0 {
			policies[nsID] = append(policies[nsID], filteredPolicies...)
		}
	}

	return policies, nil
}

// getApplicableGroupPolicies returns a slice of group policies that should
// apply to the token based on the group policy application mode,
// and the relationship between the token namespace and the group namespace.
func (c *Core) getApplicableGroupPolicies(ctx context.Context, tokenNS *namespace.Namespace, nsID string, nsPolicies []string, policyApplicationMode string) ([]string, error) {
	policyNS, err := NamespaceByID(ctx, nsID, c)
	if err != nil {
		return nil, err
	}
	if policyNS == nil {
		return nil, namespace.ErrNoNamespace
	}

	var filteredPolicies []string

	if tokenNS.Path == policyNS.Path {
		// Same namespace - add all and continue
		for _, policyName := range nsPolicies {
			filteredPolicies = append(filteredPolicies, policyName)
		}
		return filteredPolicies, nil
	}

	for _, policyName := range nsPolicies {
		t, err := c.policyStore.GetNonEGPPolicyType(policyNS.ID, policyName)
		if err != nil && errors.Is(err, ErrPolicyNotExistInTypeMap) {
			// When we attempt to get a non-EGP policy type, and receive an
			// explicit error that it doesn't exist (in the type map) we log the
			// ns/policy and continue without error.
			c.Logger().Debug(fmt.Errorf("%w: %v/%v", err, policyNS.ID, policyName).Error())
			continue
		}
		if err != nil || t == nil {
			return nil, fmt.Errorf("failed to look up type of policy: %w", err)
		}

		switch *t {
		case PolicyTypeRGP:
			if tokenNS.HasParent(policyNS) {
				filteredPolicies = append(filteredPolicies, policyName)
			}
		case PolicyTypeACL:
			if policyApplicationMode != groupPolicyApplicationModeWithinNamespaceHierarchy {
				// Group policy application mode isn't set to enforce
				// the namespace hierarchy, so apply all the ACLs,
				// regardless of their namespaces.
				filteredPolicies = append(filteredPolicies, policyName)
				continue
			}
			if policyNS.HasParent(tokenNS) {
				filteredPolicies = append(filteredPolicies, policyName)
			}
		default:
			return nil, fmt.Errorf("unexpected policy type: %v", t)
		}
	}
	if len(filteredPolicies) == 0 {
		return nil, ErrNoApplicablePolicies
	}
	return filteredPolicies, nil
}

func (c *Core) fetchACLTokenEntryAndEntity(ctx context.Context, req *logical.Request) (*ACL, *logical.TokenEntry, *identity.Entity, map[string][]string, error) {
	defer metrics.MeasureSince([]string{"core", "fetch_acl_and_token"}, time.Now())

	// Ensure there is a client token
	if req.ClientToken == "" {
		return nil, nil, nil, nil, logical.ErrPermissionDenied
	}

	if c.tokenStore == nil {
		c.logger.Error("token store is unavailable")
		return nil, nil, nil, nil, ErrInternalError
	}

	// Resolve the token policy
	var te *logical.TokenEntry
	switch req.TokenEntry() {
	case nil:
		var err error
		te, err = c.tokenStore.Lookup(ctx, req.ClientToken)
		if err != nil {
			c.logger.Error("failed to lookup acl token", "error", err)
			return nil, nil, nil, nil, ErrInternalError
		}
		// Set the token entry here since it has not been cached yet
		req.SetTokenEntry(te)
	default:
		te = req.TokenEntry()
	}

	// Ensure the token is valid
	if te == nil {
		return nil, nil, nil, nil, multierror.Append(logical.ErrPermissionDenied, logical.ErrInvalidToken)
	}

	// CIDR checks bind all tokens except non-expiring root tokens
	if te.TTL != 0 && len(te.BoundCIDRs) > 0 {
		var valid bool
		remoteSockAddr, err := sockaddr.NewSockAddr(req.Connection.RemoteAddr)
		if err != nil {
			if c.Logger().IsDebug() {
				c.Logger().Debug("could not parse remote addr into sockaddr", "error", err, "remote_addr", req.Connection.RemoteAddr)
			}
			return nil, nil, nil, nil, logical.ErrPermissionDenied
		}
		for _, cidr := range te.BoundCIDRs {
			if cidr.Contains(remoteSockAddr) {
				valid = true
				break
			}
		}
		if !valid {
			return nil, nil, nil, nil, logical.ErrPermissionDenied
		}
	}

	policyNames := make(map[string][]string)
	// Add tokens policies
	policyNames[te.NamespaceID] = append(policyNames[te.NamespaceID], te.Policies...)

	tokenNS, err := NamespaceByID(ctx, te.NamespaceID, c)
	if err != nil {
		c.logger.Error("failed to fetch token namespace", "error", err)
		return nil, nil, nil, nil, ErrInternalError
	}
	if tokenNS == nil {
		c.logger.Error("failed to fetch token namespace", "error", namespace.ErrNoNamespace)
		return nil, nil, nil, nil, ErrInternalError
	}

	// Add identity policies from all the namespaces
	entity, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, te.EntityID, te.NoIdentityPolicies)
	if err != nil {
		return nil, nil, nil, nil, ErrInternalError
	}
	for nsID, nsPolicies := range identityPolicies {
		policyNames[nsID] = policyutil.SanitizePolicies(append(policyNames[nsID], nsPolicies...), false)
	}

	// Attach token's namespace information to the context. Wrapping tokens by
	// should be able to be used anywhere, so we also special case behavior.
	var tokenCtx context.Context
	if len(policyNames) == 1 &&
		len(policyNames[te.NamespaceID]) == 1 &&
		(policyNames[te.NamespaceID][0] == responseWrappingPolicyName ||
			policyNames[te.NamespaceID][0] == controlGroupPolicyName) &&
		(strings.HasSuffix(req.Path, "sys/wrapping/unwrap") ||
			strings.HasSuffix(req.Path, "sys/wrapping/lookup") ||
			strings.HasSuffix(req.Path, "sys/wrapping/rewrap")) {
		// Use the request namespace; will find the copy of the policy for the
		// local namespace
		tokenCtx = ctx
	} else {
		// Use the token's namespace for looking up policy
		tokenCtx = namespace.ContextWithNamespace(ctx, tokenNS)
	}

	// Add the inline policy if it's set
	policies := make([]*Policy, 0)
	if te.InlinePolicy != "" {
		// TODO (HCL_DUP_KEYS_DEPRECATION): return to ParseACLPolicy once the deprecation is done
		inlinePolicy, duplicate, err := ParseACLPolicyCheckDuplicates(tokenNS, te.InlinePolicy)
		if err != nil {
			return nil, nil, nil, nil, ErrInternalError
		}
		if duplicate {
			c.logger.Warn("HCL inline policy contains duplicate attributes, which will no longer be supported in a future version", "namespace", tokenNS.Path)
		}
		policies = append(policies, inlinePolicy)
	}

	// Construct the corresponding ACL object. ACL construction should be
	// performed on the token's namespace.
	acl, err := c.policyStore.ACL(tokenCtx, entity, policyNames, policies...)
	if err != nil {
		c.logger.Error("failed to construct ACL", "error", err)
		return nil, nil, nil, nil, ErrInternalError
	}

	return acl, te, entity, identityPolicies, nil
}

// CheckTokenWithLock calls CheckToken after grabbing the internal stateLock,
// and also checking that we aren't in the process of shutting down.
func (c *Core) CheckTokenWithLock(ctx context.Context, req *logical.Request) (*logical.Auth, *logical.TokenEntry, error) {
	c.stateLock.RLock()
	defer c.stateLock.RUnlock()
	// first check that we aren't shutting down
	if c.Sealed() {
		return nil, nil, errors.New("core is sealed")
	} else if c.activeContext != nil && c.activeContext.Err() != nil {
		return nil, nil, c.activeContext.Err()
	}
	return c.CheckToken(ctx, req, false)
}

func (c *Core) existenceCheck(ctx context.Context, req *logical.Request) (*logical.Operation, error) {
	existsResp, checkExists, resourceExists, err := c.router.RouteExistenceCheck(ctx, req)
	switch err {
	case logical.ErrUnsupportedPath:
		// fail later via bad path to avoid confusing items in the log
		checkExists = false
	case logical.ErrRelativePath:
		return nil, errutil.UserError{Err: err.Error()}
	case nil:
		if existsResp != nil && existsResp.IsError() {
			return nil, existsResp.Error()
		}
		// Otherwise, continue on
	default:
		c.logger.Error("failed to run existence check", "error", err)
		if _, ok := err.(errutil.UserError); ok {
			return nil, err
		} else {
			return nil, ErrInternalError
		}
	}
	var existenceCheckOp logical.Operation
	switch {
	case !checkExists:
		// No existence check, so always treat it as an update operation, which is how it is pre 0.5
		existenceCheckOp = logical.UpdateOperation
	case resourceExists:
		// It exists, so force an update operation
		existenceCheckOp = logical.UpdateOperation
	case !resourceExists:
		// It doesn't exist, force a create operation
		existenceCheckOp = logical.CreateOperation
	default:
		panic("unreachable code")
	}
	return &existenceCheckOp, nil
}

// CheckToken returns information about the state of authentication for a request.
// The unauth flag should be set true for "login" requests; these are requests to
// logical.Paths.Unauthenticated endpoints that are not deleted auth requests,
// though note that despite being called login requests, not all of them are actually
// login requests, as in attempts to authenticate and get back a token.
//
// Generally speaking, if CheckToken return an error, the request should fail,
// but there are exceptions, e.g. ErrPerfStandbyPleaseForward may just result in
// the request being forwarded to the active node.  A returned error does not
// necessarily mean that the other return values will be nil; often they are
// useful e.g. for auditing failing requests.
//
// When unauth is true, fail root path requests, don't worry about regular
// ACL policy checks (though sentinel checks may still apply), and don't do
// client counting.  When unauth is false, an invalid token or a lack of ACL
// perms will result in an error, and client counting applies.
func (c *Core) CheckToken(ctx context.Context, req *logical.Request, unauth bool) (*logical.Auth, *logical.TokenEntry, error) {
	defer metrics.MeasureSince([]string{"core", "check_token"}, time.Now())

	var acl *ACL
	var te *logical.TokenEntry
	var entity *identity.Entity
	var identityPolicies map[string][]string

	// Even if unauth, if a token is provided, there's little reason not to
	// gather as much info as possible for the audit log and to e.g. control
	// trace mode for EGPs.
	if !unauth || (unauth && req.ClientToken != "") {
		var err error
		acl, te, entity, identityPolicies, err = c.fetchACLTokenEntryAndEntity(ctx, req)
		// In the unauth case we don't want to fail the command, since it's
		// unauth, we just have no information to attach to the request, so
		// ignore errors...this was best-effort anyways
		if err != nil && !unauth {
			return nil, te, err
		}
	}

	if entity != nil && entity.Disabled {
		c.logger.Warn("permission denied as the entity on the token is disabled")
		return nil, te, logical.ErrPermissionDenied
	}
	if te != nil && te.EntityID != "" && entity == nil {
		if c.perfStandby {
			return nil, nil, logical.ErrPerfStandbyPleaseForward
		}
		c.logger.Warn("permission denied as the entity on the token is invalid")
		return nil, te, logical.ErrPermissionDenied
	}

	// Check if this is a root protected path
	rootPath := c.router.RootPath(ctx, req.Path)

	if rootPath && unauth {
		return nil, nil, errors.New("cannot access root path in unauthenticated request")
	}

	// At this point we won't be forwarding a raw request; we should delete
	// authorization headers as appropriate.  Don't delete Authorization headers
	// if this is an unauth (login) request, as that precludes things like spiffe
	// auth handling login requests with the payload in the Authorization header.
	// However, if we did find a valid vault token in the header, we'll still
	// suppress the header even if unauth is true, on the principle that we
	// don't want to expose vault tokens to plugins which might do something
	// nefarious with them.
	switch {
	case req.ClientTokenSource == logical.ClientTokenFromVaultHeader:
		delete(req.Headers, consts.AuthHeaderName)
	case req.ClientTokenSource == logical.ClientTokenFromAuthzHeader && !unauth && te == nil:
		if headers, ok := req.Headers["Authorization"]; ok {
			retHeaders := make([]string, 0, len(headers))
			for _, v := range headers {
				if strings.HasPrefix(v, "Bearer ") {
					continue
				}
				retHeaders = append(retHeaders, v)
			}
			req.Headers["Authorization"] = retHeaders
		}
	}

	// When we receive a write of either type, rather than require clients to
	// PUT/POST and trust the operation, we ask the backend to give us the real
	// skinny -- if the backend implements an existence check, it can tell us
	// whether a particular resource exists. Then we can mark it as an update
	// or creation as appropriate.
	if req.Operation == logical.CreateOperation || req.Operation == logical.UpdateOperation {
		op, err := c.existenceCheck(ctx, req)
		if err != nil {
			return nil, te, err
		}
		req.Operation = *op
	}

	// Create the auth response
	auth := &logical.Auth{
		ClientToken: req.ClientToken,
		Accessor:    req.ClientTokenAccessor,
	}

	var clientID string
	var isTWE bool
	if te != nil {
		auth.IdentityPolicies = identityPolicies[te.NamespaceID]
		auth.TokenPolicies = te.Policies
		auth.Policies = policyutil.SanitizePolicies(append(te.Policies, identityPolicies[te.NamespaceID]...), false)
		auth.Metadata = te.Meta
		auth.DisplayName = te.DisplayName
		auth.EntityID = te.EntityID
		delete(identityPolicies, te.NamespaceID)
		auth.ExternalNamespacePolicies = identityPolicies
		// Store the entity ID in the request object
		req.EntityID = te.EntityID
		auth.TokenType = te.Type
		auth.TTL = te.TTL
		if te.CreationTime > 0 {
			auth.IssueTime = time.Unix(te.CreationTime, 0)
		}
		clientID, isTWE = te.CreateClientID()
		req.ClientID = clientID
	}

	twoStepRecover := req.Operation == logical.RecoverOperation && req.RecoverSourcePath != "" && req.RecoverSourcePath != req.Path
	var alternateRecoverCapability *logical.Operation
	if twoStepRecover {
		// An existence check call requires the operation to be set to either
		// create or update. We set it to create here, then switch it back once
		// the existence check is done.
		req.Operation = logical.CreateOperation
		op, err := c.existenceCheck(ctx, req)
		req.Operation = logical.RecoverOperation
		if err != nil {
			return nil, te, err
		}
		alternateRecoverCapability = op
	}

	// Check the standard non-root ACLs. Return the token entry if it's not
	// allowed so we can decrement the use count.
	authResults := c.performPolicyChecks(ctx, acl, te, req, entity, &PolicyCheckOpts{
		Unauth:                     unauth,
		RootPrivsRequired:          rootPath,
		CheckSourcePath:            twoStepRecover,
		RecoverAlternateCapability: alternateRecoverCapability,
	})

	// Assign the sudo path priority if the request is issued against a sudo path.
	if rootPath {
		pri := uint8(priority.NeverDrop)
		auth.HTTPRequestPriority = &pri
	}

	auth.PolicyResults = &logical.PolicyResults{
		Allowed: authResults.Allowed,
	}

	if !authResults.Allowed {
		retErr := authResults.Error

		// If we get a control group error and we are a performance standby,
		// restore the client token information to the request so that we can
		// forward this request properly to the active node.
		if retErr.ErrorOrNil() != nil && checkErrControlGroupTokenNeedsCreated(retErr) &&
			c.perfStandby && len(req.ClientToken) != 0 {
			switch req.ClientTokenSource {
			case logical.ClientTokenFromVaultHeader:
				req.Headers[consts.AuthHeaderName] = []string{req.ClientToken}
			case logical.ClientTokenFromAuthzHeader:
				req.Headers["Authorization"] = append(req.Headers["Authorization"], fmt.Sprintf("Bearer %s", req.ClientToken))
			}
			// We also return the appropriate error so that the caller can forward the
			// request to the active node
			return auth, te, logical.ErrPerfStandbyPleaseForward
		}

		if authResults.Error.ErrorOrNil() == nil || authResults.DeniedError {
			retErr = multierror.Append(retErr, logical.ErrPermissionDenied)
		}
		return auth, te, retErr
	}

	if authResults.ACLResults != nil && len(authResults.ACLResults.GrantingPolicies) > 0 {
		auth.PolicyResults.GrantingPolicies = authResults.ACLResults.GrantingPolicies
	}
	if authResults.SentinelResults != nil && len(authResults.SentinelResults.GrantingPolicies) > 0 {
		auth.PolicyResults.GrantingPolicies = append(auth.PolicyResults.GrantingPolicies, authResults.SentinelResults.GrantingPolicies...)
	}

	c.activityLogLock.RLock()
	activityLog := c.activityLog
	c.activityLogLock.RUnlock()
	// If it is an authenticated ( i.e. with vault token ) request, increment client count
	if !unauth && activityLog != nil {
		err := activityLog.HandleTokenUsage(ctx, te, clientID, isTWE)
		if err != nil {
			return auth, te, err
		}
	}
	return auth, te, nil
}

// HandleRequest is used to handle a new incoming request
func (c *Core) HandleRequest(httpCtx context.Context, req *logical.Request) (resp *logical.Response, err error) {
	return c.switchedLockHandleRequest(httpCtx, req, true)
}

func (c *Core) switchedLockHandleRequest(httpCtx context.Context, req *logical.Request, doLocking bool) (resp *logical.Response, err error) {
	if doLocking {
		c.stateLock.RLock()
		defer c.stateLock.RUnlock()
	}
	if c.Sealed() {
		return nil, consts.ErrSealed
	}
	if c.standby && !c.perfStandby {
		return nil, consts.ErrStandby
	}

	if c.activeContext == nil || c.activeContext.Err() != nil {
		return nil, errors.New("active context canceled after getting state lock")
	}

	ctx, cancel := context.WithCancel(c.activeContext)
	go func(ctx context.Context, httpCtx context.Context) {
		select {
		case <-ctx.Done():
		case <-httpCtx.Done():
			cancel()
		}
	}(ctx, httpCtx)

	ns, err := namespace.FromContext(httpCtx)
	if err != nil {
		cancel()
		return nil, fmt.Errorf("could not parse namespace from http context: %w", err)
	}

	ctx = namespace.ContextWithNamespace(ctx, ns)
	inFlightReqID, ok := httpCtx.Value(logical.CtxKeyInFlightRequestID{}).(string)
	if ok {
		ctx = context.WithValue(ctx, logical.CtxKeyInFlightRequestID{}, inFlightReqID)
	}
	requestRole, ok := httpCtx.Value(logical.CtxKeyRequestRole{}).(string)
	if ok {
		ctx = context.WithValue(ctx, logical.CtxKeyRequestRole{}, requestRole)
	}
	if disable_repl_status, ok := logical.ContextDisableReplicationStatusEndpointsValue(httpCtx); ok {
		ctx = logical.CreateContextDisableReplicationStatusEndpoints(ctx, disable_repl_status)
	}
	body, ok := logical.ContextOriginalBodyValue(httpCtx)
	if ok {
		ctx = logical.CreateContextOriginalBody(ctx, body)
	}
	redactVersion, redactAddresses, redactClusterName, ok := logical.CtxRedactionSettingsValue(httpCtx)
	if ok {
		ctx = logical.CreateContextRedactionSettings(ctx, redactVersion, redactAddresses, redactClusterName)
	}
	inFlightRequestPriority, ok := httpCtx.Value(logical.CtxKeyInFlightRequestPriority{}).(priority.AOPWritePriority)
	if ok {
		ctx = context.WithValue(ctx, logical.CtxKeyInFlightRequestPriority{}, inFlightRequestPriority)
	}

	resp, err = c.handleCancelableRequest(ctx, req)
	req.SetTokenEntry(nil)
	cancel()
	return resp, err
}

func (c *Core) handleCancelableRequest(ctx context.Context, req *logical.Request) (resp *logical.Response, err error) {
	waitGroup, err := waitForReplicationState(ctx, c, req)
	if err != nil {
		return nil, err
	}

	// Decrement the wait group when our request is done
	if waitGroup != nil {
		defer waitGroup.Done()
	}

	if c.MissingRequiredState(req.RequiredState(), c.perfStandby) {
		return nil, logical.ErrMissingRequiredState
	}

	// Ensure the req contains a MountPoint as it is depended on by some
	// functionality (e.g. quotas)
	var entry *MountEntry
	req.MountPoint, entry = c.router.MatchingMountAndEntry(ctx, req.Path)

	if entry != nil && entry.Table == mountTableType && !c.IsMountTypeAllowed(entry.Type) {
		return logical.ErrorResponse("mounts of type %q aren't supported by license", entry.Type), logical.ErrInvalidRequest
	}

	// If the request requires a snapshot ID, we need to perform checks to
	// ensure the request is valid and lock the snapshot, so it doesn't get
	// unloaded while the request is being processed.
	if req.RequiresSnapshotID != "" {
		if c.perfStandby {
			return nil, logical.ErrPerfStandbyPleaseForward
		}
		unlockSnapshot, err := c.lockSnapshotForRequest(ctx, req, entry)
		if err != nil {
			return logical.ErrorResponse("unable to lock snapshot: " + err.Error()), err
		}
		defer unlockSnapshot()
	}
	// Allowing writing to a path ending in / makes it extremely difficult to
	// understand user intent for the filesystem-like backends (kv,
	// cubbyhole) -- did they want a key named foo/ or did they want to write
	// to a directory foo/ with no (or forgotten) key, or...? It also affects
	// lookup, because paths ending in / are considered prefixes by some
	// backends. Basically, it's all just terrible, so don't allow it.
	if strings.HasSuffix(req.Path, "/") &&
		(req.Operation == logical.UpdateOperation ||
			req.Operation == logical.CreateOperation ||
			req.Operation == logical.PatchOperation ||
			req.Operation == logical.RecoverOperation) {
		if entry == nil || !entry.Config.TrimRequestTrailingSlashes {
			return logical.ErrorResponse("cannot write to a path ending in '/'"), nil
		} else {
			req.Path = strings.TrimSuffix(req.Path, "/")
		}
	}

	err = c.PopulateTokenEntry(ctx, req)
	if err != nil {
		if errwrap.Contains(err, logical.ErrPermissionDenied.Error()) {
			return nil, multierror.Append(err, logical.ErrInvalidToken)
		}
		return nil, err
	}

	// Always forward requests that are using a limited use count token.
	if c.perfStandby && req.ClientTokenRemainingUses > 0 {
		// Prevent forwarding on local-only requests.
		return nil, logical.ErrPerfStandbyPleaseForward
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, fmt.Errorf("could not parse namespace from http context: %w", err)
	}
	var requestBodyToken string
	var returnRequestAuthToken bool

	// req.Path will be relative by this point. The prefix check is first
	// to fail faster if we're not in this situation since it's a hot path
	switch {
	case strings.HasPrefix(req.Path, "sys/wrapping/"), strings.HasPrefix(req.Path, "auth/token/"):
		// Get the token ns info; if we match the paths below we want to
		// swap in the token context (but keep the relative path)
		te := req.TokenEntry()
		newCtx := ctx
		if te != nil {
			ns, err := NamespaceByID(ctx, te.NamespaceID, c)
			if err != nil {
				c.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err)
				return nil, err
			}
			if ns != nil {
				newCtx = namespace.ContextWithNamespace(ctx, ns)
			}
		}
		switch req.Path {
		// Route the token wrapping request to its respective sys NS
		case "sys/wrapping/lookup", "sys/wrapping/rewrap", "sys/wrapping/unwrap":
			ctx = newCtx
			// A lookup on a token that is about to expire returns nil, which means by the
			// time we can validate a wrapping token lookup will return nil since it will
			// be revoked after the call. So we have to do the validation here.
			valid, err := c.validateWrappingToken(ctx, req)
			if err != nil {
				return logical.ErrorResponse(fmt.Sprintf("error validating wrapping token: %s", err.Error())), logical.ErrPermissionDenied
			}
			if !valid {
				return nil, consts.ErrInvalidWrappingToken
			}

		// The -self paths have no meaning outside of the token NS, so
		// requests for these paths always go to the token NS
		case "auth/token/lookup-self", "auth/token/renew-self", "auth/token/revoke-self":
			ctx = newCtx
			returnRequestAuthToken = true

		// For the following operations, we can set the proper namespace context
		// using the token's embedded nsID if a relative path was provided.
		// The operation will still be gated by ACLs, which are checked later.
		case "auth/token/lookup", "auth/token/renew", "auth/token/revoke", "auth/token/revoke-orphan":
			token, ok := req.Data["token"]
			// If the token is not present (e.g. a bad request), break out and let the backend
			// handle the error
			if !ok {
				// If this is a token lookup request and if the token is not
				// explicitly provided, it will use the client token so we simply set
				// the context to the client token's context.
				if req.Path == "auth/token/lookup" {
					ctx = newCtx
				}
				break
			}
			if token == nil {
				return logical.ErrorResponse("invalid token"), logical.ErrPermissionDenied
			}
			// We don't care if the token is a server side consistent token or not. Either way, we're going
			// to be returning it for these paths instead of the short token stored in vault.
			requestBodyToken = token.(string)
			if IsSSCToken(token.(string)) {
				token, err = c.CheckSSCToken(ctx, token.(string), c.isLoginRequest(ctx, req), c.perfStandby)
				// If we receive an error from CheckSSCToken, we can assume the token is bad somehow, and the client
				// should receive a 403 bad token error like they do for all other invalid tokens, unless the error
				// specifies that we should forward the request or retry the request.
				if err != nil {
					if errors.Is(err, logical.ErrPerfStandbyPleaseForward) || errors.Is(err, logical.ErrMissingRequiredState) {
						return nil, err
					}
					return logical.ErrorResponse("bad token"), logical.ErrPermissionDenied
				}
				req.Data["token"] = token
			}
			_, nsID := namespace.SplitIDFromString(token.(string))
			if nsID != "" {
				ns, err := NamespaceByID(ctx, nsID, c)
				if err != nil {
					c.Logger().Warn("error looking up namespace from the token's namespace ID", "error", err)
					return nil, err
				}
				if ns != nil {
					ctx = namespace.ContextWithNamespace(ctx, ns)
				}
			}
		}

	// The following relative sys/leases/ paths handles re-routing requests
	// to the proper namespace using the lease ID on applicable paths.
	case strings.HasPrefix(req.Path, "sys/leases/"):
		switch req.Path {
		// For the following operations, we can set the proper namespace context
		// using the lease's embedded nsID if a relative path was provided.
		// The operation will still be gated by ACLs, which are checked later.
		case "sys/leases/lookup", "sys/leases/renew", "sys/leases/revoke", "sys/leases/revoke-force":
			leaseID, ok := req.Data["lease_id"]
			// If lease ID is not present, break out and let the backend handle the error
			_, isString := leaseID.(string)
			if !ok || !isString {
				break
			}
			_, nsID := namespace.SplitIDFromString(leaseID.(string))
			if nsID != "" {
				ns, err := NamespaceByID(ctx, nsID, c)
				if err != nil {
					c.Logger().Warn("error looking up namespace from the lease's namespace ID", "error", err)
					return nil, err
				}
				if ns != nil {
					ctx = namespace.ContextWithNamespace(ctx, ns)
				}
			}
		}

	// Prevent any metrics requests to be forwarded from a standby node.
	// Instead, we return an error since we cannot be sure if we have an
	// active token store to validate the provided token.
	case strings.HasPrefix(req.Path, "sys/metrics"):
		if c.standby && !c.perfStandby {
			return nil, ErrCannotForwardLocalOnly
		}
	}

	ns, err = namespace.FromContext(ctx)
	if err != nil {
		return nil, errwrap.Wrapf("could not parse namespace from http context: {{err}}", err)
	}

	if !hasNamespaces(c) && ns.Path != "" {
		return nil, logical.CodedError(403, "namespaces feature not enabled")
	}

	walState := &logical.WALState{}
	ctx = logical.IndexStateContext(ctx, walState)
	var auth *logical.Auth
	if c.isLoginRequest(ctx, req) && req.ClientTokenSource != logical.ClientTokenFromInternalAuth {
		resp, auth, err = c.handleLoginRequest(ctx, req)
	} else {
		resp, auth, err = c.handleRequest(ctx, req)
	}

	if err == nil && c.requestResponseCallback != nil {
		c.requestResponseCallback(c.router.MatchingBackend(ctx, req.Path), req, resp)
	}

	// If we saved the token in the request, we should return it in the response
	// data.
	if resp != nil && resp.Data != nil {
		if _, ok := resp.Data["error"]; !ok {
			if requestBodyToken != "" {
				resp.Data["id"] = requestBodyToken
			} else if returnRequestAuthToken && req.InboundSSCToken != "" {
				resp.Data["id"] = req.InboundSSCToken
			}
		}
	}
	if resp != nil && resp.Auth != nil && requestBodyToken != "" {
		// if a client token has already been set and the request body token's internal token
		// is equal to that value, then we can return the original request body token
		tok, _ := c.DecodeSSCToken(requestBodyToken)
		if resp.Auth.ClientToken == tok {
			resp.Auth.ClientToken = requestBodyToken
		}
	}

	// Ensure we don't leak internal data
	if resp != nil {
		if resp.Secret != nil {
			resp.Secret.InternalData = nil
		}
		if resp.Auth != nil {
			resp.Auth.InternalData = nil
		}
	}

	// We are wrapping if there is anything to wrap (not a nil response) and a
	// TTL was specified for the token. Errors on a call should be returned to
	// the caller, so wrapping is turned off if an error is hit and the error
	// is logged to the audit log.
	wrapping := resp != nil &&
		err == nil &&
		!resp.IsError() &&
		resp.WrapInfo != nil &&
		resp.WrapInfo.TTL != 0 &&
		resp.WrapInfo.Token == ""

	if wrapping {
		cubbyResp, cubbyErr := c.wrapInCubbyhole(ctx, req, resp, auth)
		// If not successful, returns either an error response from the
		// cubbyhole backend or an error; if either is set, set resp and err to
		// those and continue so that that's what we audit log. Otherwise
		// finish the wrapping and audit log that.
		if cubbyResp != nil || cubbyErr != nil {
			resp = cubbyResp
			err = cubbyErr
		} else {
			wrappingResp := &logical.Response{
				WrapInfo: resp.WrapInfo,
				Warnings: resp.Warnings,
			}
			resp = wrappingResp
		}
	}

	auditResp := resp
	// When unwrapping we want to log the actual response that will be written
	// out. We still want to return the raw value to avoid automatic updating
	// to any of it.
	if req.Path == "sys/wrapping/unwrap" &&
		resp != nil &&
		resp.Data != nil &&
		resp.Data[logical.HTTPRawBody] != nil {

		// Decode the JSON
		if resp.Data[logical.HTTPRawBodyAlreadyJSONDecoded] != nil {
			delete(resp.Data, logical.HTTPRawBodyAlreadyJSONDecoded)
		} else {
			httpResp := &logical.HTTPResponse{}
			err := jsonutil.DecodeJSON(resp.Data[logical.HTTPRawBody].([]byte), httpResp)
			if err != nil {
				c.logger.Error("failed to unmarshal wrapped HTTP response for audit logging", "error", err)
				return nil, ErrInternalError
			}

			auditResp = logical.HTTPResponseToLogicalResponse(httpResp)
		}
	}

	var nonHMACReqDataKeys []string
	var nonHMACRespDataKeys []string
	if entry != nil {
		// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
		if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
			nonHMACReqDataKeys = rawVals.([]string)
		}

		// Get and set ignored HMAC'd value. Reset those back to empty afterwards.
		if auditResp != nil {
			if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_response_keys"); ok {
				nonHMACRespDataKeys = rawVals.([]string)
			}
		}
	}

	// Create an audit trail of the response
	if !isControlGroupRun(req) {
		switch req.Path {
		case "sys/replication/dr/status", "sys/replication/performance/status", "sys/replication/status":
		default:
			logInput := &logical.LogInput{
				Auth:                auth,
				Request:             req,
				Response:            auditResp,
				OuterErr:            err,
				NonHMACReqDataKeys:  nonHMACReqDataKeys,
				NonHMACRespDataKeys: nonHMACRespDataKeys,
			}
			if auditErr := c.auditBroker.LogResponse(ctx, logInput); auditErr != nil {
				c.logger.Error("failed to audit response", "request_path", req.Path, "error", auditErr)
				return nil, ErrInternalError
			}
		}
	}

	if walState.LocalIndex != 0 || walState.ReplicatedIndex != 0 {
		walState.ClusterID = c.ClusterID()
		if walState.LocalIndex == 0 {
			if c.perfStandby {
				walState.LocalIndex = c.EntLastRemoteWAL()
			} else {
				walState.LocalIndex = c.EntLastWAL()
			}
		}
		if walState.ReplicatedIndex == 0 {
			if c.perfStandby {
				walState.ReplicatedIndex = c.entLastRemoteUpstreamWAL()
			} else {
				walState.ReplicatedIndex = c.EntLastRemoteWAL()
			}
		}

		req.SetResponseState(walState)
	}

	return
}

func isControlGroupRun(req *logical.Request) bool {
	return req.ControlGroup != nil
}

func (c *Core) doRouting(ctx context.Context, req *logical.Request) (*logical.Response, error) {
	// If we're replicating and we get a read-only error from a backend, need to forward to primary
	resp, err := c.router.Route(ctx, req)
	if shouldForward(c, resp, err) {
		fwdResp, fwdErr := forward(ctx, c, req)
		if fwdErr != nil && err != logical.ErrReadOnly {
			// When handling the request locally, we got an error that
			// contained ErrReadOnly, but had additional information.
			// Since we've now forwarded this request and got _another_
			// error, we should tell the user about both errors, so
			// they know about both.
			//
			// When there is no error from forwarding, the request
			// succeeded and so no additional context is necessary. When
			// the initial error here was only ErrReadOnly, it's likely
			// the plugin authors intended to forward this request
			// remotely anyway.
			repErr, ok := fwdErr.(*logical.ReplicationCodedError)
			if ok {
				fwdErr = &logical.ReplicationCodedError{
					Msg:  fmt.Sprintf("errors from both primary and secondary; primary error was %s; secondary errors follow: %s", repErr.Error(), err.Error()),
					Code: repErr.Code,
				}
			} else {
				fwdErr = multierror.Append(fwdErr, err)
			}
		}
		return fwdResp, fwdErr
	}
	return resp, err
}

func (c *Core) isLoginRequest(ctx context.Context, req *logical.Request) bool {
	return c.router.LoginPath(ctx, req.Path)
}

func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
	defer metrics.MeasureSince([]string{"core", "handle_request"}, time.Now())

	var nonHMACReqDataKeys []string
	entry := c.router.MatchingMountEntry(ctx, req.Path)
	if entry != nil {
		// Set here so the audit log has it even if authorization fails
		req.MountType = entry.Type
		req.SetMountRunningSha256(entry.RunningSha256)
		req.SetMountRunningVersion(entry.RunningVersion)
		req.SetMountIsExternalPlugin(entry.IsExternalPlugin())
		req.SetMountClass(entry.MountClass())

		// Get and set ignored HMAC'd value.
		if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
			nonHMACReqDataKeys = rawVals.([]string)
		}
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		c.logger.Error("failed to get namespace from context", "error", err)
		retErr = multierror.Append(retErr, ErrInternalError)
		return
	}

	// Validate the token
	auth, te, ctErr := c.CheckToken(ctx, req, false)
	if ctErr == logical.ErrRelativePath {
		return logical.ErrorResponse(ctErr.Error()), nil, ctErr
	}
	if ctErr == logical.ErrPerfStandbyPleaseForward {
		return nil, nil, ctErr
	}

	// See if the call to CheckToken set any request priority. We push the
	// processing down into CheckToken so we only have to do a router lookup
	// once.
	if auth != nil && auth.HTTPRequestPriority != nil {
		ctx = context.WithValue(ctx, logical.CtxKeyInFlightRequestPriority{}, *auth.HTTPRequestPriority)
	}

	// Updating in-flight request data with client/entity ID
	inFlightReqID, ok := ctx.Value(logical.CtxKeyInFlightRequestID{}).(string)
	if ok && req.ClientID != "" {
		c.UpdateInFlightReqData(inFlightReqID, req.ClientID)
	}

	// We run this logic first because we want to decrement the use count even
	// in the case of an error (assuming we can successfully look up; if we
	// need to forward, we exit before now)
	if te != nil && !isControlGroupRun(req) {
		// Attempt to use the token (decrement NumUses)
		var err error
		te, err = c.tokenStore.UseToken(ctx, te)
		if err != nil {
			c.logger.Error("failed to use token", "error", err)
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, nil, retErr
		}
		if te == nil {
			// Token has been revoked by this point
			retErr = multierror.Append(retErr, logical.ErrPermissionDenied, logical.ErrInvalidToken)
			return nil, nil, retErr
		}
		if te.NumUses == tokenRevocationPending {
			// We defer a revocation until after logic has run, since this is a
			// valid request (this is the token's final use). We pass the ID in
			// directly just to be safe in case something else modifies te later.
			defer func(id string) {
				nsActiveCtx := namespace.ContextWithNamespace(c.activeContext, ns)
				leaseID, err := c.expiration.CreateOrFetchRevocationLeaseByToken(nsActiveCtx, te)
				if err == nil {
					err = c.expiration.LazyRevoke(ctx, leaseID)
				}
				if err != nil {
					c.logger.Error("failed to revoke token", "error", err)
					retResp = nil
					retAuth = nil
					retErr = multierror.Append(retErr, ErrInternalError)
				}
				if retResp != nil && retResp.Secret != nil &&
					// Some backends return a TTL even without a Lease ID
					retResp.Secret.LeaseID != "" {
					retResp = logical.ErrorResponse("Secret cannot be returned; token had one use left, so leased credentials were immediately revoked.")
					return
				}
			}(te.ID)
		}
	}

	if ctErr != nil {
		newCtErr, cgResp, cgAuth, cgRetErr := checkNeedsCG(ctx, c, req, auth, ctErr, nonHMACReqDataKeys)
		switch {
		case newCtErr != nil:
			ctErr = newCtErr
		case cgResp != nil || cgAuth != nil:
			if cgRetErr != nil {
				retErr = multierror.Append(retErr, cgRetErr)
			}
			return cgResp, cgAuth, retErr
		}

		// If it is an internal error we return that, otherwise we
		// return invalid request so that the status codes can be correct
		switch {
		case ctErr == ErrInternalError,
			errwrap.Contains(ctErr, ErrInternalError.Error()),
			ctErr == logical.ErrPermissionDenied,
			errwrap.Contains(ctErr, logical.ErrPermissionDenied.Error()):
			switch ctErr.(type) {
			case *multierror.Error:
				retErr = ctErr
			default:
				retErr = multierror.Append(retErr, ctErr)
			}
		default:
			retErr = multierror.Append(retErr, logical.ErrInvalidRequest)
		}

		if !isControlGroupRun(req) {
			logInput := &logical.LogInput{
				Auth:               auth,
				Request:            req,
				OuterErr:           ctErr,
				NonHMACReqDataKeys: nonHMACReqDataKeys,
			}
			if err := c.auditBroker.LogRequest(ctx, logInput); err != nil {
				c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			}
		}

		if errwrap.Contains(retErr, ErrInternalError.Error()) {
			return nil, auth, retErr
		}
		return logical.ErrorResponse(ctErr.Error()), auth, retErr
	}

	// Attach the display name
	req.DisplayName = auth.DisplayName

	// Create an audit trail of the request
	if !isControlGroupRun(req) {
		logInput := &logical.LogInput{
			Auth:               auth,
			Request:            req,
			NonHMACReqDataKeys: nonHMACReqDataKeys,
		}
		if err := c.auditBroker.LogRequest(ctx, logInput); err != nil {
			c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		}
	}

	if err := c.entBlockRequestIfError(ns.Path, req.Path); err != nil {
		return nil, nil, multierror.Append(retErr, err)
	}

	leaseGenerated := false
	quotaResp, quotaErr := c.applyLeaseCountQuota(ctx, &quotas.Request{
		Path:          req.Path,
		MountPath:     strings.TrimPrefix(req.MountPoint, ns.Path),
		NamespacePath: ns.Path,
	})
	if quotaErr != nil {
		c.logger.Error("failed to apply quota", "path", req.Path, "error", quotaErr)
		retErr = multierror.Append(retErr, quotaErr)
		return nil, auth, retErr
	}

	if !quotaResp.Allowed {
		if c.logger.IsTrace() {
			c.logger.Trace("request rejected due to lease count quota violation", "request_path", req.Path)
		}

		retErr = multierror.Append(retErr, fmt.Errorf("request path %q: %w", req.Path, quotas.ErrLeaseCountQuotaExceeded))
		return nil, auth, retErr
	}

	defer func() {
		if quotaResp.Access != nil {
			quotaAckErr := c.ackLeaseQuota(quotaResp.Access, leaseGenerated)
			if quotaAckErr != nil {
				retErr = multierror.Append(retErr, quotaAckErr)
			}
		}
	}()

	// This context value will be empty if it's a request that doesn't require a
	// snapshot. This is done on purpose and handled in the
	// SnapshotStorageRouter
	ctx = logical.CreateContextWithSnapshotID(ctx, req.RequiresSnapshotID)

	// recover operations require 2 steps
	if req.Operation == logical.RecoverOperation {
		// first do a read operation
		// this will use the snapshot's storage
		originalPath := req.Path
		req.Operation = logical.ReadOperation
		if req.RecoverSourcePath != "" {
			req.Path = req.RecoverSourcePath
		}
		resp, err := c.doRouting(ctx, req)
		if err != nil {
			return nil, auth, err
		}
		if resp == nil {
			return logical.ErrorResponse("no data in the snapshot"), auth, err
		}
		if resp.IsError() {
			return resp, auth, err
		}
		// use the response as the data in a recover operation
		// set the snapshot ID context value to the empty string to ensure that
		// the write goes to the real storage
		req.Operation = logical.RecoverOperation
		req.Path = originalPath
		req.Data = resp.Data
		ctx = logical.CreateContextWithSnapshotID(ctx, "")
	}

	// Route the request
	resp, routeErr := c.doRouting(ctx, req)
	if resp != nil {
		// Add mount type information to the response
		if entry != nil {
			resp.MountType = entry.Type
		}

		// If wrapping is used, use the shortest between the request and response
		var wrapTTL time.Duration
		var wrapFormat, creationPath string
		var sealWrap bool

		// Ensure no wrap info information is set other than, possibly, the TTL
		if resp.WrapInfo != nil {
			if resp.WrapInfo.TTL > 0 {
				wrapTTL = resp.WrapInfo.TTL
			}
			wrapFormat = resp.WrapInfo.Format
			creationPath = resp.WrapInfo.CreationPath
			sealWrap = resp.WrapInfo.SealWrap
			resp.WrapInfo = nil
		}

		if req.WrapInfo != nil {
			if req.WrapInfo.TTL > 0 {
				switch {
				case wrapTTL == 0:
					wrapTTL = req.WrapInfo.TTL
				case req.WrapInfo.TTL < wrapTTL:
					wrapTTL = req.WrapInfo.TTL
				}
			}
			// If the wrap format hasn't been set by the response, set it to
			// the request format
			if req.WrapInfo.Format != "" && wrapFormat == "" {
				wrapFormat = req.WrapInfo.Format
			}
		}

		if wrapTTL > 0 {
			resp.WrapInfo = &wrapping.ResponseWrapInfo{
				TTL:          wrapTTL,
				Format:       wrapFormat,
				CreationPath: creationPath,
				SealWrap:     sealWrap,
			}
		}
	}

	// If there is a secret, we must register it with the expiration manager.
	// We exclude renewal of a lease, since it does not need to be re-registered
	if resp != nil && resp.Secret != nil && !strings.HasPrefix(req.Path, "sys/renew") &&
		!strings.HasPrefix(req.Path, "sys/leases/renew") {
		// KV mounts should return the TTL but not register
		// for a lease as this provides a massive slowdown
		registerLease := true

		matchingMountEntry := c.router.MatchingMountEntry(ctx, req.Path)
		if matchingMountEntry == nil {
			c.logger.Error("unable to retrieve kv mount entry from router")
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		}

		switch matchingMountEntry.Type {
		case "kv", "generic":
			// If we are kv type, first see if we are an older passthrough
			// backend, and otherwise check the mount entry options.
			matchingBackend := c.router.MatchingBackend(ctx, req.Path)
			if matchingBackend == nil {
				c.logger.Error("unable to retrieve kv backend from router")
				retErr = multierror.Append(retErr, ErrInternalError)
				return nil, auth, retErr
			}

			if ptbe, ok := matchingBackend.(*PassthroughBackend); ok {
				if !ptbe.GeneratesLeases() {
					registerLease = false
					resp.Secret.Renewable = false
				}
			} else if matchingMountEntry.Options == nil || matchingMountEntry.Options["leased_passthrough"] != "true" {
				registerLease = false
				resp.Secret.Renewable = false
			} else if req.IsSnapshotReadOrList() {
				registerLease = false
				resp.Secret.Renewable = false
			}

		case "plugin":
			// If we are a plugin type and the plugin name is "kv" check the
			// mount entry options.
			if matchingMountEntry.Config.PluginName == "kv" && (matchingMountEntry.Options == nil || matchingMountEntry.Options["leased_passthrough"] != "true" || req.IsSnapshotReadOrList()) {
				registerLease = false
				resp.Secret.Renewable = false
			}
		}

		if registerLease {
			if req.IsSnapshotReadOrList() {
				return logical.ErrorResponse("cannot register lease for snapshot read or list"), nil, ErrInternalError
			}
			sysView := c.router.MatchingSystemView(ctx, req.Path)
			if sysView == nil {
				c.logger.Error("unable to look up sys view for login path", "request_path", req.Path)
				return nil, nil, ErrInternalError
			}

			ttl, warnings, err := framework.CalculateTTL(sysView, 0, resp.Secret.TTL, 0, resp.Secret.MaxTTL, 0, time.Time{})
			if err != nil {
				return nil, nil, err
			}
			for _, warning := range warnings {
				resp.AddWarning(warning)
			}
			resp.Secret.TTL = ttl

			registerFunc, funcGetErr := getLeaseRegisterFunc(c)
			if funcGetErr != nil {
				retErr = multierror.Append(retErr, funcGetErr)
				return nil, auth, retErr
			}

			leaseID, err := registerFunc(ctx, req, resp, "")
			if err != nil {
				c.logger.Error("failed to register lease", "request_path", req.Path, "error", err)
				retErr = multierror.Append(retErr, ErrInternalError)
				return nil, auth, retErr
			}
			leaseGenerated = true
			resp.Secret.LeaseID = leaseID

			// Count the lease creation
			ttl_label := metricsutil.TTLBucket(resp.Secret.TTL)
			mountPointWithoutNs := ns.TrimmedPath(req.MountPoint)
			c.MetricSink().IncrCounterWithLabels(
				[]string{"secret", "lease", "creation"},
				1,
				[]metrics.Label{
					metricsutil.NamespaceLabel(ns),
					{"secret_engine", req.MountType},
					{"mount_point", mountPointWithoutNs},
					{"creation_ttl", ttl_label},
				},
			)
		}
	}

	// Only the token store is allowed to return an auth block, for any
	// other request this is an internal error.
	if resp != nil && resp.Auth != nil {
		if !strings.HasPrefix(req.Path, "auth/token/") {
			c.logger.Error("unexpected Auth response for non-token backend", "request_path", req.Path)
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		}

		// Fetch the namespace to which the token belongs
		tokenNS, err := NamespaceByID(ctx, te.NamespaceID, c)
		if err != nil {
			c.logger.Error("failed to fetch token's namespace", "error", err)
			retErr = multierror.Append(retErr, err)
			return nil, auth, retErr
		}
		if tokenNS == nil {
			c.logger.Error(namespace.ErrNoNamespace.Error())
			retErr = multierror.Append(retErr, namespace.ErrNoNamespace)
			return nil, auth, retErr
		}

		_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, tokenNS, resp.Auth.EntityID, false)
		if err != nil {
			// Best-effort clean up on error, so we log the cleanup error as a
			// warning but still return as internal error.
			if err := c.tokenStore.revokeOrphan(ctx, resp.Auth.ClientToken); err != nil {
				c.logger.Warn("failed to clean up token lease from entity and policy lookup failure", "request_path", req.Path, "error", err)
			}
			return nil, nil, ErrInternalError
		}

		// We skip expiration manager registration for token renewal since it
		// does not need to be re-registered
		if strings.HasPrefix(req.Path, "auth/token/renew") {
			// We build the "policies" list to be returned by starting with
			// token policies, and add identity policies right after this
			// conditional
			tok, _ := c.DecodeSSCToken(req.InboundSSCToken)
			if resp.Auth.ClientToken == tok {
				resp.Auth.ClientToken = req.InboundSSCToken
			}
			resp.Auth.Policies = policyutil.SanitizePolicies(resp.Auth.TokenPolicies, policyutil.DoNotAddDefaultPolicy)
		} else {
			resp.Auth.TokenPolicies = policyutil.SanitizePolicies(resp.Auth.Policies, policyutil.DoNotAddDefaultPolicy)

			switch resp.Auth.TokenType {
			case logical.TokenTypeBatch:
			case logical.TokenTypeService:
				if !c.perfStandby {
					registeredTokenEntry := &logical.TokenEntry{
						TTL:         auth.TTL,
						Policies:    auth.TokenPolicies,
						Path:        resp.Auth.CreationPath,
						NamespaceID: ns.ID,
					}

					// Only logins apply to role based quotas, so we can omit the role here, as we are not logging in.
					if err := c.expiration.RegisterAuth(ctx, registeredTokenEntry, resp.Auth, ""); err != nil {
						// Best-effort clean up on error, so we log the cleanup error as
						// a warning but still return as internal error.
						if err := c.tokenStore.revokeOrphan(ctx, resp.Auth.ClientToken); err != nil {
							c.logger.Warn("failed to clean up token lease during auth/token/ request", "request_path", req.Path, "error", err)
						}
						c.logger.Error("failed to register token lease during auth/token/ request", "request_path", req.Path, "error", err)
						retErr = multierror.Append(retErr, ErrInternalError)
						return nil, auth, retErr
					}
					if registeredTokenEntry.ExternalID != "" {
						resp.Auth.ClientToken = registeredTokenEntry.ExternalID
					}
					leaseGenerated = true
				}
			}
		}

		// We do these later since it's not meaningful for backends/expmgr to
		// have what is purely a snapshot of current identity policies, and
		// plugins can be confused if they are checking contents of
		// Auth.Policies instead of Auth.TokenPolicies
		resp.Auth.Policies = policyutil.SanitizePolicies(append(resp.Auth.Policies, identityPolicies[te.NamespaceID]...), policyutil.DoNotAddDefaultPolicy)
		resp.Auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies[te.NamespaceID], policyutil.DoNotAddDefaultPolicy)
		delete(identityPolicies, te.NamespaceID)
		resp.Auth.ExternalNamespacePolicies = identityPolicies
	}

	if resp != nil &&
		req.Path == "cubbyhole/response" &&
		len(te.Policies) == 1 &&
		te.Policies[0] == responseWrappingPolicyName {
		resp.AddWarning("Reading from 'cubbyhole/response' is deprecated. Please use sys/wrapping/unwrap to unwrap responses, as it provides additional security checks and other benefits.")
	}

	// Return the response and error
	if routeErr != nil {
		if _, ok := routeErr.(*logical.RequestDelegatedAuthError); ok {
			routeErr = fmt.Errorf("delegated authentication requested but authentication token present")
		}

		retErr = multierror.Append(retErr, routeErr)
	}

	return resp, auth, retErr
}

// handleLoginRequest is used to handle a login request, which is an
// unauthenticated request to the backend.
func (c *Core) handleLoginRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
	defer metrics.MeasureSince([]string{"core", "handle_login_request"}, time.Now())

	req.Unauthenticated = true

	var nonHMACReqDataKeys []string
	entry := c.router.MatchingMountEntry(ctx, req.Path)
	if entry != nil {
		// Set here so the audit log has it even if authorization fails
		req.MountType = entry.Type
		req.SetMountRunningSha256(entry.RunningSha256)
		req.SetMountRunningVersion(entry.RunningVersion)
		req.SetMountIsExternalPlugin(entry.IsExternalPlugin())
		req.SetMountClass(entry.MountClass())

		// Get and set ignored HMAC'd value.
		if rawVals, ok := entry.synthesizedConfigCache.Load("audit_non_hmac_request_keys"); ok {
			nonHMACReqDataKeys = rawVals.([]string)
		}
	}

	// Do an unauth check. This will cause EGP policies to be checked
	var auth *logical.Auth
	var ctErr error
	auth, _, ctErr = c.CheckToken(ctx, req, true)
	if ctErr == logical.ErrPerfStandbyPleaseForward {
		return nil, nil, ctErr
	}

	// Updating in-flight request data with client/entity ID
	inFlightReqID, ok := ctx.Value(logical.CtxKeyInFlightRequestID{}).(string)
	if ok && req.ClientID != "" {
		c.UpdateInFlightReqData(inFlightReqID, req.ClientID)
	}

	if ctErr != nil {
		// If it is an internal error we return that, otherwise we
		// return invalid request so that the status codes can be correct
		var errType error
		switch ctErr {
		case ErrInternalError, logical.ErrPermissionDenied:
			errType = ctErr
		default:
			errType = logical.ErrInvalidRequest
		}

		logInput := &logical.LogInput{
			Auth:               auth,
			Request:            req,
			OuterErr:           ctErr,
			NonHMACReqDataKeys: nonHMACReqDataKeys,
		}
		if err := c.auditBroker.LogRequest(ctx, logInput); err != nil {
			c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			return nil, nil, ErrInternalError
		}

		if errType != nil {
			retErr = multierror.Append(retErr, errType)
		}
		if ctErr == ErrInternalError {
			return nil, auth, retErr
		}
		return logical.ErrorResponse(ctErr.Error()), auth, retErr
	}

	switch req.Path {
	case "sys/replication/dr/status", "sys/replication/performance/status", "sys/replication/status":
	default:
		// Create an audit trail of the request. Attach auth if it was returned,
		// e.g. if a token was provided.
		logInput := &logical.LogInput{
			Auth:               auth,
			Request:            req,
			NonHMACReqDataKeys: nonHMACReqDataKeys,
		}
		if err := c.auditBroker.LogRequest(ctx, logInput); err != nil {
			c.logger.Error("failed to audit request", "path", req.Path, "error", err)
			return nil, nil, ErrInternalError
		}
	}

	// The token store uses authentication even when creating a new token,
	// so it's handled in handleRequest. It should not be reached here.
	if strings.HasPrefix(req.Path, "auth/token/") {
		c.logger.Error("unexpected login request for token backend", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	// check if user lockout feature is disabled
	isUserLockoutDisabled, err := c.isUserLockoutDisabled(entry)
	if err != nil {
		return nil, nil, err
	}

	// if user lockout feature is not disabled, check if the user is locked
	if !isUserLockoutDisabled {
		isloginUserLocked, err := c.isUserLocked(ctx, entry, req)
		if err != nil {
			return nil, nil, err
		}
		if isloginUserLocked {
			c.logger.Error("login attempts exceeded, user is locked out", "request_path", req.Path)
			return nil, nil, logical.ErrPermissionDenied
		}
	}

	// Route the request
	resp, routeErr := c.doRouting(ctx, req)

	handleInvalidCreds := func(err error) (*logical.Response, *logical.Auth, error) {
		if !isUserLockoutDisabled {
			err := c.failedUserLoginProcess(ctx, entry, req)
			if err != nil {
				return nil, nil, err
			}
		}
		return resp, nil, err
	}

	if routeErr != nil {
		// if routeErr has invalid credentials error, update the userFailedLoginMap
		if routeErr == logical.ErrInvalidCredentials {
			return handleInvalidCreds(routeErr)
		} else if da, ok := routeErr.(*logical.RequestDelegatedAuthError); ok {
			return c.handleDelegatedAuth(ctx, req, da, entry, handleInvalidCreds)
		}
	}

	if resp != nil {
		// If wrapping is used, use the shortest between the request and response
		var wrapTTL time.Duration
		var wrapFormat, creationPath string
		var sealWrap bool

		// Ensure no wrap info information is set other than, possibly, the TTL
		if resp.WrapInfo != nil {
			if resp.WrapInfo.TTL > 0 {
				wrapTTL = resp.WrapInfo.TTL
			}
			wrapFormat = resp.WrapInfo.Format
			creationPath = resp.WrapInfo.CreationPath
			sealWrap = resp.WrapInfo.SealWrap
			resp.WrapInfo = nil
		}

		if req.WrapInfo != nil {
			if req.WrapInfo.TTL > 0 {
				switch {
				case wrapTTL == 0:
					wrapTTL = req.WrapInfo.TTL
				case req.WrapInfo.TTL < wrapTTL:
					wrapTTL = req.WrapInfo.TTL
				}
			}
			if req.WrapInfo.Format != "" && wrapFormat == "" {
				wrapFormat = req.WrapInfo.Format
			}
		}

		if wrapTTL > 0 {
			resp.WrapInfo = &wrapping.ResponseWrapInfo{
				TTL:          wrapTTL,
				Format:       wrapFormat,
				CreationPath: creationPath,
				SealWrap:     sealWrap,
			}
		}
	}

	// A login request should never return a secret!
	if resp != nil && resp.Secret != nil {
		c.logger.Error("unexpected Secret response for login path", "request_path", req.Path)
		return nil, nil, ErrInternalError
	}

	ns, err := namespace.FromContext(ctx)
	if err != nil {
		c.logger.Error("failed to get namespace from context", "error", err)
		retErr = multierror.Append(retErr, ErrInternalError)
		return
	}
	// If the response generated an authentication, then generate the token
	if resp != nil && resp.Auth != nil && req.Path != "sys/mfa/validate" {
		leaseGenerated := false

		// by placing this after the authorization check, we don't leak
		// information about locked namespaces to unauthenticated clients.
		if err := c.entBlockRequestIfError(ns.Path, req.Path); err != nil {
			retErr = multierror.Append(retErr, err)
			return
		}

		// Check for request role in context to role based quotas
		var role string
		reqRole := ctx.Value(logical.CtxKeyRequestRole{})
		if reqRole != nil {
			role = reqRole.(string)
		}

		// The request successfully authenticated itself. Run the quota checks
		// before creating lease.
		quotaResp, quotaErr := c.applyLeaseCountQuota(ctx, &quotas.Request{
			Path:          req.Path,
			MountPath:     strings.TrimPrefix(req.MountPoint, ns.Path),
			Role:          role,
			NamespacePath: ns.Path,
		})

		if quotaErr != nil {
			c.logger.Error("failed to apply quota", "path", req.Path, "error", quotaErr)
			retErr = multierror.Append(retErr, quotaErr)
			return
		}

		if !quotaResp.Allowed {
			if c.logger.IsTrace() {
				c.logger.Trace("request rejected due to lease count quota violation", "request_path", req.Path)
			}

			retErr = multierror.Append(retErr, fmt.Errorf("request path %q: %w", req.Path, quotas.ErrLeaseCountQuotaExceeded))
			return
		}

		defer func() {
			if quotaResp.Access != nil {
				quotaAckErr := c.ackLeaseQuota(quotaResp.Access, leaseGenerated)
				if quotaAckErr != nil {
					retErr = multierror.Append(retErr, quotaAckErr)
				}
			}
		}()

		var entity *identity.Entity
		auth = resp.Auth

		mEntry := c.router.MatchingMountEntry(ctx, req.Path)

		if auth.Alias != nil &&
			mEntry != nil &&
			c.identityStore != nil {

			if mEntry.Local && os.Getenv(EnvVaultDisableLocalAuthMountEntities) != "" {
				goto CREATE_TOKEN
			}

			// Overwrite the mount type and mount path in the alias
			// information
			auth.Alias.MountType = req.MountType
			auth.Alias.MountAccessor = req.MountAccessor
			auth.Alias.Local = mEntry.Local

			if auth.Alias.Name == "" {
				return nil, nil, fmt.Errorf("missing name in alias")
			}

			var err error
			// Fetch the entity for the alias, or create an entity if one
			// doesn't exist.

			entity, entityCreated, err := c.identityStore.CreateOrFetchEntity(ctx, auth.Alias)
			if err != nil {
				switch auth.Alias.Local {
				case true:
					// Only create a new entity if the error was a readonly error and the creation flag is true
					// i.e the entity was in the middle of being created
					if entityCreated && errors.Is(err, logical.ErrReadOnly) {
						entity, err = registerLocalAlias(ctx, c, auth.Alias)
						if err != nil {
							if strings.Contains(err.Error(), errCreateEntityUnimplemented) {
								resp.AddWarning("primary cluster doesn't yet issue entities for local auth mounts; falling back to not issuing entities for local auth mounts")
								goto CREATE_TOKEN
							} else {
								return nil, nil, err
							}
						}
					}
				default:
					entity, entityCreated, err = possiblyForwardAliasCreation(ctx, c, err, auth, entity)
					if err != nil {
						return nil, nil, err
					}
				}
			}

			if entity == nil {
				return nil, nil, fmt.Errorf("failed to create an entity for the authenticated alias")
			}

			if entity.Disabled {
				return nil, nil, logical.ErrPermissionDenied
			}

			auth.EntityID = entity.ID
			auth.EntityCreated = entityCreated
			validAliases, err := c.identityStore.refreshExternalGroupMembershipsByEntityID(ctx, auth.EntityID, auth.GroupAliases, req.MountAccessor)
			if err != nil {
				return nil, nil, err
			}
			auth.GroupAliases = validAliases
		}

	CREATE_TOKEN:
		// Determine the source of the login
		source := c.router.MatchingMount(ctx, req.Path)

		// Login MFA
		entity, _, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID, true)
		if err != nil {
			return nil, nil, ErrInternalError
		}
		// finding the MFAEnforcementConfig that matches the ns and either of
		// entityID, MountAccessor, GroupID, or Auth type.
		matchedMfaEnforcementList, err := c.buildMFAEnforcementConfigList(ctx, entity, req.Path)
		if err != nil {
			return nil, nil, fmt.Errorf("failed to find MFAEnforcement configuration, error: %v", err)
		}

		// (for the context, a response warning above says: "primary cluster
		// doesn't yet issue entities for local auth mounts; falling back
		// to not issuing entities for local auth mounts")
		// based on the above, if the entity is nil, check if MFAEnforcementConfig
		// is configured or not. If not, continue as usual, but if there
		// is something, then report an error indicating that the user is not
		// allowed to login because there is no entity associated with it.
		// This is because an entity is needed to enforce MFA.
		if entity == nil && len(matchedMfaEnforcementList) > 0 {
			// this logic means that an MFAEnforcementConfig was configured with
			// only mount type or mount accessor
			return nil, nil, logical.ErrPermissionDenied
		}

		// The resp.Auth has been populated with the information that is required for MFA validation
		// This is why, the MFA check is placed at this point. The resp.Auth is going to be fully cached
		// in memory so that it would be used to return to the user upon MFA validation is completed.
		if entity != nil {
			if len(matchedMfaEnforcementList) == 0 && len(req.MFACreds) > 0 {
				resp.AddWarning("Found MFA header but failed to find MFA Enforcement Config")
			}

			// If X-Vault-MFA header is supplied to the login request,
			// run single-phase login MFA check, else run two-phase login MFA check
			if len(matchedMfaEnforcementList) > 0 && len(req.MFACreds) > 0 {
				for _, eConfig := range matchedMfaEnforcementList {
					err = c.validateLoginMFA(ctx, eConfig, entity, req.Connection.RemoteAddr, req.MFACreds, nil)
					if err != nil {
						return nil, nil, logical.ErrPermissionDenied
					}
				}
			} else if len(matchedMfaEnforcementList) > 0 && len(req.MFACreds) == 0 {
				mfaRequestID, err := uuid.GenerateUUID()
				if err != nil {
					return nil, nil, err
				}
				// sending back the MFARequirement config
				mfaRequirement := &logical.MFARequirement{
					MFARequestID:   mfaRequestID,
					MFAConstraints: make(map[string]*logical.MFAConstraintAny),
				}
				for _, eConfig := range matchedMfaEnforcementList {
					onlyMFAEnforcement := len(matchedMfaEnforcementList) == 1
					mfaAny, err := c.buildMfaEnforcementResponse(eConfig, entity, onlyMFAEnforcement)
					if err != nil {
						return nil, nil, err
					}
					mfaRequirement.MFAConstraints[eConfig.Name] = mfaAny
				}

				// for two phased MFA enforcement, we should not return the regular auth
				// response. This flag is indicate to store the auth response for later
				// and return MFARequirement only
				respAuth := &MFACachedAuthResponse{
					CachedAuth:            resp.Auth,
					RequestPath:           req.Path,
					RequestNSID:           ns.ID,
					RequestNSPath:         ns.Path,
					RequestConnRemoteAddr: req.Connection.RemoteAddr, // this is needed for the DUO method
					TimeOfStorage:         time.Now(),
					RequestID:             mfaRequestID,
				}
				err = possiblyForwardSaveCachedAuthResponse(ctx, c, respAuth)
				if err != nil {
					return nil, nil, err
				}
				auth = nil
				resp.Auth = &logical.Auth{
					MFARequirement: mfaRequirement,
				}
				resp.AddWarning("A login request was issued that is subject to MFA validation. Please make sure to validate the login by sending another request to mfa/validate endpoint.")
				// going to return early before generating the token
				// the user receives the mfaRequirement, and need to use the
				// login MFA validate endpoint to get the token
				return resp, auth, nil
			}
		}

		// Attach the display name, might be used by audit backends
		req.DisplayName = auth.DisplayName

		requiresLease := resp.Auth.TokenType != logical.TokenTypeBatch

		// If role was not already determined by http.rateLimitQuotaWrapping
		// and a lease will be generated, calculate a role for the leaseEntry.
		// We can skip this step if there are no pre-existing role-based quotas
		// for this mount and Vault is configured to skip lease role-based lease counting
		// until after they're created. This effectively zeroes out the lease count
		// for new role-based quotas upon creation, rather than counting old leases toward
		// the total.
		if reqRole == nil && requiresLease && !c.impreciseLeaseRoleTracking {
			role = c.DetermineRoleFromLoginRequest(ctx, req.MountPoint, req.Data, req.Connection, req.Headers)
		}

		leaseGen, respTokenCreate, errCreateToken := c.LoginCreateToken(ctx, ns, req.Path, source, role, resp)
		leaseGenerated = leaseGen
		if errCreateToken != nil {
			return respTokenCreate, nil, errCreateToken
		}
		resp = respTokenCreate
	}

	// Successful login, remove any entry from userFailedLoginInfo map
	// if it exists. This is done for batch tokens (for oss & ent)
	// For service tokens on oss it is taken care by core RegisterAuth function.
	// For service tokens on ent it is taken care by registerAuth RPC calls.
	// This update is done as part of registerAuth of RPC calls from standby
	// to active node. This is added there to reduce RPC calls
	if !isUserLockoutDisabled && (auth.TokenType == logical.TokenTypeBatch) {
		loginUserInfoKey := FailedLoginUser{
			aliasName:     auth.Alias.Name,
			mountAccessor: auth.Alias.MountAccessor,
		}

		// We don't need to try to delete the lockedUsers storage entry, since we're
		// processing a login request. If a login attempt is allowed, it means the user is
		// unlocked and we only add storage entry when the user gets locked.
		err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true)
		if err != nil {
			return nil, nil, err
		}
	}

	// if we were already going to return some error from this login, do that.
	// if not, we will then check if the API is locked for the requesting
	// namespace, to avoid leaking locked namespaces to unauthenticated clients.
	if resp != nil && resp.Data != nil {
		if _, ok := resp.Data["error"]; ok {
			return resp, auth, routeErr
		}
	}
	if routeErr != nil {
		return resp, auth, routeErr
	}

	// this check handles the bad login credential case
	if err := c.entBlockRequestIfError(ns.Path, req.Path); err != nil {
		return nil, nil, multierror.Append(retErr, err)
	}

	return resp, auth, routeErr
}

type invalidCredentialHandler func(err error) (*logical.Response, *logical.Auth, error)

// handleDelegatedAuth when a backend request returns logical.RequestDelegatedAuthError, it is requesting that
// an authentication workflow of its choosing be implemented prior to it being able to accept it. Normally
// this is used for standard protocols that communicate the credential information in a non-standard Vault way
func (c *Core) handleDelegatedAuth(ctx context.Context, origReq *logical.Request, da *logical.RequestDelegatedAuthError, entry *MountEntry, invalidCredHandler invalidCredentialHandler) (*logical.Response, *logical.Auth, error) {
	// Make sure we didn't get into a routing loop.
	if origReq.ClientTokenSource == logical.ClientTokenFromInternalAuth {
		return nil, nil, fmt.Errorf("%w: original request had delegated auth token, "+
			"forbidding another delegated request from path '%s'", ErrInternalError, origReq.Path)
	}

	// Backend has requested internally delegated authentication
	requestedAccessor := da.MountAccessor()
	if strings.TrimSpace(requestedAccessor) == "" {
		return nil, nil, fmt.Errorf("%w: backend returned an invalid mount accessor '%s'", ErrInternalError, requestedAccessor)
	}
	// First, is this allowed by the mount tunable?
	if !slices.Contains(entry.Config.DelegatedAuthAccessors, requestedAccessor) {
		return nil, nil, fmt.Errorf("delegated auth to accessor %s not permitted", requestedAccessor)
	}

	reqNamespace, err := namespace.FromContext(ctx)
	if err != nil {
		return nil, nil, fmt.Errorf("failed looking up namespace from context: %w", err)
	}

	mount := c.router.MatchingMountByAccessor(requestedAccessor)
	if mount == nil {
		return nil, nil, fmt.Errorf("%w: requested delegate authentication accessor '%s' was not found", logical.ErrPermissionDenied, requestedAccessor)
	}
	if mount.Table != credentialTableType {
		return nil, nil, fmt.Errorf("%w: requested delegate authentication mount '%s' was not an auth mount", logical.ErrPermissionDenied, requestedAccessor)
	}
	if mount.NamespaceID != reqNamespace.ID {
		return nil, nil, fmt.Errorf("%w: requested delegate authentication mount was in a different namespace than request", logical.ErrPermissionDenied)
	}

	// Found it, now form the login path and issue the request
	path := paths.Join("auth", mount.Path, da.Path())
	authReq, err := origReq.Clone()
	if err != nil {
		return nil, nil, err
	}
	authReq.MountAccessor = requestedAccessor
	authReq.Path = path
	authReq.Operation = logical.UpdateOperation

	// filter out any response wrapping headers, for our embedded login request
	delete(authReq.Headers, textproto.CanonicalMIMEHeaderKey(consts.WrapTTLHeaderName))
	authReq.WrapInfo = nil

	// Insert the data fields from the delegated auth error in our auth request
	authReq.Data = maps.Clone(da.Data())

	// Make sure we are going to perform a login request and not expose other backend types to this request
	if !c.isLoginRequest(ctx, authReq) {
		return nil, nil, fmt.Errorf("delegated path '%s' was not considered a login request", authReq.Path)
	}

	authResp, err := c.handleCancelableRequest(ctx, authReq)
	if err != nil || authResp.IsError() {
		// see if the backend wishes to handle the failed auth
		if da.AuthErrorHandler() != nil {
			if err != nil && errors.Is(err, logical.ErrInvalidCredentials) {
				// We purposefully ignore the error here as the handler will
				// always return the original error we passed in.
				_, _, _ = invalidCredHandler(err)
			}
			resp, err := da.AuthErrorHandler()(ctx, origReq, authReq, authResp, err)
			return resp, nil, err
		}
		switch err {
		case nil:
			return authResp, nil, nil
		case logical.ErrInvalidCredentials:
			return invalidCredHandler(err)
		default:
			return authResp, nil, err
		}
	}
	if authResp == nil {
		return nil, nil, fmt.Errorf("%w: delegated auth request returned empty response for request_path: %s", ErrInternalError, authReq.Path)
	}
	// A login request should never return a secret!
	if authResp.Secret != nil {
		return nil, nil, fmt.Errorf("%w: unexpected Secret response for login path for request_path: %s", ErrInternalError, authReq.Path)
	}
	if authResp.Auth == nil {
		return nil, nil, fmt.Errorf("%w: Auth response was nil for request_path: %s", ErrInternalError, authReq.Path)
	}
	if authResp.Auth.ClientToken == "" {
		if authResp.Auth.MFARequirement != nil {
			return nil, nil, fmt.Errorf("%w: delegated auth request requiring MFA is not supported: %s", logical.ErrPermissionDenied, authReq.Path)
		}
		return nil, nil, fmt.Errorf("%w: delegated auth request did not return a client token for login path: %s", ErrInternalError, authReq.Path)
	}

	// Delegated auth tokens should only be batch tokens, as we don't want to incur
	// the cost of storage/tidying for protocols that will be generating a token per
	// request.
	if !IsBatchToken(authResp.Auth.ClientToken) {
		return nil, nil, fmt.Errorf("%w: delegated auth requests must be configured to issue batch tokens", logical.ErrPermissionDenied)
	}

	// Authentication successful, use the resulting ClientToken to reissue the original request
	secondReq, err := origReq.Clone()
	if err != nil {
		return nil, nil, err
	}
	secondReq.ClientToken = authResp.Auth.ClientToken
	secondReq.ClientTokenSource = logical.ClientTokenFromInternalAuth
	resp, err := c.handleCancelableRequest(ctx, secondReq)
	return resp, nil, err
}

// LoginCreateToken creates a token as a result of a login request.
// If MFA is enforced, mfa/validate endpoint calls this functions
// after successful MFA validation to generate the token.
func (c *Core) LoginCreateToken(ctx context.Context, ns *namespace.Namespace, reqPath, mountPoint, role string, resp *logical.Response) (bool, *logical.Response, error) {
	auth := resp.Auth
	source := strings.TrimPrefix(mountPoint, credentialRoutePrefix)
	source = strings.ReplaceAll(source, "/", "-")

	// Prepend the source to the display name
	auth.DisplayName = strings.TrimSuffix(source+auth.DisplayName, "-")

	// Determine mount type
	mountEntry := c.router.MatchingMountEntry(ctx, reqPath)
	if mountEntry == nil {
		return false, nil, fmt.Errorf("failed to find a matching mount")
	}

	sysView := c.router.MatchingSystemView(ctx, reqPath)
	if sysView == nil {
		c.logger.Error("unable to look up sys view for login path", "request_path", reqPath)
		return false, nil, ErrInternalError
	}

	tokenTTL, warnings, err := framework.CalculateTTL(sysView, 0, auth.TTL, auth.Period, auth.MaxTTL, auth.ExplicitMaxTTL, time.Time{})
	if err != nil {
		return false, nil, err
	}
	for _, warning := range warnings {
		resp.AddWarning(warning)
	}

	_, identityPolicies, err := c.fetchEntityAndDerivedPolicies(ctx, ns, auth.EntityID, false)
	if err != nil {
		return false, nil, ErrInternalError
	}

	auth.TokenPolicies = policyutil.SanitizePolicies(auth.Policies, !auth.NoDefaultPolicy)
	allPolicies := policyutil.SanitizePolicies(append(auth.TokenPolicies, identityPolicies[ns.ID]...), policyutil.DoNotAddDefaultPolicy)

	// Prevent internal policies from being assigned to tokens. We check
	// this on auth.Policies including derived ones from Identity before
	// actually making the token.
	for _, policy := range allPolicies {
		if policy == "root" {
			return false, logical.ErrorResponse("auth methods cannot create root tokens"), logical.ErrInvalidRequest
		}
		if strutil.StrListContains(nonAssignablePolicies, policy) {
			return false, logical.ErrorResponse(fmt.Sprintf("cannot assign policy %q", policy)), logical.ErrInvalidRequest
		}
	}

	var registerFunc RegisterAuthFunc
	var funcGetErr error
	// Batch tokens should not be forwarded to perf standby
	if auth.TokenType == logical.TokenTypeBatch {
		registerFunc = c.RegisterAuth
	} else {
		registerFunc, funcGetErr = getAuthRegisterFunc(c)
	}
	if funcGetErr != nil {
		return false, nil, funcGetErr
	}

	leaseGenerated := false
	err = registerFunc(ctx, tokenTTL, reqPath, auth, role)
	switch {
	case err == nil:
		if auth.TokenType != logical.TokenTypeBatch {
			leaseGenerated = true
		}
	case errors.Is(err, ErrInternalError), isRetryableRPCError(ctx, err):
		return false, nil, err
	default:
		return false, logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
	}

	auth.IdentityPolicies = policyutil.SanitizePolicies(identityPolicies[ns.ID], policyutil.DoNotAddDefaultPolicy)
	delete(identityPolicies, ns.ID)
	auth.ExternalNamespacePolicies = identityPolicies
	auth.Policies = allPolicies

	// Count the successful token creation
	ttl_label := metricsutil.TTLBucket(tokenTTL)
	// Do not include namespace path in mount point; already present as separate label.
	mountPointWithoutNs := ns.TrimmedPath(mountPoint)
	c.metricSink.IncrCounterWithLabels(
		[]string{"token", "creation"},
		1,
		[]metrics.Label{
			metricsutil.NamespaceLabel(ns),
			{"auth_method", mountEntry.Type},
			{"mount_point", mountPointWithoutNs},
			{"creation_ttl", ttl_label},
			{"token_type", auth.TokenType.String()},
		},
	)

	return leaseGenerated, resp, nil
}

func isRetryableRPCError(ctx context.Context, err error) bool {
	stat, ok := status.FromError(err)
	if !ok {
		return false
	}

	switch stat.Code() {
	case codes.Unavailable:
		return true
	case codes.Canceled:
		// if the request context is canceled through a deadline exceeded, we
		// want to return false. But otherwise, there could have been an EOF or
		// the RPC client context has been canceled which should be retried
		ctxErr := ctx.Err()
		if ctxErr == nil {
			return true
		}
		return !errors.Is(ctxErr, context.DeadlineExceeded)
	case codes.Unknown:
		// sometimes a missing HTTP content-type error can happen when multiple
		// HTTP statuses have been written. This can happen when the error
		// occurs in the middle of a response. This should be retried.
		return strings.Contains(err.Error(), "malformed header: missing HTTP content-type")
	default:
		return false
	}
}

// failedUserLoginProcess updates the userFailedLoginMap with login count and  last failed
// login time for users with failed login attempt
// If the user gets locked for current login attempt, it updates the storage entry too
func (c *Core) failedUserLoginProcess(ctx context.Context, mountEntry *MountEntry, req *logical.Request) error {
	// get the user lockout configuration for the user
	userLockoutConfiguration := c.getUserLockoutConfiguration(mountEntry)

	// determine the key for userFailedLoginInfo map
	loginUserInfoKey, err := c.getLoginUserInfoKey(ctx, mountEntry, req)
	if err != nil {
		return err
	}

	// get entry from userFailedLoginInfo map for the key
	userFailedLoginInfo, err := getUserFailedLoginInfo(ctx, c, loginUserInfoKey)
	if err != nil {
		return err
	}

	// update the last failed login time with current time
	failedLoginInfo := FailedLoginInfo{
		lastFailedLoginTime: int(time.Now().Unix()),
	}

	// set the failed login count value for the entry in userFailedLoginInfo map
	switch userFailedLoginInfo {
	case nil: // entry does not exist in userfailedLoginMap
		failedLoginInfo.count = 1
	default:
		failedLoginInfo.count = userFailedLoginInfo.count + 1

		// if counter reset, set the count value to 1 as this gets counted as new entry
		lastFailedLoginTime := time.Unix(int64(userFailedLoginInfo.lastFailedLoginTime), 0)
		counterResetDuration := userLockoutConfiguration.LockoutCounterReset
		if time.Now().After(lastFailedLoginTime.Add(counterResetDuration)) {
			failedLoginInfo.count = 1
		}
	}

	// update the userFailedLoginInfo map (and/or storage) with the updated/new entry
	err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, &failedLoginInfo, false)
	if err != nil {
		return err
	}

	return nil
}

// getLoginUserInfoKey gets failedUserLoginInfo map key for login user
func (c *Core) getLoginUserInfoKey(ctx context.Context, mountEntry *MountEntry, req *logical.Request) (FailedLoginUser, error) {
	userInfo := FailedLoginUser{}
	aliasName, err := c.aliasNameFromLoginRequest(ctx, req)
	if err != nil {
		return userInfo, err
	}
	if aliasName == "" {
		return userInfo, errors.New("failed to determine alias name from login request")
	}

	userInfo.aliasName = aliasName
	userInfo.mountAccessor = mountEntry.Accessor
	return userInfo, nil
}

// isUserLockoutDisabled checks if user lockout feature to prevent brute forcing is disabled
// Auth types userpass, ldap and approle support this feature
// precedence: environment var setting >> auth tune setting >> config file setting >> default (enabled)
func (c *Core) isUserLockoutDisabled(mountEntry *MountEntry) (bool, error) {
	if !strutil.StrListContains(configutil.GetSupportedUserLockoutsAuthMethods(), mountEntry.Type) {
		return true, nil
	}

	// check environment variable
	if disableUserLockoutEnv := os.Getenv(consts.VaultDisableUserLockout); disableUserLockoutEnv != "" {
		var err error
		disableUserLockout, err := strconv.ParseBool(disableUserLockoutEnv)
		if err != nil {
			return false, errors.New("Error parsing the environment variable VAULT_DISABLE_USER_LOCKOUT")
		}
		if disableUserLockout {
			return true, nil
		}
		return false, nil
	}

	// read auth tune for mount entry
	userLockoutConfigFromMount := mountEntry.Config.UserLockoutConfig
	if userLockoutConfigFromMount != nil && userLockoutConfigFromMount.DisableLockout {
		return true, nil
	}

	// read config for auth type from config file
	userLockoutConfiguration := c.getUserLockoutFromConfig(mountEntry.Type)
	if userLockoutConfiguration.DisableLockout {
		return true, nil
	}

	// default
	return false, nil
}

// isUserLocked determines if the login user is locked
func (c *Core) isUserLocked(ctx context.Context, mountEntry *MountEntry, req *logical.Request) (locked bool, err error) {
	// get userFailedLoginInfo map key for login user
	loginUserInfoKey, err := c.getLoginUserInfoKey(ctx, mountEntry, req)
	if err != nil {
		return false, err
	}

	// get entry from userFailedLoginInfo map for the key
	userFailedLoginInfo, err := getUserFailedLoginInfo(ctx, c, loginUserInfoKey)
	if err != nil {
		return false, err
	}
	userLockoutConfiguration := c.getUserLockoutConfiguration(mountEntry)

	switch userFailedLoginInfo {
	case nil:
		// entry not found in userFailedLoginInfo map, check storage to re-verify
		ns, err := namespace.FromContext(ctx)
		if err != nil {
			return false, fmt.Errorf("could not parse namespace from http context: %w", err)
		}
		storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath+"%s/%s/%s", ns.ID, loginUserInfoKey.mountAccessor, loginUserInfoKey.aliasName)
		existingEntry, err := c.barrier.Get(ctx, storageUserLockoutPath)
		if err != nil {
			return false, err
		}
		var lastLoginTime int
		if existingEntry == nil {
			// no storage entry found, user is not locked
			return false, nil
		}

		err = jsonutil.DecodeJSON(existingEntry.Value, &lastLoginTime)
		if err != nil {
			return false, err
		}

		// if time passed from last login time is within lockout duration, the user is locked
		if time.Now().Unix()-int64(lastLoginTime) < int64(userLockoutConfiguration.LockoutDuration.Seconds()) {
			// user locked
			return true, nil
		}

		// else user is not locked. Entry is stale, this will be removed from storage during cleanup
		// by the background thread

	default:
		// entry found in userFailedLoginInfo map, check if the user is locked
		isCountOverLockoutThreshold := userFailedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold)
		isWithinLockoutDuration := time.Now().Unix()-int64(userFailedLoginInfo.lastFailedLoginTime) < int64(userLockoutConfiguration.LockoutDuration.Seconds())

		if isCountOverLockoutThreshold && isWithinLockoutDuration {
			// user locked
			return true, nil
		}
	}
	return false, nil
}

// getUserLockoutConfiguration gets the user lockout configuration for a mount entry
// it checks the config file and auth tune values
// precedence: auth tune >> config file values for auth type >> config file values for all type
// >> default user lockout values
// getUserLockoutFromConfig call in this function takes care of config file precedence
func (c *Core) getUserLockoutConfiguration(mountEntry *MountEntry) (userLockoutConfig UserLockoutConfig) {
	// get user configuration values from config file
	userLockoutConfig = c.getUserLockoutFromConfig(mountEntry.Type)

	authTuneUserLockoutConfig := mountEntry.Config.UserLockoutConfig
	// if user lockout is not configured using auth tune, return values from config file
	if authTuneUserLockoutConfig == nil {
		return userLockoutConfig
	}
	// replace values in return with config file configuration
	// for fields that are not configured using auth tune
	if authTuneUserLockoutConfig.LockoutThreshold != 0 {
		userLockoutConfig.LockoutThreshold = authTuneUserLockoutConfig.LockoutThreshold
	}
	if authTuneUserLockoutConfig.LockoutDuration != 0 {
		userLockoutConfig.LockoutDuration = authTuneUserLockoutConfig.LockoutDuration
	}
	if authTuneUserLockoutConfig.LockoutCounterReset != 0 {
		userLockoutConfig.LockoutCounterReset = authTuneUserLockoutConfig.LockoutCounterReset
	}
	if authTuneUserLockoutConfig.DisableLockout {
		userLockoutConfig.DisableLockout = authTuneUserLockoutConfig.DisableLockout
	}
	return userLockoutConfig
}

// getUserLockoutFromConfig gets the userlockout configuration for given mount type from config file
// it reads the user lockout configuration from server config
// it has values for "all" type and any mountType that is configured using config file
// "all" type values are updated in shared config with default values i.e; if "all" type is
// not configured in config file, it is updated in shared config with default configuration
// If "all" type is configured in config file, any missing fields are updated with default values
// similarly missing values for a given mount type in config file are updated with "all" type
// default values
// If user_lockout configuration is not configured using config file at all, defaults are returned
func (c *Core) getUserLockoutFromConfig(mountType string) UserLockoutConfig {
	defaultUserLockoutConfig := UserLockoutConfig{
		LockoutThreshold:    configutil.UserLockoutThresholdDefault,
		LockoutDuration:     configutil.UserLockoutDurationDefault,
		LockoutCounterReset: configutil.UserLockoutCounterResetDefault,
		DisableLockout:      configutil.DisableUserLockoutDefault,
	}
	conf := c.rawConfig.Load()
	if conf == nil {
		return defaultUserLockoutConfig
	}
	userlockouts := conf.(*server.Config).UserLockouts
	if userlockouts == nil {
		return defaultUserLockoutConfig
	}
	for _, userLockoutConfig := range userlockouts {
		switch userLockoutConfig.Type {
		case "all":
			defaultUserLockoutConfig = UserLockoutConfig{
				LockoutThreshold:    userLockoutConfig.LockoutThreshold,
				LockoutDuration:     userLockoutConfig.LockoutDuration,
				LockoutCounterReset: userLockoutConfig.LockoutCounterReset,
				DisableLockout:      userLockoutConfig.DisableLockout,
			}
		case mountType:
			return UserLockoutConfig{
				LockoutThreshold:    userLockoutConfig.LockoutThreshold,
				LockoutDuration:     userLockoutConfig.LockoutDuration,
				LockoutCounterReset: userLockoutConfig.LockoutCounterReset,
				DisableLockout:      userLockoutConfig.DisableLockout,
			}

		}
	}
	return defaultUserLockoutConfig
}

func (c *Core) buildMfaEnforcementResponse(eConfig *mfa.MFAEnforcementConfig, entity *identity.Entity, onlyMFAEnforcement bool) (*logical.MFAConstraintAny, error) {
	if eConfig == nil {
		return nil, fmt.Errorf("MFA enforcement config is nil")
	}
	if entity == nil {
		return nil, fmt.Errorf("entity is nil")
	}
	mfaAny := &logical.MFAConstraintAny{
		Any: []*logical.MFAMethodID{},
	}
	for _, methodID := range eConfig.MFAMethodIDs {
		mConfig, err := c.loginMFABackend.MemDBMFAConfigByID(methodID)
		if err != nil {
			return nil, fmt.Errorf("failed to get methodID %s from MFA config table, error: %v", methodID, err)
		}
		var duoUsePasscode bool
		if mConfig.Type == mfaMethodTypeDuo {
			duoConf, ok := mConfig.Config.(*mfa.Config_DuoConfig)
			if !ok {
				return nil, fmt.Errorf("invalid MFA configuration type, expected DuoConfig")
			}
			duoUsePasscode = duoConf.DuoConfig.UsePasscode
		}

		allowSelfEnrollment := false
		if mConfig.Type == mfaMethodTypeTOTP && constants.IsEnterprise {
			totpConf, ok := mConfig.Config.(*mfa.Config_TOTPConfig)
			if !ok {
				return nil, fmt.Errorf("invalid MFA configuration type, expected TOTPConfig")
			}
			enrollmentEnabled := totpConf.TOTPConfig.GetEnableSelfEnrollment()
			_, entityHasMFASecretForMethodID := entity.MFASecrets[methodID]
			if enrollmentEnabled && !entityHasMFASecretForMethodID && onlyMFAEnforcement {
				// If enable_self_enrollment setting on the TOTP MFA method config is set to
				// true and the entity does not have an MFA secret yet, we will allow
				// self-service enrollment as long as only a single MFA enforcement applies to
				// this login request.
				allowSelfEnrollment = true
			}
		}

		mfaMethod := &logical.MFAMethodID{
			Type:         mConfig.Type,
			ID:           methodID,
			UsesPasscode: mConfig.Type == mfaMethodTypeTOTP || duoUsePasscode,
			Name:         mConfig.Name,
			// This will be used by the client to determine whether it should offer the user
			// a way to generate an MFA secret for this method.
			SelfEnrollmentEnabled: allowSelfEnrollment,
		}
		mfaAny.Any = append(mfaAny.Any, mfaMethod)
	}
	return mfaAny, nil
}

// RegisterAuth uses a logical.Auth object to create a token entry in the token
// store, and registers a corresponding token lease to the expiration manager.
// role is the login role used as part of the creation of the token entry. If not
// relevant, can be omitted (by being provided as "").
func (c *Core) RegisterAuth(ctx context.Context, tokenTTL time.Duration, path string, auth *logical.Auth, role string) error {
	// We first assign token policies to what was returned from the backend
	// via auth.Policies. Then, we get the full set of policies into
	// auth.Policies from the backend + entity information -- this is not
	// stored in the token, but we perform sanity checks on it and return
	// that information to the user.

	// Generate a token
	ns, err := namespace.FromContext(ctx)
	if err != nil {
		return err
	}
	te := logical.TokenEntry{
		Path:           path,
		Meta:           auth.Metadata,
		DisplayName:    auth.DisplayName,
		CreationTime:   time.Now().Unix(),
		TTL:            tokenTTL,
		NumUses:        auth.NumUses,
		EntityID:       auth.EntityID,
		BoundCIDRs:     auth.BoundCIDRs,
		Policies:       auth.TokenPolicies,
		NamespaceID:    ns.ID,
		ExplicitMaxTTL: auth.ExplicitMaxTTL,
		Period:         auth.Period,
		Type:           auth.TokenType,
	}

	if te.TTL == 0 && (len(te.Policies) != 1 || te.Policies[0] != "root") {
		c.logger.Error("refusing to create a non-root zero TTL token")
		return ErrInternalError
	}

	if err := c.tokenStore.create(ctx, &te); err != nil {
		c.logger.Error("failed to create token", "error", err)
		return possiblyWrapOverloadedError("failed to create token", err)
	}

	// Populate the client token, accessor, and TTL
	auth.ClientToken = te.ID
	auth.Accessor = te.Accessor
	auth.TTL = te.TTL
	auth.Orphan = te.Parent == ""

	switch auth.TokenType {
	case logical.TokenTypeBatch:
		// Ensure it's not marked renewable since it isn't
		auth.Renewable = false
	case logical.TokenTypeService:
		// Register with the expiration manager
		if err := c.expiration.RegisterAuth(ctx, &te, auth, role); err != nil {
			if err := c.tokenStore.revokeOrphan(ctx, te.ID); err != nil {
				c.logger.Warn("failed to clean up token lease during login request", "request_path", path, "error", err)
			}
			c.logger.Error("failed to register token lease during login request", "request_path", path, "error", err)
			return possiblyWrapOverloadedError("failed to register token lease during login request", err)
		}
		if te.ExternalID != "" {
			auth.ClientToken = te.ExternalID
		}
		// Successful login, remove any entry from userFailedLoginInfo map
		// if it exists. This is done for service tokens (for oss) here.
		// For ent it is taken care by registerAuth RPC calls.
		if auth.Alias != nil {
			loginUserInfoKey := FailedLoginUser{
				aliasName:     auth.Alias.Name,
				mountAccessor: auth.Alias.MountAccessor,
			}

			// We don't need to try to delete the lockedUsers storage entry, since we're
			// processing a login request. If a login attempt is allowed, it means the user is
			// unlocked and we only add storage entry when the user gets locked.
			err = updateUserFailedLoginInfo(ctx, c, loginUserInfoKey, nil, true)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// LocalGetUserFailedLoginInfo gets the failed login information for a user based on alias name and mountAccessor
func (c *Core) LocalGetUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser) *FailedLoginInfo {
	c.userFailedLoginInfoLock.Lock()
	value, exists := c.userFailedLoginInfo[userKey]
	c.userFailedLoginInfoLock.Unlock()
	if exists {
		return value
	}
	return nil
}

// LocalUpdateUserFailedLoginInfo updates the failed login information for a user based on alias name and mountAccessor
func (c *Core) LocalUpdateUserFailedLoginInfo(ctx context.Context, userKey FailedLoginUser, failedLoginInfo *FailedLoginInfo, deleteEntry bool) error {
	c.userFailedLoginInfoLock.Lock()
	defer c.userFailedLoginInfoLock.Unlock()

	switch deleteEntry {
	case false:
		// update entry in the map
		c.userFailedLoginInfo[userKey] = failedLoginInfo

		// get the user lockout configuration for the user
		mountEntry := c.router.MatchingMountByAccessor(userKey.mountAccessor)
		if mountEntry == nil {
			mountEntry = &MountEntry{}
			mountEntry.NamespaceID = namespace.RootNamespaceID
		}
		userLockoutConfiguration := c.getUserLockoutConfiguration(mountEntry)

		// if failed login count has reached threshold, create a storage entry as the user got locked
		if failedLoginInfo.count >= uint(userLockoutConfiguration.LockoutThreshold) {
			// user locked
			storageUserLockoutPath := fmt.Sprintf(coreLockedUsersPath+"%s/%s/%s", mountEntry.NamespaceID, userKey.mountAccessor, userKey.aliasName)

			compressedBytes, err := jsonutil.EncodeJSONAndCompress(failedLoginInfo.lastFailedLoginTime, nil)
			if err != nil {
				c.logger.Error("failed to encode or compress failed login user entry", "error", err)
				return err
			}

			// Create an entry
			entry := &logical.StorageEntry{
				Key:   storageUserLockoutPath,
				Value: compressedBytes,
			}

			// Write to the physical backend
			if err := c.barrier.Put(ctx, entry); err != nil {
				c.logger.Error("failed to persist failed login user entry", "error", err)
				return err
			}

		}

	default:
		// delete the entry from the map, if no key exists it is no-op
		delete(c.userFailedLoginInfo, userKey)
	}
	return nil
}

// PopulateTokenEntry looks up req.ClientToken in the token store and uses
// it to set other fields in req.  Does nothing if ClientToken is empty
// or a JWT token, or for service tokens that don't exist in the token store.
// Should be called with read stateLock held.
func (c *Core) PopulateTokenEntry(ctx context.Context, req *logical.Request) error {
	if req.ClientToken == "" {
		return nil
	}

	// Also attach the accessor if we have it. This doesn't fail if it
	// doesn't exist because the request may be to an unauthenticated
	// endpoint/login endpoint where a bad current token doesn't matter, or
	// a token from a Vault version pre-accessors. We ignore errors for
	// JWTs.
	token := req.ClientToken
	var err error
	req.InboundSSCToken = token
	decodedToken := token
	if IsSSCToken(token) {
		// If ForwardToActive is set to ForwardSSCTokenToActive, we ignore
		// whether the endpoint is a login request, as since we have the token
		// forwarded to us, we should treat it as an unauthenticated endpoint
		// and ensure the token is populated too regardless.
		// Notably, this is important for some endpoints, such as endpoints
		// such as sys/ui/mounts/internal, which is unauthenticated but a token
		// may be provided to be used.
		// Without the check to see if
		// c.ForwardToActive() == ForwardSSCTokenToActive unauthenticated
		// requests that do not use a token but were provided one anyway
		// could fail with a 412.
		// We only follow this behaviour if we're a perf standby, as
		// this behaviour only makes sense in that case as only they
		// could be missing the token population.
		// Without ForwardToActive being set to ForwardSSCTokenToActive,
		// behaviours that rely on this functionality also wouldn't make
		// much sense, as they would fail with 412 required index not present
		// as perf standbys aren't guaranteed to have the WAL state
		// for new tokens.
		unauth := c.isLoginRequest(ctx, req)
		if c.ForwardToActive() == ForwardSSCTokenToActive && c.perfStandby {
			unauth = false
		}
		decodedToken, err = c.CheckSSCToken(ctx, token, unauth, c.perfStandby)
		// If we receive an error from CheckSSCToken, we can assume the token is bad somehow, and the client
		// should receive a 403 bad token error like they do for all other invalid tokens, unless the error
		// specifies that we should forward the request or retry the request.
		if err != nil {
			if errors.Is(err, logical.ErrPerfStandbyPleaseForward) || errors.Is(err, logical.ErrMissingRequiredState) {
				return err
			}
			return logical.ErrPermissionDenied
		}
	}
	req.ClientToken = decodedToken
	// We ignore the token returned from CheckSSCToken here as Lookup also
	// decodes the SSCT, and it may need the original SSCT to check state.
	te, err := c.LookupToken(ctx, token)
	if err != nil {
		// If we're missing required state, return that error
		// as-is to the client
		if errors.Is(err, logical.ErrPerfStandbyPleaseForward) || errors.Is(err, logical.ErrMissingRequiredState) {
			return err
		}
		// If we have two dots but the second char is a dot it's a vault
		// token of the form s.SOMETHING.nsid, not a JWT
		if !IsJWT(token) {
			return fmt.Errorf("error performing token check: %w", err)
		}
	}
	if err == nil && te != nil {
		req.ClientTokenAccessor = te.Accessor
		req.ClientTokenRemainingUses = te.NumUses
		req.SetTokenEntry(te)
	}
	return nil
}

func (c *Core) CheckSSCToken(ctx context.Context, token string, unauth bool, isPerfStandby bool) (string, error) {
	if unauth && token != "" {
		// This token shouldn't really be here, but alas it was sent along with the request
		// Since we're already knee deep in the token checking code pre-existing token checking
		// code, we have to deal with this token whether we like it or not. So, we'll just try
		// to get the inner token, and if that fails, return the token as-is. We intentionally
		// will skip any token checks, because this is an unauthenticated paths and the token
		// is just a nuisance rather than a means of auth.

		// We cannot return whatever we like here, because if we do then CheckToken, which looks up
		// the corresponding lease, will not find the token entry and lease. There are unauth'ed
		// endpoints that use the token entry (such as sys/ui/mounts/internal) to do custom token
		// checks, which would then fail. Therefore, we must try to get whatever thing is tied to
		// token entries, but we must explicitly not do any SSC Token checks.
		tok, err := c.DecodeSSCToken(token)
		if err != nil || tok == "" {
			return token, nil
		}
		return tok, nil
	}
	return c.checkSSCTokenInternal(ctx, token, isPerfStandby)
}

// DecodeSSCToken returns the random part of an SSCToken without
// performing any signature or WAL checks.
func (c *Core) DecodeSSCToken(token string) (string, error) {
	// Skip batch and old style service tokens. These can have the prefix "b.",
	// "s." (for old tokens) or "hvb."
	if !IsSSCToken(token) {
		return token, nil
	}
	tok, err := DecodeSSCTokenInternal(token)
	if err != nil {
		return "", err
	}
	return tok.Random, nil
}

// DecodeSSCTokenInternal is a helper used to get the inner part of a SSC token without
// checking the token signature or the WAL index.
func DecodeSSCTokenInternal(token string) (*tokens.Token, error) {
	signedToken := &tokens.SignedToken{}

	// Skip batch and old style service tokens. These can have the prefix "b.",
	// "s." (for old tokens) or "hvb."
	if !strings.HasPrefix(token, consts.ServiceTokenPrefix) {
		return nil, fmt.Errorf("not service token")
	}

	// Consider the suffix of the token only when unmarshalling
	suffixToken := token[4:]

	tokenBytes, err := base64.RawURLEncoding.DecodeString(suffixToken)
	if err != nil {
		return nil, fmt.Errorf("can't decode token")
	}

	err = proto.Unmarshal(tokenBytes, signedToken)
	if err != nil {
		return nil, err
	}
	plainToken := &tokens.Token{}
	err2 := proto.Unmarshal([]byte(signedToken.Token), plainToken)
	if err2 != nil {
		return nil, err2
	}
	return plainToken, nil
}

func (c *Core) checkSSCTokenInternal(ctx context.Context, token string, isPerfStandby bool) (string, error) {
	signedToken := &tokens.SignedToken{}

	// Skip batch and old style service tokens. These can have the prefix "b.",
	// "s." (for old tokens) or "hvb."
	if !strings.HasPrefix(token, consts.ServiceTokenPrefix) {
		return token, nil
	}
	// Check token length to guess if this is an server side consistent token or not.
	// Note that even when the DisableSSCTokens flag is set, index
	// bearing tokens that have already been given out may still be used.
	if !IsSSCToken(token) {
		return token, nil
	}

	// Consider the suffix of the token only when unmarshalling
	suffixToken := token[4:]

	tokenBytes, err := base64.RawURLEncoding.DecodeString(suffixToken)
	if err != nil {
		c.logger.Warn("cannot decode token", "error", err)
		return token, nil
	}

	err = proto.Unmarshal(tokenBytes, signedToken)
	if err != nil {
		// Log a warning here, but don't return an error. This is because we want don't
		// want to forward the request to the active node if the token is invalid.
		c.logger.Debug("error occurred when unmarshalling ssc token: %w", err)
		return token, nil
	}
	hm, err := c.tokenStore.CalculateSignedTokenHMAC(signedToken.Token)
	if !hmac.Equal(hm, signedToken.Hmac) {
		// As above, don't return an error so that the request is handled like normal,
		// and handled by the node that received it.
		c.logger.Debug("token mac is incorrect", "token", signedToken.Token)
		return token, nil
	}

	plainToken := &tokens.Token{}
	err = proto.Unmarshal([]byte(signedToken.Token), plainToken)
	if err != nil {
		return "", err
	}

	// Disregard SSCT on perf-standbys for non-raft storage
	if c.perfStandby && c.getRaftBackend() == nil {
		return plainToken.Random, nil
	}

	ep := int(plainToken.IndexEpoch)
	if ep < c.tokenStore.GetSSCTokensGenerationCounter() {
		return plainToken.Random, nil
	}

	requiredWalState := &logical.WALState{ClusterID: c.ClusterID(), LocalIndex: plainToken.LocalIndex, ReplicatedIndex: 0}
	if c.HasWALState(requiredWalState, isPerfStandby) {
		return plainToken.Random, nil
	}

	// Make sure to forward the request instead of checking the token if the flag
	// is set and we're on a perf standby
	if c.ForwardToActive() == ForwardSSCTokenToActive && isPerfStandby {
		return "", logical.ErrPerfStandbyPleaseForward
	}

	// In this case, the server side consistent token cannot be used on this node. We return the appropriate
	// status code.
	return "", logical.ErrMissingRequiredState
}
