middleware

package
v1.1.11 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 5, 2025 License: MIT Imports: 16 Imported by: 1

Documentation

Overview

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Package middleware provides a collection of HTTP middleware components for the SRouter framework. These middleware components can be used to add functionality such as logging, recovery from panics, authentication, request timeouts, and more to your HTTP handlers.

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Package middleware provides a collection of HTTP middleware components for the SRouter framework.

Index

Constants

This section is empty.

Variables

View Source
var (
	// From middleware.go
	Recovery    = recovery
	Logging     = logging
	MaxBodySize = maxBodySize
	Timeout     = timeout
	CORS        = cors

	// From ip.go
	ClientIPMiddleware = clientIPMiddleware // Renamed from ClientIPMiddleware

	// From trace.go
	Trace           = traceMiddleware           // Renamed from TraceMiddleware
	TraceWithConfig = traceMiddlewareWithConfig // Renamed from TraceMiddlewareWithConfig
)

Public middleware constructors exposed via variables. The actual implementations are kept private within their respective files.

View Source
var ClientIPKey = clientIPKey{}

ClientIPKey is the key used to store the client IP in the request context

Functions

func AddTraceIDToRequest added in v1.0.0

func AddTraceIDToRequest(r *http.Request, traceID string) *http.Request

AddTraceIDToRequest adds a trace ID to the request context. This is useful for testing or for manually setting a trace ID.

func Authentication

func Authentication[T comparable, U any](
	authFunc func(*http.Request) (T, bool),
) common.Middleware

Authentication is a middleware that checks if a request is authenticated using a simple auth function. T is the User ID type (comparable), U is the User object type (any). It allows for custom authentication logic to be provided as a simple function.

func AuthenticationBool added in v1.0.0

func AuthenticationBool[T comparable, U any](
	authFunc func(*http.Request) bool,
	flagName string,
) common.Middleware

AuthenticationBool is a middleware that checks if a request is authenticated using a simple auth function. It allows for custom authentication logic to be provided as a simple function that returns a boolean. It adds a boolean flag to the SRouterContext if authentication is successful. T is the User ID type (comparable), U is the User object type (any).

func AuthenticationWithProvider

func AuthenticationWithProvider[T comparable, U any](
	provider AuthProvider[T],
	logger *zap.Logger,
) common.Middleware

AuthenticationWithProvider is a middleware that checks if a request is authenticated using the provided auth provider. If authentication fails, it returns a 401 Unauthorized response. This middleware allows for flexible authentication mechanisms by accepting any AuthProvider implementation. T is the User ID type (comparable), U is the User object type (any).

This implementation uses the SRouterContext approach to store the authenticated user ID, which avoids deep nesting of context values by using a single wrapper structure. The type parameters allow for type-safe access to the user ID without type assertions.

func AuthenticationWithUser added in v1.0.0

func AuthenticationWithUser[T comparable, U any](
	authFunc func(*http.Request) (*U, error),
) common.Middleware

AuthenticationWithUser is a middleware that uses a custom auth function that returns a user object and adds it to the SRouterContext if authentication is successful. T is the User ID type (comparable), U is the User object type (any).

func AuthenticationWithUserProvider added in v1.0.0

func AuthenticationWithUserProvider[T comparable, U any](
	provider UserAuthProvider[U],
	logger *zap.Logger,
) common.Middleware

AuthenticationWithUserProvider is a middleware that uses an auth provider that returns a user object and adds it to the SRouterContext if authentication is successful. T is the User ID type (comparable), U is the User object type (any).

func ClientIP added in v1.0.0

func ClientIP(r *http.Request) string

ClientIP extracts the client IP from the request context First checks the SRouterContext, then falls back to the legacy context key

func ClientIPMiddlewareGeneric added in v1.0.9

func ClientIPMiddlewareGeneric[T comparable, U any](config *IPConfig) func(http.Handler) http.Handler

ClientIPMiddlewareGeneric creates a middleware that extracts the client IP from the request and adds it to the SRouterContext with specific type parameters. T is the User ID type (comparable), U is the User object type (any).

This implementation uses the SRouterContext approach with specific type parameters, making it useful when working with strongly typed middleware chains. It stores the IP address only in the SRouterContext wrapper, avoiding context nesting issues.

func CreateRateLimitMiddleware added in v1.0.2

func CreateRateLimitMiddleware[T comparable, U any](
	bucketName string,
	limit int,
	window time.Duration,
	strategy RateLimitStrategy,
	userIDFromUser func(U) T,
	userIDToString func(T) string,
	logger *zap.Logger,
) func(http.Handler) http.Handler

CreateRateLimitMiddleware is a helper function to create a rate limit middleware with generic type parameters This function is useful when you want to create a rate limit middleware with a specific user ID type and user type The type parameter T represents the user ID type, which can be any comparable type. The type parameter U represents the user type, which can be any type.

func GetClientIP added in v1.0.9

func GetClientIP[T comparable, U any](ctx context.Context) (string, bool)

GetClientIP retrieves a client IP from the router context

func GetClientIPFromRequest added in v1.0.9

func GetClientIPFromRequest[T comparable, U any](r *http.Request) (string, bool)

GetClientIPFromRequest is a convenience function to get the client IP from a request

func GetFlag added in v1.0.9

func GetFlag[T comparable, U any](ctx context.Context, name string) (bool, bool)

GetFlag retrieves a flag from the router context

func GetFlagFromRequest added in v1.0.9

func GetFlagFromRequest[T comparable, U any](r *http.Request, name string) (bool, bool)

GetFlagFromRequest is a convenience function to get a flag from a request

func GetTraceID added in v1.0.0

func GetTraceID(r *http.Request) string

GetTraceID extracts the trace ID from the request context. Returns an empty string if no trace ID is found.

func GetTraceIDFromContext added in v1.0.0

func GetTraceIDFromContext(ctx context.Context) string

GetTraceIDFromContext extracts the trace ID from a context. It first tries to find the trace ID in the SRouterContext using the flags map, and falls back to the legacy context key approach for backward compatibility. Returns an empty string if no trace ID is found.

func GetUser added in v1.0.0

func GetUser[T comparable, U any](ctx context.Context) (*U, bool)

GetUser retrieves a user from the router context

func GetUserFromRequest added in v1.0.9

func GetUserFromRequest[T comparable, U any](r *http.Request) (*U, bool)

GetUserFromRequest is a convenience function to get the user from a request

func GetUserID added in v1.0.0

func GetUserID[T comparable, U any](ctx context.Context) (T, bool)

GetUserID retrieves a user ID from the router context

func GetUserIDFromRequest added in v1.0.9

func GetUserIDFromRequest[T comparable, U any](r *http.Request) (T, bool)

GetUserIDFromRequest is a convenience function to get the user ID from a request

func NewAPIKeyMiddleware

func NewAPIKeyMiddleware[T comparable, U any](
	validKeys map[string]T,
	header, query string,
	logger *zap.Logger,
) common.Middleware

NewAPIKeyMiddleware creates a middleware that uses API Key Authentication. T is the User ID type (comparable), U is the User object type (any).

func NewAPIKeyWithUserMiddleware added in v1.0.0

func NewAPIKeyWithUserMiddleware[T comparable, U any](
	getUserFunc func(key string) (*U, error),
	header, query string,
	logger *zap.Logger,
) common.Middleware

NewAPIKeyWithUserMiddleware creates a middleware that uses API Key Authentication NewAPIKeyWithUserMiddleware creates a middleware that uses API Key Authentication and returns a user object, adding it to the SRouterContext. T is the User ID type (comparable), U is the User object type (any).

func NewBearerTokenMiddleware

func NewBearerTokenMiddleware[T comparable, U any](
	validTokens map[string]T,
	logger *zap.Logger,
) common.Middleware

NewBearerTokenMiddleware creates a middleware that uses Bearer Token Authentication. T is the User ID type (comparable), U is the User object type (any).

func NewBearerTokenValidatorMiddleware

func NewBearerTokenValidatorMiddleware[T comparable, U any](
	validator func(string) (T, bool),
	logger *zap.Logger,
) common.Middleware

NewBearerTokenValidatorMiddleware creates a middleware that uses Bearer Token Authentication NewBearerTokenValidatorMiddleware creates a middleware that uses Bearer Token Authentication with a custom validator function. T is the User ID type (comparable), U is the User object type (any).

func NewBearerTokenWithUserMiddleware added in v1.0.0

func NewBearerTokenWithUserMiddleware[T comparable, U any](
	getUserFunc func(token string) (*U, error),
	logger *zap.Logger,
) common.Middleware

NewBearerTokenWithUserMiddleware creates a middleware that uses Bearer Token Authentication NewBearerTokenWithUserMiddleware creates a middleware that uses Bearer Token Authentication and returns a user object, adding it to the SRouterContext. T is the User ID type (comparable), U is the User object type (any).

func RateLimit added in v1.0.0

func RateLimit[T comparable, U any](config *RateLimitConfig[T, U], limiter RateLimiter, logger *zap.Logger) func(http.Handler) http.Handler

RateLimit creates a middleware that enforces rate limits using generic type parameters The type parameter T represents the user ID type, which can be any comparable type. The type parameter U represents the user type, which can be any type.

func WithClientIP added in v1.0.9

func WithClientIP[T comparable, U any](ctx context.Context, ip string) context.Context

WithClientIP adds a client IP to the context

func WithFlag added in v1.0.9

func WithFlag[T comparable, U any](ctx context.Context, name string, value bool) context.Context

WithFlag adds a flag to the context

func WithSRouterContext added in v1.0.9

func WithSRouterContext[T comparable, U any](ctx context.Context, rc *SRouterContext[T, U]) context.Context

WithSRouterContext adds or updates the router context in the request context

func WithTraceID added in v1.0.9

func WithTraceID[T comparable, U any](ctx context.Context, traceID string) context.Context

WithTraceID adds a trace ID to the SRouterContext in the provided context. If no SRouterContext exists, one will be created.

This function is part of the SRouterContext approach for storing values in the context, which avoids deep nesting of context values by using a single wrapper structure.

func WithTransaction added in v1.1.5

func WithTransaction[T comparable, U any](ctx context.Context, tx DatabaseTransaction) context.Context

WithTransaction adds a database transaction to the context

func WithUser added in v1.0.9

func WithUser[T comparable, U any](ctx context.Context, user *U) context.Context

WithUser adds a user to the context

func WithUserID added in v1.0.9

func WithUserID[T comparable, U any](ctx context.Context, userID T) context.Context

WithUserID adds a user ID to the context

Types

type APIKeyProvider

type APIKeyProvider[T comparable] struct {
	ValidKeys map[string]T // key -> user ID
	Header    string       // header name (e.g., "X-API-Key")
	Query     string       // query parameter name (e.g., "api_key")
}

APIKeyProvider provides API Key Authentication. It can validate API keys provided in a header or query parameter. The type parameter T represents the user ID type, which can be any comparable type.

func (*APIKeyProvider[T]) Authenticate

func (p *APIKeyProvider[T]) Authenticate(r *http.Request) (T, bool)

Authenticate authenticates a request using API Key Authentication. It checks for the API key in either the specified header or query parameter and validates it against the stored valid keys. Returns the user ID if authentication is successful, the zero value of T and false otherwise.

type APIKeyUserAuthProvider added in v1.0.0

type APIKeyUserAuthProvider[T any] struct {
	GetUserFunc func(key string) (*T, error)
	Header      string // header name (e.g., "X-API-Key")
	Query       string // query parameter name (e.g., "api_key")
}

APIKeyUserAuthProvider provides API Key Authentication with user object return.

func (*APIKeyUserAuthProvider[T]) AuthenticateUser added in v1.0.0

func (p *APIKeyUserAuthProvider[T]) AuthenticateUser(r *http.Request) (*T, error)

AuthenticateUser authenticates a request using API Key Authentication. It checks for the API key in either the specified header or query parameter and validates it using the GetUserFunc. Returns the user object if authentication is successful, nil and an error otherwise.

type AuthProvider

type AuthProvider[T comparable] interface {
	// Authenticate authenticates a request and returns the user ID if authentication is successful.
	// It examines the request for authentication credentials (such as headers, cookies, or query parameters)
	// and validates them according to the provider's implementation.
	// Returns the user ID if the request is authenticated, the zero value of T otherwise.
	Authenticate(r *http.Request) (T, bool)
}

AuthProvider defines an interface for authentication providers. Different authentication mechanisms can implement this interface to be used with the AuthenticationWithProvider middleware. The framework includes several implementations: BasicAuthProvider, BearerTokenProvider, and APIKeyProvider. The type parameter T represents the user ID type, which can be any comparable type.

type BasicUserAuthProvider added in v1.0.0

type BasicUserAuthProvider[T any] struct {
	GetUserFunc func(username, password string) (*T, error)
}

BasicUserAuthProvider provides HTTP Basic Authentication with user object return.

func (*BasicUserAuthProvider[T]) AuthenticateUser added in v1.0.0

func (p *BasicUserAuthProvider[T]) AuthenticateUser(r *http.Request) (*T, error)

AuthenticateUser authenticates a request using HTTP Basic Authentication. It extracts the username and password from the Authorization header and validates them using the GetUserFunc. Returns the user object if authentication is successful, nil and an error otherwise.

type BearerTokenProvider

type BearerTokenProvider[T comparable] struct {
	ValidTokens map[string]T                 // token -> user ID
	Validator   func(token string) (T, bool) // optional token validator
}

BearerTokenProvider provides Bearer Token Authentication. It can validate tokens against a predefined map or using a custom validator function. The type parameter T represents the user ID type, which can be any comparable type.

func (*BearerTokenProvider[T]) Authenticate

func (p *BearerTokenProvider[T]) Authenticate(r *http.Request) (T, bool)

Authenticate authenticates a request using Bearer Token Authentication. It extracts the token from the Authorization header and validates it using either the validator function (if provided) or the ValidTokens map. Returns the user ID if authentication is successful, the zero value of T and false otherwise.

type BearerTokenUserAuthProvider added in v1.0.0

type BearerTokenUserAuthProvider[T any] struct {
	GetUserFunc func(token string) (*T, error)
}

BearerTokenUserAuthProvider provides Bearer Token Authentication with user object return.

func (*BearerTokenUserAuthProvider[T]) AuthenticateUser added in v1.0.0

func (p *BearerTokenUserAuthProvider[T]) AuthenticateUser(r *http.Request) (*T, error)

AuthenticateUser authenticates a request using Bearer Token Authentication. It extracts the token from the Authorization header and validates it using the GetUserFunc. Returns the user object if authentication is successful, nil and an error otherwise.

type CORSOptions added in v1.1.6

type CORSOptions struct {
	Origins          []string
	Methods          []string
	Headers          []string
	ExposeHeaders    []string // Headers the browser is allowed to access
	AllowCredentials bool     // Whether to allow credentials (cookies, authorization headers)
	MaxAge           time.Duration
}

type DatabaseTransaction added in v1.1.5

type DatabaseTransaction interface {
	Commit() error
	Rollback() error
	SavePoint(name string) error
	RollbackTo(name string) error
	// GetDB returns the underlying GORM DB instance for direct use when needed.
	GetDB() *gorm.DB
}

DatabaseTransaction defines an interface for essential transaction control methods. This allows mocking transaction behavior for testing purposes.

func GetTransaction added in v1.1.5

func GetTransaction[T comparable, U any](ctx context.Context) (DatabaseTransaction, bool)

GetTransaction retrieves a database transaction from the router context

func GetTransactionFromRequest added in v1.1.5

func GetTransactionFromRequest[T comparable, U any](r *http.Request) (DatabaseTransaction, bool)

GetTransactionFromRequest is a convenience function to get the transaction from a request

type GormTransactionWrapper added in v1.1.5

type GormTransactionWrapper struct {
	DB *gorm.DB
}

GormTransactionWrapper wraps a *gorm.DB instance to implement the DatabaseTransaction interface. This is necessary because GORM's methods like Commit return *gorm.DB for chaining, which doesn't match the interface signature aiming for simple error returns.

func NewGormTransactionWrapper added in v1.1.5

func NewGormTransactionWrapper(tx *gorm.DB) *GormTransactionWrapper

NewGormTransactionWrapper creates a new wrapper around a GORM transaction.

func (*GormTransactionWrapper) Commit added in v1.1.5

func (w *GormTransactionWrapper) Commit() error

Commit implements the DatabaseTransaction interface.

func (*GormTransactionWrapper) GetDB added in v1.1.5

func (w *GormTransactionWrapper) GetDB() *gorm.DB

GetDB returns the underlying *gorm.DB instance.

func (*GormTransactionWrapper) Rollback added in v1.1.5

func (w *GormTransactionWrapper) Rollback() error

Rollback implements the DatabaseTransaction interface.

func (*GormTransactionWrapper) RollbackTo added in v1.1.5

func (w *GormTransactionWrapper) RollbackTo(name string) error

RollbackTo implements the DatabaseTransaction interface.

func (*GormTransactionWrapper) SavePoint added in v1.1.5

func (w *GormTransactionWrapper) SavePoint(name string) error

SavePoint implements the DatabaseTransaction interface.

type IDGenerator added in v1.1.2

type IDGenerator struct {
	// contains filtered or unexported fields
}

IDGenerator provides efficient generation of trace IDs by precomputing them

func GetDefaultGenerator added in v1.1.2

func GetDefaultGenerator() *IDGenerator

GetDefaultGenerator returns the default singleton IDGenerator

func NewIDGenerator added in v1.1.2

func NewIDGenerator(bufferSize int) *IDGenerator

NewIDGenerator creates a new IDGenerator with the specified buffer size

func (*IDGenerator) GetID added in v1.1.2

func (g *IDGenerator) GetID() string

GetID returns a precomputed UUID from the channel. This is significantly more efficient than generating UUIDs on-demand since the generation happens in a background goroutine and is batched. The channel acts as a buffer, ensuring there's always a pool of IDs ready. This method will block if the channel is empty until a UUID becomes available.

func (*IDGenerator) GetIDNonBlocking added in v1.1.2

func (g *IDGenerator) GetIDNonBlocking() string

GetIDNonBlocking attempts to get a precomputed UUID from the channel without blocking. If the channel is empty (which should be rare with proper sizing), it will generate a new UUID on the spot rather than waiting. This ensures requests are never delayed even during extreme traffic spikes.

type IPConfig added in v1.0.0

type IPConfig struct {
	// Source specifies where to extract the client IP from
	Source IPSourceType

	// CustomHeader is the name of the custom header to use when Source is IPSourceCustomHeader
	CustomHeader string

	// TrustProxy determines whether to trust proxy headers like X-Forwarded-For
	// If false, RemoteAddr will be used as a fallback for all sources
	TrustProxy bool
}

IPConfig defines configuration for IP extraction

func DefaultIPConfig added in v1.0.0

func DefaultIPConfig() *IPConfig

DefaultIPConfig returns the default IP configuration

type IPSourceType added in v1.0.0

type IPSourceType string

IPSourceType defines the source for client IP addresses

const (
	// IPSourceRemoteAddr uses the request's RemoteAddr field
	IPSourceRemoteAddr IPSourceType = "remote_addr"

	// IPSourceXForwardedFor uses the X-Forwarded-For header
	IPSourceXForwardedFor IPSourceType = "x_forwarded_for"

	// IPSourceXRealIP uses the X-Real-IP header
	IPSourceXRealIP IPSourceType = "x_real_ip"

	// IPSourceCustomHeader uses a custom header specified in the configuration
	IPSourceCustomHeader IPSourceType = "custom_header"
)

type Middleware

type Middleware = common.Middleware

Middleware is an alias for the common.Middleware type. It represents a function that wraps an http.Handler to provide additional functionality.

func Chain

func Chain(middlewares ...Middleware) Middleware

Chain chains multiple middlewares together into a single middleware. The middlewares are applied in reverse order, so the first middleware in the list will be the outermost wrapper (the first to process the request and the last to process the response).

type RateLimitConfig added in v1.0.0

type RateLimitConfig[T comparable, U any] struct {
	// Unique identifier for this rate limit bucket
	// If multiple routes/subrouters share the same BucketName, they share the same rate limit
	BucketName string

	// Maximum number of requests allowed in the time window
	Limit int

	// Time window for the rate limit (e.g., 1 minute, 1 hour)
	Window time.Duration

	// Strategy for identifying clients (IP, User, Custom)
	// - "ip": Use client IP address
	// - "user": Use authenticated user ID
	// - "custom": Use a custom key extractor
	Strategy RateLimitStrategy

	// Function to extract user ID from user object (only used when Strategy is StrategyUser)
	// This allows for efficient user ID extraction without trying multiple types
	UserIDFromUser func(U) T

	// Function to convert user ID to string (only used when Strategy is StrategyUser)
	// This allows for efficient user ID conversion without type assertions
	UserIDToString func(T) string

	// Custom key extractor function (used when Strategy is "custom")
	// This allows for complex rate limiting scenarios
	KeyExtractor func(*http.Request) (string, error)

	// Response to send when rate limit is exceeded
	// If nil, a default 429 Too Many Requests response is sent
	ExceededHandler http.Handler
}

RateLimitConfig defines configuration for rate limiting with generic type parameters The type parameter T represents the user ID type, which can be any comparable type. The type parameter U represents the user type, which can be any type.

type RateLimitStrategy added in v1.0.0

type RateLimitStrategy int
const (
	// StrategyIP uses the client's IP address as the key for rate limiting
	StrategyIP RateLimitStrategy = iota
	// StrategyUser uses the authenticated user's ID as the key for rate limiting
	StrategyUser
	// StrategyCustom uses a custom key extractor function for rate limiting
	StrategyCustom
)

type RateLimiter added in v1.0.0

type RateLimiter interface {
	// Allow checks if a request is allowed based on the key and rate limit config
	// Returns true if the request is allowed, false otherwise
	// Also returns the number of remaining requests and time until reset
	Allow(key string, limit int, window time.Duration) (bool, int, time.Duration)
}

RateLimiter defines the interface for rate limiting algorithms

type SRouterContext added in v1.0.9

type SRouterContext[T comparable, U any] struct {
	// User ID and User object storage
	UserID T
	User   *U

	// Trace ID for tracing
	TraceID string

	// Client IP address
	ClientIP string

	// Database transaction
	Transaction DatabaseTransaction

	// Track which fields are set
	UserIDSet      bool
	UserSet        bool
	ClientIPSet    bool
	TraceIDSet     bool
	TransactionSet bool

	// Additional flags
	Flags map[string]bool
}

SRouterContext holds all values that SRouter adds to request contexts. It allows storing multiple types of values with only a single level of context nesting, solving the problem of deep context nesting which occurs when multiple middleware components each add their own values to the context.

T is the User ID type (comparable), U is the User object type (any). This structure centralizes all context values that middleware components need to store or access, providing a cleaner and more efficient approach than using multiple separate context keys.

func EnsureSRouterContext added in v1.0.9

func EnsureSRouterContext[T comparable, U any](ctx context.Context) (*SRouterContext[T, U], context.Context)

EnsureSRouterContext retrieves or creates a router context

func GetSRouterContext added in v1.0.9

func GetSRouterContext[T comparable, U any](ctx context.Context) (*SRouterContext[T, U], bool)

GetSRouterContext retrieves the router context from a request context

func NewSRouterContext added in v1.0.9

func NewSRouterContext[T comparable, U any]() *SRouterContext[T, U]

NewSRouterContext creates a new router context

type UberRateLimiter added in v1.0.0

type UberRateLimiter struct {
	// contains filtered or unexported fields
}

UberRateLimiter implements RateLimiter using Uber's ratelimit library

func NewUberRateLimiter added in v1.0.0

func NewUberRateLimiter() *UberRateLimiter

NewUberRateLimiter creates a new rate limiter using Uber's ratelimit library

func (*UberRateLimiter) Allow added in v1.0.0

func (u *UberRateLimiter) Allow(key string, limit int, window time.Duration) (bool, int, time.Duration)

Allow checks if a request is allowed based on the key and rate limit config This implementation uses only the leaky bucket algorithm for simplicity and efficiency

type UserAuthProvider added in v1.0.0

type UserAuthProvider[T any] interface {
	// AuthenticateUser authenticates a request and returns the user object if authentication is successful.
	// It examines the request for authentication credentials (such as headers, cookies, or query parameters)
	// and validates them according to the provider's implementation.
	// Returns the user object if the request is authenticated, nil and an error otherwise.
	AuthenticateUser(r *http.Request) (*T, error)
}

UserAuthProvider defines an interface for authentication providers that return a user object. Different authentication mechanisms can implement this interface to be used with the AuthenticationWithUserProvider middleware.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL