auth

package
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 16, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

README

auth

A weedbox module for JWT-based authentication. Provides login/logout, access token generation, refresh token rotation, and Gin middleware for token validation and permission checking.

Overview

The Auth module handles:

  • Login: Authenticates credentials via the user module, returns a JWT access token + database-backed refresh token
  • Token Refresh: Validates refresh tokens and issues new token pairs (with automatic rotation — old refresh tokens are revoked)
  • Logout: Revokes refresh tokens (single or all sessions)
  • Token Validation: Validates JWT access tokens and extracts user claims
  • Middleware: Two-layer Gin middleware for authentication and authorization

Dependencies

Dependency Source Description
database.DatabaseConnector common-modules GORM database connection
user.UserManager user-modules/user User authentication and lookup
rbac.RBACManager user-modules/rbac Permission checking

Module Registration

auth.Module("auth")

Configuration

Key Type Default Description
jwt_secret string "change-this-secret-in-production" HMAC secret for signing JWTs
access_token_expiry duration "15m" Access token lifetime
refresh_token_expiry duration "168h" (7 days) Refresh token lifetime
issuer string "weedbox" JWT issuer claim

Important: Always override jwt_secret in production.

Data Model

The RefreshToken model (auth/models/refresh_token.go) maps to the refresh_tokens table:

Field Type Constraints Description
ID varchar(36) Primary key UUID v7
UserID varchar(36) Not null, indexed Owner user ID
Token varchar(512) Unique, not null The refresh token string
ExpiresAt timestamp Not null, indexed Expiration time
Revoked bool Default: false Whether the token has been revoked
CreatedAt timestamp Indexed Creation time

JWT Claims

Access tokens contain the following custom claims:

{
  "iss": "weedbox",
  "sub": "user-uuid",
  "exp": 1234567890,
  "iat": 1234567890,
  "jti": "unique-token-id",
  "user_id": "user-uuid",
  "username": "admin",
  "email": "admin@localhost",
  "roles": ["admin"],
  "display_name": "System Administrator"
}

API Reference

AuthManager Methods

Authentication:

Method Signature Description
Login (ctx, identifier, password string) (*TokenPair, error) Authenticate and return token pair
RefreshTokens (ctx, refreshToken string) (*TokenPair, error) Refresh tokens (rotates refresh token)
Logout (ctx, refreshToken string) error Revoke a single refresh token
LogoutAll (ctx, userID string) error Revoke all refresh tokens for a user

Token Validation:

Method Signature Description
ValidateAccessToken (tokenString string) (*AccessTokenClaims, error) Validate JWT and extract claims

Token Management:

Method Signature Description
GetActiveRefreshTokens (ctx, userID string) ([]*RefreshTokenInfo, error) List active refresh tokens for a user
CleanupExpiredTokens (ctx) (int64, error) Remove expired/revoked tokens from DB

Middleware:

Method Signature Description
GetMiddleware (name string) interface{} Get middleware by name (see below)
Middleware

The GetMiddleware method returns two types of middleware:

"authenticate" — Token Validation Middleware

Returns gin.HandlerFunc. This middleware:

  1. Reads the Authorization: Bearer <token> header
  2. If no token is provided, passes through (allows unauthenticated access)
  3. If a token is provided, validates it and sets the X-User-Info header with base64-encoded session data
  4. If the token is invalid or expired, returns 401 Unauthorized
authMiddleware := authManager.GetMiddleware("authenticate").(gin.HandlerFunc)
router.Use(authMiddleware)
"require_permission" — Permission Check Middleware

Returns func(string) gin.HandlerFunc. This middleware:

  1. If permission is "*", passes through (public endpoint)
  2. Reads the X-User-Info header (set by the authenticate middleware)
  3. Decodes the session and checks RBAC permissions
  4. Sets session data in the Gin context for downstream handlers
  5. Returns 401 if not authenticated, 403 if insufficient permissions
requirePerm := authManager.GetMiddleware("require_permission").(func(string) gin.HandlerFunc)
router.GET("/users", requirePerm("user.list"), listHandler)
router.POST("/public", requirePerm("*"), publicHandler)
Context Helpers

Helper functions to extract session data from the Gin context (set by require_permission middleware):

Function Signature Description
GetSession (c *gin.Context) (*Session, bool) Get the full session
GetUserID (c *gin.Context) (string, bool) Get the authenticated user's ID
GetUsername (c *gin.Context) (string, bool) Get the authenticated user's username
GetRoles (c *gin.Context) ([]string, bool) Get the authenticated user's roles
HasRole (c *gin.Context, role string) bool Check if user has a specific role
Types

TokenPair:

type TokenPair struct {
    AccessToken      string
    RefreshToken     string
    TokenType        string    // "Bearer"
    ExpiresIn        int64     // Access token lifetime in seconds
    ExpiresAt        time.Time // Access token expiry timestamp
    RefreshExpiresAt time.Time // Refresh token expiry timestamp
}

Session:

type Session struct {
    UserID      string
    Username    string
    Email       string
    Roles       []string
    DisplayName string
}
Errors
Error Description
ErrInvalidCredentials Wrong username/email or password
ErrInvalidToken Token is malformed or has an invalid signature
ErrTokenExpired Token has expired
ErrTokenRevoked Refresh token has been revoked
ErrTokenNotFound Refresh token not found in database
ErrUserInactive User account is not active
ErrOperationFailed General operation failure

Architecture: Two-Layer Middleware

The middleware is split into two layers to support ingress/gateway architectures:

Request --> [authenticate] --> [require_permission] --> Handler
               |                      |
               v                      v
         Validates JWT          Reads X-User-Info header
         Sets X-User-Info       Checks RBAC permissions
         header                 Sets session in context

This design allows:

  • Running authenticate at the gateway/ingress level
  • Forwarding X-User-Info to backend services
  • Backend services only need require_permission to check permissions

Example: Using in a Custom Handler

func myHandler(c *gin.Context) {
    // Get authenticated user info
    session, ok := auth.GetSession(c)
    if !ok {
        c.JSON(401, gin.H{"error": "not authenticated"})
        return
    }

    // Use session data
    fmt.Printf("User: %s (roles: %v)\n", session.Username, session.Roles)

    // Check role
    if auth.HasRole(c, "admin") {
        // admin-specific logic
    }
}

Documentation

Index

Constants

View Source
const (
	// HeaderAuthorization is the Authorization header name
	HeaderAuthorization = "Authorization"

	// HeaderUserInfo is the header name for passing user info (for ingress integration)
	HeaderUserInfo = "X-User-Info"

	// ContextKeySession is the key for storing session info in gin context
	ContextKeySession = "auth_session"

	// ContextKeyUserID is the key for storing user ID in gin context
	ContextKeyUserID = "auth_user_id"

	// ContextKeyUsername is the key for storing username in gin context
	ContextKeyUsername = "auth_username"

	// ContextKeyRoles is the key for storing user roles in gin context
	ContextKeyRoles = "auth_roles"
)
View Source
const ModuleName = "Auth"

Variables

View Source
var (
	// ErrInvalidCredentials indicates invalid username/email or password
	ErrInvalidCredentials = errors.New("invalid credentials")

	// ErrInvalidToken indicates the token is invalid or malformed
	ErrInvalidToken = errors.New("invalid token")

	// ErrTokenExpired indicates the token has expired
	ErrTokenExpired = errors.New("token expired")

	// ErrTokenRevoked indicates the refresh token has been revoked
	ErrTokenRevoked = errors.New("token revoked")

	// ErrTokenNotFound indicates the refresh token was not found
	ErrTokenNotFound = errors.New("token not found")

	// ErrUserInactive indicates the user account is not active
	ErrUserInactive = errors.New("user account is not active")

	// ErrOperationFailed indicates a general operation failure
	ErrOperationFailed = errors.New("operation failed")
)

Functions

func GetRoles

func GetRoles(c *gin.Context) ([]string, bool)

GetRoles is a helper function to get user roles from gin context

func GetUserID

func GetUserID(c *gin.Context) (string, bool)

GetUserID is a helper function to get user ID from gin context

func GetUsername

func GetUsername(c *gin.Context) (string, bool)

GetUsername is a helper function to get username from gin context

func HasRole

func HasRole(c *gin.Context, role string) bool

HasRole checks if the user has a specific role

func Module

func Module(scope string) fx.Option

Types

type AccessTokenClaims

type AccessTokenClaims struct {
	UserID      string   `json:"user_id"`
	Username    string   `json:"username"`
	Email       string   `json:"email"`
	Roles       []string `json:"roles"`
	DisplayName string   `json:"display_name"`
}

AccessTokenClaims represents the claims in an access token

type AuthConfig

type AuthConfig struct {
	// JWT secret key for signing tokens
	JWTSecret string

	// Access token expiration duration
	AccessTokenExpiry time.Duration

	// Refresh token expiration duration
	RefreshTokenExpiry time.Duration

	// Token issuer
	Issuer string
}

AuthConfig contains configuration for authentication

type AuthManager

type AuthManager struct {
	weedbox.Module[*Params]
	// contains filtered or unexported fields
}

func (*AuthManager) CleanupExpiredTokens

func (m *AuthManager) CleanupExpiredTokens(ctx context.Context) (int64, error)

CleanupExpiredTokens removes expired refresh tokens from the database

func (*AuthManager) GetActiveRefreshTokens

func (m *AuthManager) GetActiveRefreshTokens(ctx context.Context, userID string) ([]*RefreshTokenInfo, error)

GetActiveRefreshTokens returns all active refresh tokens for a user

func (*AuthManager) GetMiddleware

func (m *AuthManager) GetMiddleware(name string) interface{}

GetMiddleware returns a middleware by name Supported middlewares:

  • "authenticate": validates JWT token and sets X-User-Info header
  • "require_permission": reads X-User-Info header and sets session in context

func (*AuthManager) InitDefaultConfigs

func (m *AuthManager) InitDefaultConfigs()

func (*AuthManager) Login

func (m *AuthManager) Login(ctx context.Context, identifier, password string) (*TokenPair, error)

Login authenticates a user and returns a token pair

func (*AuthManager) Logout

func (m *AuthManager) Logout(ctx context.Context, refreshToken string) error

Logout revokes a refresh token

func (*AuthManager) LogoutAll

func (m *AuthManager) LogoutAll(ctx context.Context, userID string) error

LogoutAll revokes all refresh tokens for a user

func (*AuthManager) OnStart

func (m *AuthManager) OnStart(ctx context.Context) error

func (*AuthManager) OnStop

func (m *AuthManager) OnStop(ctx context.Context) error

func (*AuthManager) RefreshTokens

func (m *AuthManager) RefreshTokens(ctx context.Context, refreshToken string) (*TokenPair, error)

RefreshTokens validates a refresh token and returns a new token pair

func (*AuthManager) ValidateAccessToken

func (m *AuthManager) ValidateAccessToken(tokenString string) (*AccessTokenClaims, error)

ValidateAccessToken validates an access token and returns the claims

type CustomClaims

type CustomClaims struct {
	jwt.RegisteredClaims
	UserID      string   `json:"user_id"`
	Username    string   `json:"username"`
	Email       string   `json:"email"`
	Roles       []string `json:"roles"`
	DisplayName string   `json:"display_name"`
}

CustomClaims represents JWT claims with user information

type MiddlewareFunc

type MiddlewareFunc = gin.HandlerFunc

MiddlewareFunc is the type for middleware functions

type MiddlewareWithParamFunc

type MiddlewareWithParamFunc = func(param string) gin.HandlerFunc

MiddlewareWithParamFunc is the type for middleware functions that accept parameters

type Params

type Params struct {
	weedbox.Params
	Database database.DatabaseConnector
	User     *user.UserManager `name:"user"`
	RBAC     *rbac.RBACManager `name:"rbac"`
}

type RefreshTokenInfo

type RefreshTokenInfo struct {
	ID        string    `json:"id"`
	UserID    string    `json:"user_id"`
	ExpiresAt time.Time `json:"expires_at"`
	Revoked   bool      `json:"revoked"`
	CreatedAt time.Time `json:"created_at"`
}

RefreshTokenInfo contains information about a refresh token

type Session

type Session struct {
	UserID      string   `json:"user_id"`
	Username    string   `json:"username"`
	Email       string   `json:"email"`
	Roles       []string `json:"roles"`
	DisplayName string   `json:"display_name"`
}

Session represents the authenticated user session

func GetSession

func GetSession(c *gin.Context) (*Session, bool)

GetSession is a helper function to get session from gin context

type TokenPair

type TokenPair struct {
	AccessToken      string    `json:"access_token"`
	RefreshToken     string    `json:"refresh_token"`
	TokenType        string    `json:"token_type"`
	ExpiresIn        int64     `json:"expires_in"`         // Access token expiry in seconds
	ExpiresAt        time.Time `json:"expires_at"`         // Access token expiry timestamp
	RefreshExpiresAt time.Time `json:"refresh_expires_at"` // Refresh token expiry timestamp
}

TokenPair contains both access and refresh tokens

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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