mirror of
https://github.com/Fishwaldo/auth2.git
synced 2025-06-03 12:21:22 +00:00
- Create password utilities with bcrypt and argon2id hashing support - Implement password policy enforcement with configurable requirements - Create basic username/password authentication provider - Implement account locking mechanism for security protection - Build bruteforce protection with IP and global rate limiting - Improve test resiliency for time-based operations - Add comprehensive black box testing with >80% coverage - Update project plan to mark Phase 2.2 as completed
301 lines
No EOL
7.7 KiB
Go
301 lines
No EOL
7.7 KiB
Go
package errors
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Standard errors in the auth2 package
|
|
var (
|
|
// General errors
|
|
ErrInternal = errors.New("internal error")
|
|
ErrNotImplemented = errors.New("not implemented")
|
|
ErrInvalidArgument = errors.New("invalid argument")
|
|
ErrInvalidOperation = errors.New("invalid operation")
|
|
|
|
// Authentication errors
|
|
ErrAuthFailed = errors.New("authentication failed")
|
|
ErrInvalidCredentials = errors.New("invalid credentials")
|
|
ErrUserNotFound = errors.New("user not found")
|
|
ErrUserDisabled = errors.New("user account is disabled")
|
|
ErrUserLocked = errors.New("user account is locked")
|
|
|
|
// MFA errors
|
|
ErrMFARequired = errors.New("multi-factor authentication required")
|
|
ErrMFAFailed = errors.New("multi-factor authentication failed")
|
|
ErrMFANotEnabled = errors.New("multi-factor authentication not enabled")
|
|
ErrInvalidMFACode = errors.New("invalid multi-factor authentication code")
|
|
|
|
// Session errors
|
|
ErrSessionExpired = errors.New("session has expired")
|
|
ErrInvalidSession = errors.New("invalid session")
|
|
ErrSessionNotFound = errors.New("session not found")
|
|
|
|
// Token errors
|
|
ErrInvalidToken = errors.New("invalid token")
|
|
ErrTokenExpired = errors.New("token has expired")
|
|
|
|
// Permission errors
|
|
ErrPermissionDenied = errors.New("permission denied")
|
|
ErrRoleNotFound = errors.New("role not found")
|
|
|
|
// Rate limiting errors
|
|
ErrRateLimitExceeded = errors.New("rate limit exceeded")
|
|
|
|
// Plugin errors
|
|
ErrPluginNotFound = errors.New("plugin not found")
|
|
ErrIncompatiblePlugin = errors.New("incompatible plugin")
|
|
ErrProviderExists = errors.New("provider already exists")
|
|
)
|
|
|
|
// AuthError represents an authentication-related error
|
|
type AuthError struct {
|
|
Err error
|
|
Code string
|
|
UserID string
|
|
Reason string
|
|
Temporary bool
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *AuthError) Error() string {
|
|
if e.UserID != "" {
|
|
return fmt.Sprintf("[%s] %s: %s (user: %s)", e.Code, e.Err.Error(), e.Reason, e.UserID)
|
|
}
|
|
return fmt.Sprintf("[%s] %s: %s", e.Code, e.Err.Error(), e.Reason)
|
|
}
|
|
|
|
// Unwrap returns the wrapped error
|
|
func (e *AuthError) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// NewAuthError creates a new AuthError
|
|
func NewAuthError(err error, code string, reason string, userID string, temporary bool) *AuthError {
|
|
return &AuthError{
|
|
Err: err,
|
|
Code: code,
|
|
UserID: userID,
|
|
Reason: reason,
|
|
Temporary: temporary,
|
|
}
|
|
}
|
|
|
|
// IsAuthError checks if an error is an AuthError
|
|
func IsAuthError(err error) bool {
|
|
var authErr *AuthError
|
|
return errors.As(err, &authErr)
|
|
}
|
|
|
|
// GetAuthError extracts an AuthError from an error
|
|
func GetAuthError(err error) (*AuthError, bool) {
|
|
var authErr *AuthError
|
|
if errors.As(err, &authErr) {
|
|
return authErr, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// PluginError represents an error related to plugins
|
|
type PluginError struct {
|
|
Err error
|
|
PluginName string
|
|
PluginType string
|
|
Description string
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *PluginError) Error() string {
|
|
return fmt.Sprintf("plugin error [%s - %s]: %s: %s",
|
|
e.PluginType, e.PluginName, e.Err.Error(), e.Description)
|
|
}
|
|
|
|
// Unwrap returns the wrapped error
|
|
func (e *PluginError) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// NewPluginError creates a new PluginError
|
|
func NewPluginError(err error, pluginType, pluginName, description string) *PluginError {
|
|
return &PluginError{
|
|
Err: err,
|
|
PluginType: pluginType,
|
|
PluginName: pluginName,
|
|
Description: description,
|
|
}
|
|
}
|
|
|
|
// IsPluginError checks if an error is a PluginError
|
|
func IsPluginError(err error) bool {
|
|
var pluginErr *PluginError
|
|
return errors.As(err, &pluginErr)
|
|
}
|
|
|
|
// GetPluginError extracts a PluginError from an error
|
|
func GetPluginError(err error) (*PluginError, bool) {
|
|
var pluginErr *PluginError
|
|
if errors.As(err, &pluginErr) {
|
|
return pluginErr, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// ValidationError represents an error related to validation
|
|
type ValidationError struct {
|
|
Err error
|
|
Field string
|
|
Value interface{}
|
|
Constraint string
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *ValidationError) Error() string {
|
|
return fmt.Sprintf("validation error [%s]: %s (got: %v, constraint: %s)",
|
|
e.Field, e.Err.Error(), e.Value, e.Constraint)
|
|
}
|
|
|
|
// Unwrap returns the wrapped error
|
|
func (e *ValidationError) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// NewValidationError creates a new ValidationError
|
|
func NewValidationError(err error, field string, value interface{}, constraint string) *ValidationError {
|
|
return &ValidationError{
|
|
Err: err,
|
|
Field: field,
|
|
Value: value,
|
|
Constraint: constraint,
|
|
}
|
|
}
|
|
|
|
// IsValidationError checks if an error is a ValidationError
|
|
func IsValidationError(err error) bool {
|
|
var validationErr *ValidationError
|
|
return errors.As(err, &validationErr)
|
|
}
|
|
|
|
// GetValidationError extracts a ValidationError from an error
|
|
func GetValidationError(err error) (*ValidationError, bool) {
|
|
var validationErr *ValidationError
|
|
if errors.As(err, &validationErr) {
|
|
return validationErr, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// StorageError represents an error related to storage operations
|
|
type StorageError struct {
|
|
Err error
|
|
Operation string
|
|
Entity string
|
|
StorageType string
|
|
}
|
|
|
|
// Error implements the error interface
|
|
func (e *StorageError) Error() string {
|
|
return fmt.Sprintf("storage error [%s - %s - %s]: %s",
|
|
e.StorageType, e.Entity, e.Operation, e.Err.Error())
|
|
}
|
|
|
|
// Unwrap returns the wrapped error
|
|
func (e *StorageError) Unwrap() error {
|
|
return e.Err
|
|
}
|
|
|
|
// NewStorageError creates a new StorageError
|
|
func NewStorageError(err error, storageType, entity, operation string) *StorageError {
|
|
return &StorageError{
|
|
Err: err,
|
|
StorageType: storageType,
|
|
Entity: entity,
|
|
Operation: operation,
|
|
}
|
|
}
|
|
|
|
// IsStorageError checks if an error is a StorageError
|
|
func IsStorageError(err error) bool {
|
|
var storageErr *StorageError
|
|
return errors.As(err, &storageErr)
|
|
}
|
|
|
|
// GetStorageError extracts a StorageError from an error
|
|
func GetStorageError(err error) (*StorageError, bool) {
|
|
var storageErr *StorageError
|
|
if errors.As(err, &storageErr) {
|
|
return storageErr, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Is checks if an error matches a target error
|
|
func Is(err, target error) bool {
|
|
return errors.Is(err, target)
|
|
}
|
|
|
|
// As finds the first error in err's chain that matches target
|
|
func As(err error, target interface{}) bool {
|
|
return errors.As(err, target)
|
|
}
|
|
|
|
// New creates a new error with the given message
|
|
func New(text string) error {
|
|
return errors.New(text)
|
|
}
|
|
|
|
// NewInternalError creates a new internal error with the given message
|
|
func NewInternalError(message string) error {
|
|
return Wrap(ErrInternal, message)
|
|
}
|
|
|
|
// Wrap wraps an error with additional context
|
|
func Wrap(err error, message string) error {
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("%s: %w", message, err)
|
|
}
|
|
|
|
// CompareVersions compares two semantic version strings.
|
|
// Returns:
|
|
// -1 if v1 < v2
|
|
// 0 if v1 == v2
|
|
// +1 if v1 > v2
|
|
// Error if versions are not valid semantic versions
|
|
func CompareVersions(v1, v2 string) (int, error) {
|
|
// Parse version strings
|
|
v1Parts := strings.Split(strings.Split(v1, "-")[0], ".")
|
|
v2Parts := strings.Split(strings.Split(v2, "-")[0], ".")
|
|
|
|
// Ensure we have at least 3 parts (major.minor.patch)
|
|
for len(v1Parts) < 3 {
|
|
v1Parts = append(v1Parts, "0")
|
|
}
|
|
for len(v2Parts) < 3 {
|
|
v2Parts = append(v2Parts, "0")
|
|
}
|
|
|
|
// Compare each part
|
|
for i := 0; i < 3; i++ {
|
|
v1Num, err := strconv.Atoi(v1Parts[i])
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid version format in v1: %s", v1)
|
|
}
|
|
|
|
v2Num, err := strconv.Atoi(v2Parts[i])
|
|
if err != nil {
|
|
return 0, fmt.Errorf("invalid version format in v2: %s", v2)
|
|
}
|
|
|
|
if v1Num < v2Num {
|
|
return -1, nil
|
|
} else if v1Num > v2Num {
|
|
return 1, nil
|
|
}
|
|
}
|
|
|
|
// Versions are equal
|
|
return 0, nil
|
|
} |