S SmartDocs
シリーズ: Go go 73 行 · 更新日 2026-04-18

auth.go

Go/Baasid/internal/middleware/auth.go

// Package middleware hosts Echo middlewares shared across routes.
//
// The specification requires every goods CRUD request to carry a JWT in the
// Authorization header; we centralise verification here so handlers stay thin.
package middleware

import (
	"net/http"
	"strings"

	"baasid/internal/auth"

	"github.com/google/uuid"
	"github.com/labstack/echo/v4"
)

// Context keys used to pass authenticated identity from middleware to handlers.
// Using typed constants avoids string typos and collisions with other libs.
const (
	// CtxUserID holds the caller's system_user._id as uuid.UUID.
	CtxUserID = "baasid_ctx_user_id"
	// CtxAccount holds the caller's login account string (for logging).
	CtxAccount = "baasid_ctx_account"
)

// JWTAuth returns an Echo middleware that enforces a valid Bearer JWT.
//
// The spec labels missing/invalid credentials as HTTP 403「權限不足」for
// goods endpoints (login uses 401 for bad password). We follow that wording
// here so API responses match the requirement document exactly.
func JWTAuth(tokens *auth.TokenService) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			raw := c.Request().Header.Get(echo.HeaderAuthorization)
			if strings.TrimSpace(raw) == "" {
				// Echo.HTTPError.Message is rendered by router.customHTTPErrorHandler.
				return echo.NewHTTPError(http.StatusForbidden, echo.Map{"message": "權限不足"})
			}

			// Accept either "Bearer <token>" (RFC 6750) or a raw token string,
			// because the requirement document only says "JWT token".
			token := strings.TrimSpace(raw)
			if strings.HasPrefix(strings.ToLower(token), "bearer ") {
				token = strings.TrimSpace(token[7:])
			}

			claims, err := tokens.ParseAccessToken(token)
			if err != nil {
				return echo.NewHTTPError(http.StatusForbidden, echo.Map{"message": "權限不足"})
			}

			uid, err := uuid.Parse(claims.UserID)
			if err != nil {
				return echo.NewHTTPError(http.StatusForbidden, echo.Map{"message": "權限不足"})
			}

			c.Set(CtxUserID, uid)
			c.Set(CtxAccount, claims.Account)
			return next(c)
		}
	}
}

// GetUserID reads the authenticated user's UUID from Echo context.
// Panics if middleware did not run — that is a programming error.
func GetUserID(c echo.Context) uuid.UUID {
	v := c.Get(CtxUserID)
	uid, ok := v.(uuid.UUID)
	if !ok {
		panic("middleware.GetUserID: CtxUserID not set — forgot JWT middleware?")
	}
	return uid
}

関連記事