package core

import (
	"bytes"
	"context"
	"crypto/sha256"
	"crypto/x509"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"maps"
	"net/http"
	"net/url"
	"regexp"
	"slices"
	"strconv"
	"strings"
	"time"

	"github.com/hashicorp/errwrap"
	"github.com/hashicorp/go-multierror"
	"github.com/openbao/openbao/sdk/v2/helper/pathmanager"
	sdklogical "github.com/openbao/openbao/sdk/v2/logical"
	authhelper "github.com/stephnangue/warden/auth/helper"
	"github.com/stephnangue/warden/internal/namespace"
	"github.com/stephnangue/warden/listener"
	"github.com/stephnangue/warden/logger"
	"github.com/stephnangue/warden/logical"
)

const (
	// maxRequestBodySize is the maximum allowed size for JSON request bodies on sys/ API paths.
	// This prevents unbounded memory allocation from malicious large payloads.
	maxRequestBodySize = 32 << 20 // 32MB
)

var (
	// restrictedSysAPIs is the set of `sys/` APIs available only in the root namespace.
	restrictedSysAPIs = pathmanager.New()
)

func init() {
	restrictedSysAPIs.AddPaths([]string{
		"audit-hash",
		"audit",
		"config/auditing",
		"config/cors",
		"config/reload",
		"config/state",
		"config/ui",
		"decode-token",
		"generate-recovery-token",
		"generate-root",
		"health",
		"host-info",
		"in-flight-req",
		"init",
		"internal/counters/activity",
		"internal/counters/activity/export",
		"internal/counters/activity/monthly",
		"internal/counters/config",
		"internal/inspect/router",
		"key-status",
		"loggers",
		"managed-keys",
		"metrics",
		"mfa/method",
		"monitor",
		"pprof",
		"quotas/config",
		"quotas/lease-count",
		"quotas/rate-limit",
		"raw",
		"rekey-recovery-key",
		"rekey",
		"replication/merkle-check",
		"replication/recover",
		"replication/reindex",
		"replication/status",
		"rotate",
		"rotate/root",
		"rotate/config",
		"rotate/keyring",
		"rotate/keyring/config",
		"seal",
		"sealwrap/rewrap",
		"step-down",
		"storage",
		"sync/config",
		"unseal",
	})
}

func (c *Core) CheckToken(ctx context.Context, req *logical.Request, unauth bool) (*logical.Auth, *CBP, *logical.TokenEntry, error) {
	var cbp *CBP
	var te *logical.TokenEntry

	// Even if unauth, if a token is provided, there's little reason not to
	// gather as much info as possible for the audit log
	if !unauth || (unauth && req.ClientToken != "") {
		var err error
		cbp, te, err = c.fetchCBPAndTokenEntry(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 {
			if c.standby.Load() || (c.activeContext != nil && c.activeContext.Err() != nil) {
				return nil, cbp, te, ErrStandby
			}
			return nil, cbp, te, err
		}
	}

	// For create/update operations, perform existence check to determine the actual operation
	if req.Operation == logical.CreateOperation || req.Operation == logical.UpdateOperation {
		existsResp, checkExists, resourceExists, err := c.router.RouteExistenceCheck(ctx, req)
		switch err {
		case sdklogical.ErrUnsupportedPath:
			// Backend doesn't support existence check, continue with original operation
			checkExists = false
		case nil:
			if existsResp != nil && existsResp.IsError() {
				return nil, cbp, te, existsResp.Error()
			}
		default:
			c.logger.Error("failed to run existence check", logger.Err(err))
			return nil, cbp, te, ErrInternalError
		}

		// Adjust operation based on existence check result
		if checkExists {
			if resourceExists {
				// Resource exists, this should be an update
				req.Operation = logical.UpdateOperation
			} else {
				// Resource doesn't exist, this should be a create
				req.Operation = logical.CreateOperation
			}
		}
	}

	// Check if this is a root protected path
	rootPath := c.router.RootPath(ctx, req.Path)
	if rootPath && unauth {
		return nil, nil, nil, errors.New("cannot access root path in unauthenticated request")
	}

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

	if te != nil {
		auth.Policies = te.Policies
		auth.TokenType = te.Type
	}

	// Check the standard non-root CBPs. Return the token entry if it's not
	// allowed so we can decrement the use count (to be implemented in the future)
	accessControlResults := c.performPolicyChecks(ctx, cbp, te, req, &PolicyCheckOpts{
		Unauth:            unauth,
		RootPrivsRequired: rootPath,
	})

	auth.PolicyResults = &sdklogical.PolicyResults{
		Allowed: accessControlResults.Allowed,
	}

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

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

	if accessControlResults.CBPResults != nil && len(accessControlResults.CBPResults.GrantingPolicies) > 0 {
		auth.PolicyResults.GrantingPolicies = accessControlResults.CBPResults.GrantingPolicies
	}

	return auth, cbp, te, nil
}

func (c *Core) fetchCBPAndTokenEntry(ctx context.Context, req *logical.Request) (*CBP, *logical.TokenEntry, error) {
	// Ensure there is a client token
	if req.ClientToken == "" {
		return nil, nil, sdklogical.ErrPermissionDenied
	}

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

	// Inject client IP into context so validateIPBinding can read it
	if req.ClientIP != "" {
		ctx = context.WithValue(ctx, logical.ClientIPKey, req.ClientIP)
	}

	// Resolve the token policy
	var te *logical.TokenEntry
	switch req.TokenEntry() {
	case nil:
		var err error
		te, err = c.LookupToken(ctx, req.ClientToken)
		if err != nil {
			// Authentication/authorization failures should return permission denied
			if errors.Is(err, ErrTokenNamespaceMismatch) ||
				errors.Is(err, ErrTokenNotFound) ||
				errors.Is(err, ErrTokenExpired) ||
				errors.Is(err, ErrOriginViolation) ||
				errors.Is(err, ErrTypeNotFound) {
				c.logger.Warn("token lookup failed", logger.Err(err))
				return nil, nil, sdklogical.ErrPermissionDenied
			}
			c.logger.Error("failed to lookup token", logger.Err(err))
			return nil, nil, ErrInternalError
		}
		req.SetTokenEntry(te)
	default:
		te = req.TokenEntry()
	}

	// Ensure the token is valid
	if te == nil {
		return nil, nil, sdklogical.ErrPermissionDenied
	}

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

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

	if tokenNS == nil {
		c.logger.Error("failed to fetch token namespace", logger.Err(namespace.ErrNoNamespace))
		return nil, nil, ErrInternalError
	}

	tokenCtx := namespace.ContextWithNamespace(ctx, tokenNS)

	// Construct the corresponding CBP object. CBP construction should be
	// performed on the token's namespace.
	if c.policyStore == nil {
		c.logger.Error("policy store is unavailable")
		return nil, nil, ErrInternalError
	}
	cbp, err := c.policyStore.CBP(tokenCtx, policyNames)
	if err != nil {
		c.logger.Error("failed to construct CBP", logger.Err(err))
		return nil, nil, ErrInternalError
	}

	return cbp, te, nil
}

func (c *Core) HandleRequest(ctx context.Context, req *logical.Request) (*logical.Response, error) {
	// Check if the core is sealed
	if c.Sealed() {
		return logical.ErrorResponse(logical.ErrServiceUnavailable("Warden is sealed")), nil
	}

	// Check if the core has an active context
	if c.activeContext == nil || c.activeContext.Err() != nil {
		if c.standby.Load() {
			return nil, ErrStandby
		}
		return logical.ErrorResponse(logical.ErrServiceUnavailable("server context canceled")), nil
	}

	// Extract namespace header from HTTP request
	var nsHeader string
	if req.HTTPRequest != nil {
		nsHeader = req.HTTPRequest.Header.Get("X-Warden-Namespace")
	}

	// Sanitize the request path.
	// Preserve trailing slash for help operations so mount-root help works
	// (e.g., "azure/" needs the slash to match mount key "azure/" in the router).
	requestPath := req.Path
	if req.Operation != logical.HelpOperation {
		requestPath = strings.TrimSuffix(req.Path, "/")
	}

	// Resolve namespace from header and request path
	// /v1/ns1/sys/namespaces/test1 -> ns1/ and sys/namespaces/test1
	// /v1/sys/namespaces/test1 with X-Warden-Namespace=ns1 -> ns1/ and sys/namespaces/test1
	ns, trimmedPath := c.namespaceStore.ResolveNamespaceFromRequest(nsHeader, requestPath)
	if ns == nil {
		if trimmedPath != "" {
			c.logger.Warn("namespace resolution failed",
				logger.String("trimmed_path", trimmedPath),
				logger.String("namespace_header", nsHeader),
				logger.String("request_path", req.Path))
		}
		return logical.ErrorResponse(logical.ErrNotFound("namespace not found")), nil
	}

	// Check for restricted APIs in non-root namespaces
	if ns.ID != namespace.RootNamespaceID {
		if strings.HasPrefix(trimmedPath, "sys/") &&
			restrictedSysAPIs.HasPathSegments(trimmedPath[len("sys/"):]) {
			return logical.ErrorResponse(logical.ErrBadRequest("operation unavailable in namespaces")), nil
		}
	}

	// Set the resolved path (the path relative to the namespace)
	req.Path = trimmedPath

	ctx = namespace.ContextWithNamespace(ctx, ns)
	activeCtx, cancel := context.WithCancel(c.activeContext)
	defer cancel()

	go func(activeCtx context.Context, reqCtx context.Context) {
		select {
		case <-activeCtx.Done():
		case <-reqCtx.Done():
			cancel()
		}
	}(activeCtx, ctx)

	// Header-based routing has two cases driven by which X-Warden-* headers
	// are present:
	//
	//   X-Warden-Provider present (header-routing mode):
	//     Treat the request URL as the literal upstream API path. Synthesize
	//     the canonical <provider>/role/<role>/gateway/<api> shape before
	//     mount lookup. Reject against the system backend (the header isn't
	//     a routing override for system endpoints) and against SigV4-signed
	//     requests (mutating the URL would invalidate the client signature
	//     and the failure would look like an authentication bug).
	//
	//   X-Warden-Provider absent, X-Warden-Role present:
	//     Path-routing mode where the role header overrides any role baked
	//     into the URL. Rewrite the role segment in req.Path so the
	//     streaming backend's role parser sees the same role the auth
	//     resolver chooses (see isTransparentRequest).
	//
	// In both cases HTTPRequest.URL.Path is realigned with req.Path so
	// providers that read URL.Path directly see a consistent shape.
	if req.HTTPRequest != nil {
		provider := req.HTTPRequest.Header.Get("X-Warden-Provider")
		headerRole := req.HTTPRequest.Header.Get("X-Warden-Role")

		switch {
		case provider != "":
			if strings.HasPrefix(req.Path, "sys/") {
				return logical.ErrorResponse(logical.ErrBadRequest("X-Warden-Provider is not honored on system backend paths")), nil
			}
			if isSigV4Authorization(req.HTTPRequest.Header.Get("Authorization")) {
				return logical.ErrorResponse(logical.ErrBadRequest("X-Warden-Provider is not compatible with SigV4 authentication; use the path-routed /v1/<mount>/role/<role>/gateway/<api> form instead")), nil
			}
			synthesized, err := synthesizeGatewayPath(provider, headerRole, req.Path)
			if err != nil {
				return logical.ErrorResponse(logical.ErrBadRequest(err.Error())), nil
			}
			req.Path = synthesized
			req.HTTPRequest.URL.Path = "/v1/" + synthesized
			req.HTTPRequest.URL.RawPath = ""

		case headerRole != "":
			rewritten := applyRoleHeader(req.Path, headerRole)
			if rewritten != req.Path {
				req.Path = rewritten
				req.HTTPRequest.URL.Path = "/v1/" + rewritten
				req.HTTPRequest.URL.RawPath = ""
			}
		}
	}

	// For help operations, mount-root paths (e.g., "azure") may arrive without
	// a trailing slash because path.Join in the API client strips them.
	// Use the silent MatchingMount to probe with a trailing slash first,
	// so MatchingBackend doesn't log a spurious error on the initial miss.
	if req.Operation == logical.HelpOperation && !strings.HasSuffix(req.Path, "/") {
		if c.router.MatchingMount(ctx, req.Path) == "" && c.router.MatchingMount(ctx, req.Path+"/") != "" {
			req.Path = req.Path + "/"
		}
	}
	matchingBackend := c.router.MatchingBackend(ctx, req.Path)
	if matchingBackend == nil {
		c.logger.Warn("no backend mounted at path",
			logger.String("full_req_path", req.HTTPRequest.URL.Path),
			logger.String("relative_req_path", req.Path),
			logger.String("namespace", ns.Path),
		)
		return logical.ErrorResponse(logical.ErrNotFoundf("no handler for path %q", req.Path)), nil
	}

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

// sigV4AuthorizationPrefixes enumerates the SigV4 dialects whose canonical
// request includes the URL path. If any of these appears as the Authorization
// scheme, the header-routing path mutation would invalidate the client's
// signature — so we reject the request loudly instead of letting it fail as
// a confusing 401.
var sigV4AuthorizationPrefixes = []string{
	"AWS4-HMAC-SHA256",
	"ACS3-HMAC-SHA256",
}

// isSigV4Authorization reports whether the Authorization header value looks
// like a SigV4-family signature.
func isSigV4Authorization(authz string) bool {
	for _, prefix := range sigV4AuthorizationPrefixes {
		if strings.HasPrefix(authz, prefix) {
			return true
		}
	}
	return false
}

// roleSegmentPattern matches the canonical role/<role>/gateway/ segment that
// appears between the mount prefix and the upstream API path in path-routed
// requests. Only the leftmost occurrence is rewritten by applyRoleHeader.
var roleSegmentPattern = regexp.MustCompile(`role/[^/]+/gateway/`)

// applyRoleHeader rewrites the leftmost role/<role>/gateway/ segment in the
// path to match the given role. Idempotent for paths that don't carry such a
// segment (returned unchanged). Mount prefixes cannot contain a "role" or
// "gateway" segment (rejected at mount-create time), so the leftmost match
// is guaranteed to be the gateway-role segment, not part of the upstream API
// path.
func applyRoleHeader(path, role string) string {
	if role == "" {
		return path
	}
	loc := roleSegmentPattern.FindStringIndex(path)
	if loc == nil {
		return path
	}
	return path[:loc[0]] + "role/" + role + "/gateway/" + path[loc[1]:]
}

// synthesizeGatewayPath builds the canonical gateway path from a provider
// mount value and optional role. The result matches the shape the router
// and streaming backend expect:
//
//	role present: <provider>/role/<role>/gateway/<api>
//	role empty:   <provider>/gateway/<api>
//
// One leading and one trailing slash on the provider value are tolerated
// and stripped. Empty path segments and bare ".." segments are rejected.
// Whether the resolved mount exists is the caller's concern (the router's
// existing 404 path handles that).
func synthesizeGatewayPath(provider, role, apiPath string) (string, error) {
	provider = strings.TrimSuffix(provider, "/")
	provider = strings.TrimPrefix(provider, "/")
	if provider == "" {
		return "", fmt.Errorf("X-Warden-Provider value is empty")
	}
	for _, seg := range strings.Split(provider, "/") {
		if seg == "" {
			return "", fmt.Errorf("X-Warden-Provider value contains an empty path segment")
		}
		if seg == ".." {
			return "", fmt.Errorf("X-Warden-Provider value contains a path-traversal segment")
		}
	}

	apiPath = strings.TrimPrefix(apiPath, "/")

	var b strings.Builder
	b.WriteString(provider)
	b.WriteString("/")
	if role != "" {
		b.WriteString("role/")
		b.WriteString(role)
		b.WriteString("/")
	}
	b.WriteString("gateway/")
	b.WriteString(apiPath)
	return b.String(), nil
}

func (c *Core) handleCancelableRequest(ctx context.Context, req *logical.Request) (resp *logical.Response, err error) {
	// MountPoint will not always be set at this point, so we ensure the req contains it
	req.MountPoint = c.router.MatchingMount(ctx, req.Path)

	err = c.PopulateTokenEntry(ctx, req)
	if err != nil {
		return nil, err
	}

	var auth *logical.Auth
	var te *logical.TokenEntry

	if c.isLoginRequest(ctx, req) {
		resp, auth, err = c.handleLoginRequest(ctx, req, false)
		te = req.TokenEntry()
	} else {
		resp, auth, err = c.handleNonLoginRequest(ctx, req)
		te = req.TokenEntry()
	}

	if resp != nil && resp.Streamed {
		if srw, ok := req.ResponseWriter.(*logical.StatusRecordingWriter); ok {
			resp.StatusCode = srw.StatusCode()
		}
	}

	// Create an audit trail of the response
	auditEntry := c.buildResponseAuditEntry(ctx, req, resp, auth, te, err)
	if auditOK, auditErr := c.auditManager.LogResponse(ctx, auditEntry); auditErr != nil {
		c.logger.Error("failed to audit response",
			logger.String("path", req.Path),
			logger.Err(auditErr),
		)
		// For non-streaming requests, audit failure = request failure
		if !req.Streamed {
			return nil, ErrInternalError
		}
	} else if !auditOK && !req.Streamed && !req.StreamUnauthenticated {
		// For non-streaming requests, block if no audit devices are configured.
		// Streaming requests are allowed through even without auditing because:
		// 1. The connection may already be established with data flowing
		// 2. Abruptly terminating would be more disruptive than missing audit
		// 3. The audit failure is still logged for operator awareness
		c.logger.Warn("response blocked: no audit devices configured",
			logger.String("path", req.Path),
		)
		return nil, ErrInternalError
	}

	return resp, err
}

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

// 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, isInternalLogin bool) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {

	req.Unauthenticated = true

	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.MountClass = entry.Class
		req.MountAccessor = entry.Accessor
	}

	// Parse request body before CheckToken since req.Data may be used during token validation
	if err := c.parseRequestBody(req); err != nil {
		return logical.ErrorResponse(logical.ErrBadRequest(err.Error())), nil, err
	}

	// Do an unauth check.
	var auth *logical.Auth
	//var cbp *CBP
	//var te *logical.TokenEntry
	var ctErr error
	auth, _, _, ctErr = c.CheckToken(ctx, req, true)

	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, sdklogical.ErrPermissionDenied:
			errType = ctErr
		default:
			errType = sdklogical.ErrInvalidRequest
		}

		// Audit the failed request
		auditEntry := c.buildRequestAuditEntry(ctx, req, auth, nil, ctErr)
		if _, auditErr := c.auditManager.LogRequest(ctx, auditEntry); auditErr != nil {
			c.logger.Error("failed to audit login request error",
				logger.String("path", req.Path),
				logger.Err(auditErr),
			)
		}

		if errType != nil {
			retErr = multierror.Append(retErr, errType)
		}

		// Build the error response for audit logging
		var resp *logical.Response
		if ctErr == ErrInternalError {
			resp = nil
		} else {
			resp = logical.ErrorResponse(logical.ErrInternal(ctErr.Error()))
		}

		// Audit the failed response - ensures complete request/response pair in audit log
		respAuditEntry := c.buildResponseAuditEntry(ctx, req, resp, auth, nil, ctErr)
		if _, auditErr := c.auditManager.LogResponse(ctx, respAuditEntry); auditErr != nil {
			c.logger.Error("failed to audit login failure response",
				logger.String("path", req.Path),
				logger.Err(auditErr),
			)
		}

		if ctErr == ErrInternalError {
			return nil, auth, retErr
		}
		return resp, auth, retErr
	}

	// Create an audit trail of the request
	auditEntry := c.buildRequestAuditEntry(ctx, req, auth, nil, nil)
	if auditOK, auditErr := c.auditManager.LogRequest(ctx, auditEntry); auditErr != nil {
		c.logger.Error("failed to audit login request",
			logger.String("path", req.Path),
			logger.Err(auditErr),
		)
		return nil, nil, ErrInternalError
	} else if !auditOK {
		c.logger.Warn("login request blocked: no audit devices configured",
			logger.String("path", req.Path),
		)
		return nil, nil, ErrInternalError
	}

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

	// If the response generated an authentication, then generate the token.
	// Transparent token types (jwt_role, cert_role) are reserved for the
	// internal gateway auth flow — reject explicit login attempts early.
	if resp != nil && resp.Auth != nil {
		if !isInternalLogin && (resp.Auth.TokenType == "jwt_role" || resp.Auth.TokenType == "cert_role") {
			return logical.ErrorResponse(logical.ErrBadRequest("explicit login is not supported for roles with token_type=transparent; clients authenticate implicitly via gateway requests")), nil, nil
		} else {
			respTokenCreate, errCreateToken := c.LoginCreateToken(ctx, resp)
			if errCreateToken != nil {
				return respTokenCreate, nil, errCreateToken
			}
			resp = respTokenCreate
			resp.MountClass = req.MountClass
		}
	}

	if routeErr != nil {
		retErr = multierror.Append(retErr, routeErr)
	}

	return resp, auth, routeErr
}

func (c *Core) LoginCreateToken(ctx context.Context, resp *logical.Response) (*logical.Response, error) {
	auth := resp.Auth

	// Prevent internal policies from being assigned to tokens.
	for _, policy := range auth.Policies {
		if policy == "root" {
			return logical.ErrorResponse(logical.ErrForbidden("auth methods cannot create root tokens")), sdklogical.ErrInvalidRequest
		}
		if slices.Contains(nonAssignablePolicies, policy) {
			return logical.ErrorResponse(logical.ErrForbiddenf("cannot assign policy %q", policy)), sdklogical.ErrInvalidRequest
		}
	}

	now := time.Now()
	authData := logical.AuthData{
		PrincipalID:    auth.PrincipalID,
		RoleName:       auth.RoleName,
		ExpireAt:       now.Add(auth.TokenTTL),
		CredentialSpec: auth.CredentialSpec,
		Policies:       auth.Policies,
		ClientIP:       auth.ClientIP,
		TokenValue:     auth.ClientToken,
		Actors:         auth.Actors,
	}

	tokenValue, err := c.tokenStore.GenerateToken(ctx, auth.TokenType, &authData)

	if err != nil {
		// error when creating token
		c.logger.Error("Failed to generate token", logger.String("token_type", auth.TokenType), logger.Any("auth_data", authData))
		return logical.ErrorResponse(logical.ErrInternal("failed to generate token")), ErrInternalError
	}

	data := map[string]any{
		"token_type":     authhelper.DisplayTokenType(tokenValue.Type),
		"expire_at":      tokenValue.ExpireAt,
		"bound_ip":       tokenValue.CreatedByIP,
		"token_id":       tokenValue.ID,
		"token_assessor": tokenValue.Accessor,
		"namespace":      tokenValue.NamespacePath,
		"principal_id":   tokenValue.PrincipalID,
		"role":           tokenValue.RoleName,
		"data":           tokenValue.Data,
		"policies":       tokenValue.Policies,
	}

	maps.Copy(resp.Data, data)

	// Remove internal fields that were needed for token generation
	// but should not be exposed to the client.
	delete(resp.Data, "fingerprint")

	resp.StatusCode = http.StatusCreated

	return resp, nil
}

func (c *Core) doRouting(ctx context.Context, req *logical.Request) (*logical.Response, error) {
	return c.router.Route(ctx, req)
}

func (c *Core) handleNonLoginRequest(ctx context.Context, req *logical.Request) (retResp *logical.Response, retAuth *logical.Auth, retErr error) {
	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.MountClass = entry.Class
		req.MountAccessor = entry.Accessor
		req.MountPoint = entry.Path
	}

	// Parse request body before CheckToken since req.Data may be used during policy evaluation.
	// Help requests are never streaming - they should return help text, not proxy upstream.
	isStreaming := c.isStreamingRequest(ctx, req.Path) && req.Operation != logical.HelpOperation

	if !isStreaming {
		if err := c.parseRequestBody(req); err != nil {
			return logical.ErrorResponse(logical.ErrBadRequest(err.Error())), nil, err
		}
		req.ClientToken = extractToken(req.HTTPRequest)

		// Transparent operations: implicit auth on non-gateway endpoints.
		// Triggered when there's no explicit X-Warden-Token but an auth path
		// is configured on the namespace. The Authorization: Bearer value is
		// treated as a JWT credential, not a Warden token.
		// Auth role precedence: ?role= query param > X-Warden-Role header > auth method default_role.
		if req.HTTPRequest != nil && req.HTTPRequest.Header.Get("X-Warden-Token") == "" {
			var authPath string
			if ns, _ := namespace.FromContext(ctx); ns != nil {
				authPath = ns.CustomMetadata["auto_auth_path"]
			}
			if authPath != "" {
				var authRole string
				if role, ok := req.Data["role"].(string); ok {
					authRole = role
				} else {
					authRole = req.HTTPRequest.Header.Get("X-Warden-Role")
				}
				if err := c.performImplicitAuth(ctx, req, authPath, authRole); err != nil {
					c.logger.Warn("transparent operations auth failed",
						logger.Err(err),
						logger.String("path", req.Path),
						logger.String("auth_path", authPath),
					)
					return logical.ErrorResponse(logical.ErrUnauthorized(err.Error())), nil, nil
				}
			}
		}
	} else {
		req.Streamed = true

		// Set AuditPath for consistent audit logging between request and response entries.
		// For streaming requests, this is the path relative to the mount point
		// (e.g., "role/operator/gateway/v1/...") before routing transforms req.Path.
		if req.MountPoint != "" {
			req.AuditPath = strings.TrimPrefix(req.Path, req.MountPoint)
		} else {
			req.AuditPath = req.Path
		}

		matchingBackend := c.router.MatchingBackend(ctx, req.Path)
		if matchingBackend == nil {
			c.logger.Warn("no backend mounted at path",
				logger.String("full_req_path", req.HTTPRequest.URL.Path),
				logger.String("relative_req_path", req.Path),
			)
			return logical.ErrorResponse(logical.ErrNotFoundf("no handler for path %q", req.Path)), nil, logical.ErrNotFoundf("no handler for path %q", req.Path)
		}

		// Opt-in body parsing for streaming requests.
		// Backends that implement StreamBodyParser and return true get req.Data
		// populated before policy evaluation. The body is restored after parsing
		// so the streaming handler can still read it.
		if parser, ok := matchingBackend.(logical.StreamBodyParser); ok && parser.ShouldParseStreamBody() {
			if err := c.parseRequestBody(req); err != nil {
				return logical.ErrorResponse(logical.ErrBadRequest(err.Error())), nil, err
			}
		}

		req.ClientToken = matchingBackend.ExtractToken(req.HTTPRequest)

		// Check for unauthenticated paths on streaming backends
		if req.ClientToken == "" {
			if tmp, ok := matchingBackend.(logical.TransparentModeProvider); ok {
				relativePath := req.Path
				if req.MountPoint != "" {
					relativePath = strings.TrimPrefix(req.Path, req.MountPoint)
				}
				if tmp.IsUnauthenticatedPath(relativePath) {
					req.StreamUnauthenticated = true
					c.logger.Trace("unauthenticated streaming request",
						logger.String("path", req.Path),
					)
				}
			}
		}

		// Perform implicit auth if needed
		// Skip if already marked as unauthenticated (handled above)
		if !req.StreamUnauthenticated {
			if isTransparent, role := c.isTransparentRequest(req, matchingBackend); isTransparent {
				if err := c.handleTransparentAuth(ctx, req, matchingBackend, role); err != nil {
					c.logger.Warn("implicit authentication failed",
						logger.Err(err),
						logger.String("path", req.Path),
						logger.String("operation", string(req.Operation)),
						logger.String("request_id", req.RequestID),
					)

					return logical.ErrorResponse(logical.ErrUnauthorized(err.Error())), nil, nil
				}
			}
		}
	}

	var auth *logical.Auth
	var te *logical.TokenEntry

	// Validate the token (non-login requests require authentication)
	// Skip for unauthenticated streaming paths
	if !req.StreamUnauthenticated {
		var ctErr error
		auth, _, te, ctErr = c.CheckToken(ctx, req, false)
		if ctErr != nil {
			c.logger.Warn("error when checking token", logger.Err(ctErr))
			switch {
			case ctErr == ErrInternalError,
				errwrap.Contains(ctErr, ErrInternalError.Error()),
				ctErr == sdklogical.ErrPermissionDenied,
				errwrap.Contains(ctErr, sdklogical.ErrPermissionDenied.Error()):
				switch ctErr.(type) {
				case *multierror.Error:
					retErr = ctErr
				default:
					retErr = multierror.Append(retErr, ctErr)
				}
			default:
				retErr = multierror.Append(retErr, sdklogical.ErrInvalidRequest)
			}

			// Audit the failed request even for internal errors
			auditEntry := c.buildRequestAuditEntry(ctx, req, auth, te, ctErr)
			if _, auditErr := c.auditManager.LogRequest(ctx, auditEntry); auditErr != nil {
				c.logger.Error("failed to audit token check failure",
					logger.String("path", req.Path),
					logger.Err(auditErr),
				)
			}

			// Build the error response
			var resp *logical.Response
			if errwrap.Contains(retErr, ErrInternalError.Error()) {
				resp = nil
			} else {
				resp = logical.ErrorResponse(ctErr)
			}

			// Audit the failed response
			respAuditEntry := c.buildResponseAuditEntry(ctx, req, resp, auth, te, ctErr)
			if _, auditErr := c.auditManager.LogResponse(ctx, respAuditEntry); auditErr != nil {
				c.logger.Error("failed to audit token check failure response",
					logger.String("path", req.Path),
					logger.Err(auditErr),
				)
			}

			if errwrap.Contains(retErr, ErrInternalError.Error()) {
				return nil, auth, retErr
			}
			return resp, auth, retErr
		}
	}

	// On-behalf-of header (X-Warden-On-Behalf-Of): an authenticated
	// principal — typically an MCP concentrator — names the upstream
	// caller it is acting for so per-agent attribution survives the
	// concentrator hop. Recorded with verified=false; the strip-list
	// keeps the header from leaking upstream.
	//
	// Gated on te != nil so an unauthenticated caller on a stream-
	// unauthenticated path can't plant an actor into the response
	// audit entry (which is emitted unconditionally upstream in
	// handleCancelableRequest).
	if te != nil && req.HTTPRequest != nil {
		if obo := strings.TrimSpace(req.HTTPRequest.Header.Get("X-Warden-On-Behalf-Of")); obo != "" {
			if !validActorSubject(obo) {
				// Truncate the offending value so the warn log isn't a vehicle
				// for an attacker-controlled string of arbitrary length. Quote
				// for safe rendering since the input may contain non-ASCII or
				// control bytes that would otherwise corrupt the log.
				logged := obo
				if len(logged) > 64 {
					logged = logged[:64] + "..."
				}
				c.logger.Warn("dropped X-Warden-On-Behalf-Of: invalid subject",
					logger.String("actor", strconv.Quote(logged)),
					logger.String("path", req.Path),
				)
			} else {
				if auth == nil {
					auth = &logical.Auth{}
				}
				auth.Actors = append(auth.Actors, logical.ActorRef{
					Subject:  obo,
					Verified: false,
				})
			}
		}
	}

	// Create an audit trail of the request.
	// Skip for unauthenticated streaming paths (e.g., public PKI certificates) because:
	// 1. No authentication context exists - no token, principal, or policies to audit
	// 2. These paths are explicitly marked as public by the backend
	// 3. High-volume public requests would create audit log noise without security value
	if !req.StreamUnauthenticated {
		auditEntry := c.buildRequestAuditEntry(ctx, req, auth, te, nil)
		if auditOK, auditErr := c.auditManager.LogRequest(ctx, auditEntry); auditErr != nil {
			c.logger.Error("failed to audit request",
				logger.String("path", req.Path),
				logger.Err(auditErr),
			)
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		} else if !auditOK {
			c.logger.Warn("request blocked: no audit devices configured",
				logger.String("path", req.Path),
			)
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		}
	}

	// For streaming requests, mint credentials (unless StreamUnauthenticated)
	if req.Streamed && !req.StreamUnauthenticated {
		if err := c.mintCredentialForRequest(ctx, req, te); err != nil {
			c.logger.Error("failed to mint credential for streaming request",
				logger.Err(err),
				logger.String("path", req.Path),
			)
			return logical.ErrorResponse(err), auth, nil
		}
	}

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

	// A non-login request should not return an auth block
	if resp != nil && resp.Auth != nil {
		c.logger.Error("unexpected Auth response for non-login backend", logger.String("request_path", req.Path))
		retErr = multierror.Append(retErr, ErrInternalError)
		return nil, auth, retErr
	}

	// Access backend returned AccessData → mint credential + let backend format response
	if resp != nil && resp.AccessData != nil {
		ad := resp.AccessData
		if te == nil {
			c.logger.Error("AccessData returned but no token entry", logger.String("path", req.Path))
			retErr = multierror.Append(retErr, ErrInternalError)
			return nil, auth, retErr
		}
		te.CredentialSpec = ad.CredentialSpec
		if err := c.mintCredentialForRequest(ctx, req, te); err != nil {
			c.logger.Error("failed to mint credential for access request",
				logger.Err(err), logger.String("path", req.Path))
			return logical.ErrorResponse(err), auth, nil
		}
		resp.Data = ad.ResponseBuilder(req.Credential)
		resp.AccessData = nil
	}

	// Return the response and error
	if routeErr != nil {
		retErr = multierror.Append(retErr, routeErr)
	}

	if resp != nil {
		resp.MountClass = req.MountClass
	}

	return resp, auth, retErr

}

// 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 for 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
	}

	token := req.ClientToken

	// Inject client IP into context so validateIPBinding can read it
	if req.ClientIP != "" {
		ctx = context.WithValue(ctx, logical.ClientIPKey, req.ClientIP)
	}

	te, err := c.LookupToken(ctx, token)
	if err == nil && te != nil {
		req.ClientTokenAccessor = te.Accessor
		req.ClientTokenID = te.ID
		req.SetTokenEntry(te)
	}
	return nil
}

// isStreamingRequest checks if the request path is a streaming path
func (c *Core) isStreamingRequest(ctx context.Context, path string) bool {
	return c.router.StreamingPath(ctx, path)
}

// parseRequestBody parses query params and JSON body into req.Data
func (c *Core) parseRequestBody(req *logical.Request) error {
	if req.HTTPRequest == nil {
		return nil
	}

	// Initialize Data map
	if req.Data == nil {
		req.Data = make(map[string]any)
	}

	// Parse query params first
	for k, v := range req.HTTPRequest.URL.Query() {
		if len(v) == 1 {
			req.Data[k] = v[0]
		} else {
			req.Data[k] = v
		}
	}

	// For GET/DELETE/HEAD - query params only, no body
	method := req.HTTPRequest.Method
	if method == http.MethodGet || method == http.MethodDelete || method == http.MethodHead {
		return nil
	}

	// Parse body based on Content-Type (overwrites query params with same keys)
	return c.parseBody(req)
}

// parseBody dispatches to the appropriate body parser based on Content-Type.
// Supports application/json and application/x-www-form-urlencoded.
// Unknown content types are silently skipped (body left untouched).
func (c *Core) parseBody(req *logical.Request) error {
	contentType := req.HTTPRequest.Header.Get("Content-Type")

	switch {
	case strings.HasPrefix(contentType, "application/json") || contentType == "":
		return c.parseJSONBody(req)
	case strings.HasPrefix(contentType, "application/x-www-form-urlencoded"):
		return c.parseFormBody(req)
	default:
		return nil
	}
}

// parseJSONBody parses the JSON body of the request into req.Data
func (c *Core) parseJSONBody(req *logical.Request) error {
	if req.HTTPRequest.Body == nil {
		return nil
	}

	contentType := req.HTTPRequest.Header.Get("Content-Type")
	if !strings.HasPrefix(contentType, "application/json") && contentType != "" {
		return nil // Not JSON, skip
	}

	body, err := io.ReadAll(io.LimitReader(req.HTTPRequest.Body, maxRequestBodySize+1))
	if err != nil {
		return fmt.Errorf("failed to read request body: %w", err)
	}
	req.HTTPRequest.Body.Close()

	if int64(len(body)) > maxRequestBodySize {
		return fmt.Errorf("request body exceeds maximum size of %d bytes", maxRequestBodySize)
	}

	// Restore body for potential re-reading (audit, streaming, etc.)
	req.HTTPRequest.Body = io.NopCloser(bytes.NewReader(body))

	if len(body) == 0 {
		return nil
	}

	return json.Unmarshal(body, &req.Data)
}

// parseFormBody parses application/x-www-form-urlencoded body into req.Data.
// Like parseJSONBody, it reads the body with a size limit and restores it for re-reading.
func (c *Core) parseFormBody(req *logical.Request) error {
	if req.HTTPRequest.Body == nil {
		return nil
	}

	body, err := io.ReadAll(io.LimitReader(req.HTTPRequest.Body, maxRequestBodySize+1))
	if err != nil {
		return fmt.Errorf("failed to read request body: %w", err)
	}
	req.HTTPRequest.Body.Close()

	if int64(len(body)) > maxRequestBodySize {
		return fmt.Errorf("request body exceeds maximum size of %d bytes", maxRequestBodySize)
	}

	// Restore body for potential re-reading (audit, streaming, etc.)
	req.HTTPRequest.Body = io.NopCloser(bytes.NewReader(body))

	if len(body) == 0 {
		return nil
	}

	values, err := url.ParseQuery(string(body))
	if err != nil {
		return fmt.Errorf("failed to parse form body: %w", err)
	}

	for k, v := range values {
		if len(v) == 1 {
			req.Data[k] = v[0]
		} else {
			req.Data[k] = v
		}
	}

	return nil
}

// mintCredentialForRequest mints credentials for requests using the credential manager
func (c *Core) mintCredentialForRequest(ctx context.Context, req *logical.Request, te *logical.TokenEntry) error {
	if te == nil {
		return logical.ErrInternal("cannot mint credential since token entry is nil")
	}
	if te.CredentialSpec == "" {
		c.logger.Debug("no credential spec for token",
			logger.String("token_id", te.ID),
		)
		return logical.ErrBadRequest("cannot mint credential since no credential spec is bound to the token")
	}

	// Skip if credential manager not initialized
	if c.credentialManager == nil {
		return logical.ErrInternal("credential manager not initialized")
	}

	// Validate credential spec exists before attempting to issue
	if !c.credentialManager.SpecExists(ctx, te.CredentialSpec) {
		c.logger.Warn("credential spec not found or disabled",
			logger.String("token_id", te.ID),
			logger.String("spec_name", te.CredentialSpec),
		)
		return logical.ErrBadRequestf("credential spec %q not found or disabled", te.CredentialSpec)
	}

	// Calculate token TTL for cache duration
	var tokenTTL time.Duration
	if !te.ExpireAt.IsZero() {
		tokenTTL = time.Until(te.ExpireAt)
		if tokenTTL <= 0 {
			return fmt.Errorf("token has expired")
		}
	} else {
		// Default TTL for tokens without expiration
		tokenTTL = 1 * time.Hour
	}

	// Issue credential using the credential manager
	// Credentials are cache-only (not persisted) - ExpirationEntry handles lease revocation
	cred, err := c.credentialManager.IssueCredential(ctx, te.ID, te.CredentialSpec, tokenTTL)
	if err != nil {
		return fmt.Errorf("failed to issue credential: %w", err)
	}

	// Inject credential into request
	req.Credential = cred

	return nil
}

// It checks X-Warden-Token header first, then falls back to Authorization: Bearer.
func extractToken(r *http.Request) string {
	// Try X-Warden-Token header first
	if token := r.Header.Get("X-Warden-Token"); token != "" {
		return token
	}
	// Fall back to Authorization: Bearer
	authHeader := r.Header.Get("Authorization")
	if len(authHeader) > 7 && strings.EqualFold(authHeader[:7], "Bearer ") {
		return authHeader[7:]
	}
	return ""
}

// actorSubjectPattern enforces the wire-format of X-Warden-On-Behalf-Of
// values: ASCII alphanumerics plus a small punctuation set, 1-256 bytes.
// Comma is deliberately excluded so the header carries a single actor.
var actorSubjectPattern = regexp.MustCompile(`^[A-Za-z0-9._:@/+\-]{1,256}$`)

// validActorSubject reports whether s is acceptable as an on-behalf-of
// actor subject. Rejects empty strings, oversized values, control bytes,
// and any character outside the allowed set.
func validActorSubject(s string) bool {
	return actorSubjectPattern.MatchString(s)
}

// isTransparentRequest checks if this request needs implicit authentication.
// Returns true and the role name if the backend supports implicit auth,
// has an auth config, and the path is a gateway path.
// The role may be empty if no role is in the URL and no default role is configured.
func (c *Core) isTransparentRequest(req *logical.Request, backend logical.Backend) (bool, string) {
	// Check if backend supports implicit auth
	tmp, ok := backend.(logical.TransparentModeProvider)
	if !ok {
		return false, ""
	}

	// Check if auth config is set
	if !tmp.IsTransparentMode() {
		return false, ""
	}

	// Get the path relative to the mount point for pattern matching
	// req.Path at this point still includes the mount prefix (e.g., "vault-auto/role/terraform/gateway/...")
	// The transparent pattern expects paths relative to the mount (e.g., "role/terraform/gateway/...")
	relativePath := req.Path
	if req.MountPoint != "" {
		relativePath = strings.TrimPrefix(req.Path, req.MountPoint)
	}

	// Check if this path should trigger transparent auth (delegated to the backend).
	// Streaming backends match gateway paths; access backends match access/ paths.
	if !tmp.IsTransparentPath(relativePath) {
		return false, ""
	}

	// Extract auth role from path (may return default auth role or empty string)
	authRole := tmp.GetAuthRole(relativePath, req)

	// Fallback: extract role from protocol-specific request headers.
	// Used by SigV4-compatible providers (AWS, Alibaba Cloud, Scaleway) where
	// the role is embedded in the Authorization header's Credential field.
	if authRole == "" {
		if ext, ok := backend.(logical.TransparentAuthRoleExtractor); ok && req.HTTPRequest != nil {
			role, isTransparent := ext.GetAuthRoleFromRequest(req.HTTPRequest)
			if !isTransparent {
				return false, "" // Not a transparent request (e.g., explicit auth with Warden token)
			}
			authRole = role
		}
	}

	// X-Warden-Role header overrides any role parsed from the path or chosen
	// by the backend's default-role fallback. HandleRequest also rewrites the
	// role segment of req.Path to match (see applyRoleHeader), so the auth
	// resolver and the streaming backend always agree on which role is in
	// force.
	if req.HTTPRequest != nil {
		if headerRole := req.HTTPRequest.Header.Get("X-Warden-Role"); headerRole != "" {
			authRole = headerRole
		}
	}

	return true, authRole
}

// handleTransparentAuth performs implicit authentication for gateway requests.
// It delegates to performImplicitAuth for credential detection and login.
// The authRole may be empty — the auth method's default_role config provides the fallback.
func (c *Core) handleTransparentAuth(ctx context.Context, req *logical.Request, backend logical.Backend, authRole string) error {
	tmp := backend.(logical.TransparentModeProvider)
	autoAuthPath := tmp.GetAutoAuthPath()
	return c.performImplicitAuth(ctx, req, autoAuthPath, authRole)
}

// performImplicitAuth performs implicit authentication by detecting the credential type
// (JWT bearer token or TLS client certificate), looking up cached tokens, and performing
// login if needed. Used by both gateway requests and transparent operations.
//
// Transparent mode precedence rules:
//
//	Credential (first match wins):
//	  1. X-Warden-Token header → explicit auth, implicit auth skipped entirely
//	  2. TLS client certificate (via X-SSL-Client-Cert) → implicit cert auth
//	  3. Authorization: Bearer eyJ... → implicit JWT auth
//
//	Auth path (config only, no per-request override):
//	  - Gateway: provider auto_auth_path config
//	  - Operations: namespace auto_auth_path metadata
//
//	Auth role (first match wins):
//	  1. X-Warden-Role header
//	  2. URL path /role/{name}/gateway/... (gateway only)
//	  3. Provider default_role config (gateway only)
//	  4. Auth method default_role config (login-time fallback)
//
// Uses singleflight to prevent duplicate token creation when concurrent requests
// arrive with the same credential+authRole combination.
//
// IMPORTANT: The auth role is validated when reusing cached tokens. A token created for
// credential+Role1 cannot be reused for credential+Role2, as they may have different policies.
func (c *Core) performImplicitAuth(ctx context.Context, req *logical.Request, autoAuthPath, authRole string) error {
	// Mark request as implicit auth
	req.Transparent = true

	// Inject client IP into context so validateIPBinding can read it
	// during cached token lookups. Without this, implicitly-authed tokens
	// bypass IP binding because the context has no client IP.
	if req.ClientIP != "" {
		ctx = context.WithValue(ctx, logical.ClientIPKey, req.ClientIP)
	}

	// Detect credential type: client certificate or JWT bearer token
	var singleflightKey string
	var lookupFunc func() (*TokenEntry, error)
	var loginData map[string]any
	var credType string // "cert" or "jwt" — used for post-login lookup with resolved role
	var credKey string  // fingerprint or JWT — used for post-login lookup with resolved role

	// Check for client certificate first (forwarded from LB or direct TLS)
	clientCert := extractTransparentClientCert(req)

	switch {
	case clientCert != nil:
		// Certificate-based transparent auth
		hash := sha256.Sum256(clientCert.Raw)
		fingerprint := hex.EncodeToString(hash[:])
		sfHash := sha256.Sum256([]byte("cert:" + fingerprint + ":" + authRole))
		singleflightKey = hex.EncodeToString(sfHash[:])
		loginData = map[string]any{"role": authRole}
		credType = "cert"
		credKey = fingerprint
		lookupFunc = func() (*TokenEntry, error) {
			return c.LookupCertTokenWithRole(ctx, fingerprint, authRole)
		}

	case strings.HasPrefix(req.ClientToken, "eyJ"):
		// JWT-based transparent auth
		clientToken := req.ClientToken
		jwtHash := sha256.Sum256([]byte(clientToken + ":" + authRole))
		singleflightKey = hex.EncodeToString(jwtHash[:])
		loginData = map[string]any{"jwt": clientToken, "role": authRole}
		credType = "jwt"
		credKey = clientToken
		lookupFunc = func() (*TokenEntry, error) {
			return c.LookupJWTTokenWithRole(ctx, clientToken, authRole)
		}

	default:
		return fmt.Errorf("no valid credential for implicit auth (need JWT bearer token or TLS client certificate)")
	}

	// Step 1: Try cached lookup
	// Note: cached tokens are returned without re-checking certificate revocation.
	// This is an accepted design trade-off — revocation is a best-effort signal and
	// the token TTL (default 1h) bounds the exposure window. Explicit login requests
	// always perform full revocation checks. To reduce exposure for revoked certs,
	// configure a shorter token_ttl on cert roles.
	te, err := lookupFunc()
	if err == nil && te != nil {
		req.SetTokenEntry(te)
		// For cert auth, ClientToken is empty since the client only sends a certificate.
		// Set it to the token ID so downstream policy checks don't reject it.
		if req.ClientToken == "" {
			req.ClientToken = te.ID
		}
		return nil
	}

	// IP binding violation means the token exists but the client IP doesn't match.
	// Return the error immediately — don't fall through and create a new token.
	if err == ErrOriginViolation {
		return err
	}

	// Step 2: Token not found — perform implicit auth with singleflight
	result, err, shared := c.transparentAuthGroup.Do(singleflightKey, func() (interface{}, error) {
		// Double-check: another goroutine may have just created the token
		checkTE, lookupErr := lookupFunc()
		if lookupErr == nil && checkTE != nil {
			return checkTE, nil
		}
		// Same as above: IP binding violation should not trigger re-login
		if lookupErr == ErrOriginViolation {
			return nil, lookupErr
		}

		// Build login request to the auto-auth path (e.g., auth/jwt/login or auth/cert/login)
		loginPath := strings.TrimSuffix(autoAuthPath, "/") + "/login"
		loginReq := &logical.Request{
			Operation:   logical.UpdateOperation,
			Path:        loginPath,
			HTTPRequest: req.HTTPRequest, // Carries TLS info and forwarded cert context
			Data:        loginData,
			ClientIP:    req.ClientIP,
			RequestID:   req.RequestID,
		}

		// Perform the login request
		loginResp, _, loginErr := c.handleLoginRequest(ctx, loginReq, true)
		if loginErr != nil {
			return nil, loginErr
		}

		if loginResp != nil && loginResp.Err != nil {
			return nil, loginResp.Err
		}

		if loginResp == nil || loginResp.Auth == nil {
			return nil, fmt.Errorf("implicit auth returned no auth data")
		}

		// Lookup the newly created token using the resolved role from the login response.
		// This handles default_role fallback: the original role may be empty, but the
		// auth method resolves it to default_role. The token is stored with the resolved role.
		resolvedRole := loginResp.Auth.RoleName
		var newTE *TokenEntry
		var postErr error
		switch credType {
		case "cert":
			newTE, postErr = c.LookupCertTokenWithRole(ctx, credKey, resolvedRole)
		case "jwt":
			newTE, postErr = c.LookupJWTTokenWithRole(ctx, credKey, resolvedRole)
		}
		if postErr != nil {
			return nil, fmt.Errorf("failed to lookup created token: %w", postErr)
		}

		return newTE, nil
	})

	if err != nil {
		c.transparentAuthGroup.Forget(singleflightKey)
		return err
	}

	te = result.(*TokenEntry)
	_ = shared

	req.SetTokenEntry(te)
	// Ensure ClientToken is set so downstream policy checks (fetchCBPAndTokenEntry)
	// don't short-circuit on the empty-token guard. For JWT transparent auth the
	// JWT string is already in ClientToken, but for cert transparent auth there is
	// no token in the request — only a certificate.
	if req.ClientToken == "" {
		req.ClientToken = te.ID
	}
	return nil
}

// extractTransparentClientCert extracts the client certificate from a request.
// The cert forwarding middleware stores it in the request context after
// extracting it from either a forwarding header (X-SSL-Client-Cert or
// X-Forwarded-Client-Cert from a trusted proxy) or the TLS connection
// state (r.TLS.PeerCertificates for direct mTLS / LB passthrough).
// On cluster-forwarded requests (standby → leader), the header is
// re-forwarded and re-parsed, so ForwardedClientCert works correctly
// for all paths.
func extractTransparentClientCert(req *logical.Request) *x509.Certificate {
	if req.HTTPRequest == nil {
		return nil
	}

	return listener.ForwardedClientCert(req.HTTPRequest.Context())
}
