security

package
v1.11.1 Latest Latest
Warning

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

Go to latest
Published: Jul 4, 2026 License: GPL-3.0 Imports: 23 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrTokenAlreadyRotated = errors.New("refresh token already rotated")

ErrTokenAlreadyRotated is returned when a rotation loses the race against a concurrent rotation of the same token

Functions

func AuditLoginFailed added in v0.16.0

func AuditLoginFailed(c *gin.Context, username, reason string)

AuditLoginFailed logs a failed login attempt

func AuditLoginSuccess added in v0.16.0

func AuditLoginSuccess(c *gin.Context, userID uint, rememberMe bool)

AuditLoginSuccess logs a successful login attempt

func AuditLogout added in v0.16.0

func AuditLogout(c *gin.Context, userID uint)

AuditLogout logs a logout event

func AuditLogoutAll added in v1.11.0

func AuditLogoutAll(c *gin.Context, userID uint, revokedSessions int64)

AuditLogoutAll logs a logout-from-all-devices event

func AuditRateLimitExceeded added in v0.16.0

func AuditRateLimitExceeded(c *gin.Context, endpoint string)

AuditRateLimitExceeded logs a rate limit exceeded event

func AuditRefreshFailed added in v0.16.0

func AuditRefreshFailed(c *gin.Context, reason string)

AuditRefreshFailed logs a failed token refresh attempt

func AuditRefreshReuseDetected added in v1.11.1

func AuditRefreshReuseDetected(c *gin.Context, userID uint)

AuditRefreshReuseDetected logs the replay of a superseded refresh token beyond the rotation grace window — a possible token-theft signal surfaced for out-of-band alerting (no automatic revocation is performed)

func AuditRefreshSuccess added in v0.16.0

func AuditRefreshSuccess(c *gin.Context, userID uint)

AuditRefreshSuccess logs a successful token refresh

func CleanupExpiredTokens added in v0.16.0

func CleanupExpiredTokens(ctx context.Context) (int64, error)

CleanupExpiredTokens deletes refresh tokens that can never be valid again: expired ones and revoked ones (logout, logout-all, password change)

func ExtractToken

func ExtractToken(c *gin.Context) string

ExtractToken extracts the JWT from the Authorization header. Tokens are deliberately not accepted via query string: URLs leak into access logs, proxies, browser history and Referer headers.

func ExtractTokenID

func ExtractTokenID(c *gin.Context) (uint, error)

ExtractTokenID extracts user ID from token (existing function, moved here)

func GenerateToken

func GenerateToken(userID uint) (string, error)

GenerateToken generates a JWT access token (existing function, moved here)

func HasLiveRefreshToken added in v1.11.1

func HasLiveRefreshToken(ctx context.Context, accountID uint) (bool, error)

HasLiveRefreshToken reports whether the account still has at least one non-revoked, unexpired refresh token — used to deny the rotation grace once a logout/password-change/admin action has cleared the sessions.

func HashPassword

func HashPassword(password string) (string, error)

HashPassword hashes a password using bcrypt

func Headers added in v1.11.0

func Headers() gin.HandlerFunc

Headers sets defense-in-depth response headers on every response. HSTS is intentionally left to the reverse proxy / load balancer.

func JwtAuthAdminProcessor

func JwtAuthAdminProcessor() gin.HandlerFunc

JwtAuthAdminProcessor validates JWT and checks admin role (existing function, moved here). Alg-confusion and key-source attacks are handled by TokenValid (via jwtKeyFunc + WithValidMethods).

func JwtAuthProcessor

func JwtAuthProcessor() gin.HandlerFunc

JwtAuthProcessor validates JWT tokens (existing function, moved here)

func LogoutAllHandler added in v1.11.0

func LogoutAllHandler(c *gin.Context)

LogoutAllHandler handles POST /v1/auth/logout-all @Summary Logout from all devices @Description Revoke all refresh tokens of the authenticated user @Security Bearer @Tags Authentication @Produce json @Success 200 {object} LogoutAllResponse @Failure 401 {object} apitypes.ErrorResponse "Unauthorized" @Failure 500 {object} apitypes.ErrorResponse "Internal server error" @Router /v1/auth/logout-all [post]

func LogoutHandler added in v1.11.0

func LogoutHandler(c *gin.Context)

LogoutHandler handles POST /auth/logout @Summary Logout (revoke a refresh token) @Description Revoke the presented refresh token, ending that session. Always 200: token existence cannot be probed. @Tags Authentication @Accept json @Produce json @Param refresh_token body RefreshTokenInput true "Refresh Token" @Success 200 {object} apitypes.OkResponse @Failure 400 {object} apitypes.ErrorResponse "Invalid input" @Failure 500 {object} apitypes.ErrorResponse "Internal server error" @Router /auth/logout [post]

func MaxBodyBytes added in v1.11.0

func MaxBodyBytes(limit int64) gin.HandlerFunc

MaxBodyBytes limits the size of the request body. Requests declaring a larger Content-Length are rejected with 413; bodies without a declared length are capped while reading via http.MaxBytesReader (the read error surfaces in the handler, typically as a 400 on binding).

func NewEndpointRateLimiter added in v1.6.1

func NewEndpointRateLimiter(endpoint string, requestsPerWindow, burstSize, windowMinutes int) gin.HandlerFunc

NewEndpointRateLimiter creates a rate limiting middleware for any endpoint

func RefreshTokenHandler added in v0.16.0

func RefreshTokenHandler(c *gin.Context)

RefreshTokenHandler handles POST /auth/refresh @Summary Refresh access token @Description Exchange a valid refresh token for a new access token @Tags Authentication @Accept json @Produce json @Param refresh_token body RefreshTokenInput true "Refresh Token" @Success 200 {object} RefreshResponse @Failure 400 {object} apitypes.ErrorResponse "Invalid input" @Failure 401 {object} apitypes.ErrorResponse "Invalid or expired refresh token" @Failure 500 {object} apitypes.ErrorResponse "Internal server error" @Router /auth/refresh [post]

func RevokeAllUserTokens added in v1.11.0

func RevokeAllUserTokens(ctx context.Context, accountID uint) (int64, error)

RevokeAllUserTokens revokes every live refresh token of an account and returns how many sessions were revoked

func RevokeRefreshToken added in v1.11.0

func RevokeRefreshToken(ctx context.Context, tokenString string) (uint, bool, error)

RevokeRefreshToken marks a refresh token as revoked. It returns the owning account ID and whether a live token matched, so callers can audit the event yet respond generically either way (no token enumeration).

func TokenValid

func TokenValid(c *gin.Context) error

TokenValid validates a JWT token (existing function, moved here)

func VerifyPassword

func VerifyPassword(password, hashedPassword string) error

VerifyPassword verifies a password against a bcrypt hash

Types

type AuditEvent added in v0.16.0

type AuditEvent struct {
	Timestamp  time.Time      `json:"timestamp"`
	EventType  AuditEventType `json:"event_type"`
	UserID     *uint          `json:"user_id,omitempty"`
	Username   string         `json:"username,omitempty"`
	IP         string         `json:"ip"`
	UserAgent  string         `json:"user_agent,omitempty"`
	Message    string         `json:"message"`
	RememberMe bool           `json:"remember_me,omitempty"`
}

AuditEvent represents a security audit event

type AuditEventType added in v0.16.0

type AuditEventType string

AuditEventType represents the type of audit event

const (
	// Authentication events
	EventLoginSuccess      AuditEventType = "login_success"
	EventLoginFailed       AuditEventType = "login_failed"
	EventRefreshSuccess    AuditEventType = "refresh_success"
	EventRefreshFailed     AuditEventType = "refresh_failed"
	EventLogout            AuditEventType = "logout_success"
	EventLogoutAll         AuditEventType = "logout_all_success"
	EventRefreshReuse      AuditEventType = "refresh_token_reuse_detected"
	EventRateLimitExceeded AuditEventType = "rate_limit_exceeded"
)

type IPRateLimiter added in v0.16.0

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

IPRateLimiter manages rate limiters per IP address

func NewIPRateLimiter added in v0.16.0

func NewIPRateLimiter(requestsPerWindow, burstSize, windowMinutes int) *IPRateLimiter

NewIPRateLimiter creates a new IP-based rate limiter

func (*IPRateLimiter) GetLimiter added in v0.16.0

func (rl *IPRateLimiter) GetLimiter(ip string) *rate.Limiter

GetLimiter returns the rate limiter for an IP, creating if needed

func (*IPRateLimiter) RetryAfterSeconds added in v1.6.1

func (rl *IPRateLimiter) RetryAfterSeconds() int

RetryAfterSeconds returns the retry_after value in seconds based on the configured window

type LogoutAllResponse added in v1.11.0

type LogoutAllResponse struct {
	Message         string `json:"message"`
	RevokedSessions int64  `json:"revoked_sessions"`
}

LogoutAllResponse is the response of POST /v1/auth/logout-all

type RefreshResponse added in v0.16.0

type RefreshResponse struct {
	AccessToken      string `json:"access_token"`
	ExpiresIn        int64  `json:"expires_in"`
	RefreshToken     string `json:"refresh_token,omitempty"`
	RefreshExpiresIn int64  `json:"refresh_expires_in,omitempty"`
}

RefreshResponse represents the response from refresh endpoint. RefreshToken carries the rotated successor the client must store — the presented token is revoked. The fields are absent only on the short reuse-grace path (benign multi-tab race), where no rotation happens.

type RefreshToken added in v0.16.0

type RefreshToken struct {
	ID         uint
	Token      string
	AccountID  uint
	ExpiresAt  time.Time
	CreatedAt  time.Time
	LastUsedAt *time.Time
	Revoked    bool
	// RotatedAt is set when the token was revoked because a successor was
	// issued (rotation), nil when revoked by logout/password/admin action
	RotatedAt *time.Time
}

RefreshToken represents a refresh token row in the database. It is a storage model, never serialized to clients — no json tags on purpose: Token holds the plaintext right after creation but the SHA-256 hash when read back from the database.

func CreateRefreshToken added in v0.16.0

func CreateRefreshToken(ctx context.Context, accountID uint, rememberMe bool) (*RefreshToken, error)

CreateRefreshToken creates a new refresh token for a user

func GetRefreshToken added in v0.16.0

func GetRefreshToken(ctx context.Context, tokenString string) (*RefreshToken, error)

GetRefreshToken retrieves a refresh token by token string

func RotateRefreshToken added in v1.11.1

func RotateRefreshToken(ctx context.Context, old *RefreshToken) (*RefreshToken, error)

RotateRefreshToken atomically revokes the presented token and issues its successor, preserving the session horizon. Returns ErrTokenAlreadyRotated when a concurrent rotation of the same token won the race.

type RefreshTokenInput added in v0.16.0

type RefreshTokenInput struct {
	Token string `json:"refresh_token" binding:"required"`
}

RefreshTokenInput represents the input for refresh token endpoint

type TokenPairResponse added in v0.16.0

type TokenPairResponse struct {
	Token            string `json:"token"` // Backward compatibility - same as AccessToken
	AccessToken      string `json:"access_token"`
	RefreshToken     string `json:"refresh_token"`
	AccessExpiresIn  int64  `json:"access_expires_in"`
	RefreshExpiresIn int64  `json:"refresh_expires_in"`
}

TokenPairResponse represents access + refresh token pair

func GenerateTokenPair added in v0.16.0

func GenerateTokenPair(ctx context.Context, accountID uint, rememberMe bool) (*TokenPairResponse, error)

GenerateTokenPair generates both access and refresh tokens (NEW)

Jump to

Keyboard shortcuts

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