S SmartDocs
Série: Go go 77 linhas · Atualizado 2026-04-18

auth_handler.go

Go/Baasid/internal/handlers/auth_handler.go

package handlers

import (
	"errors"
	"net/http"

	"baasid/internal/auth"
	"baasid/internal/dto"
	"baasid/internal/repository"

	"github.com/labstack/echo/v4"
	"gorm.io/gorm"
)

// AuthHandler implements POST /auth/login.
type AuthHandler struct {
	users  *repository.UserRepository
	tokens *auth.TokenService
}

// NewAuthHandler wires dependencies for login.
func NewAuthHandler(users *repository.UserRepository, tokens *auth.TokenService) *AuthHandler {
	return &AuthHandler{users: users, tokens: tokens}
}

// Login godoc
// @Summary      用戶登入
// @Description  驗證帳號密碼後簽發 JWT。成功時於 **Response Header** 的 `Authorization` 欄位回傳 Bearer Token,Body 回傳帳號與姓名。
// @Tags         auth
// @Accept       json
// @Produce      json
// @Param        body  body      dto.LoginRequest  true  "登入帳號與密碼"
// @Success      200   {object}  dto.LoginResponse "成功;並於 Header `Authorization` 帶入 JWT"
// @Failure      401   {object}  dto.ErrorMessage  "帳號密碼錯誤"
// @Failure      500   {object}  dto.ErrorMessage  "系統錯誤"
// @Router       /auth/login [post]
func (h *AuthHandler) Login(c echo.Context) error {
	var req dto.LoginRequest
	// Bind automatically unmarshals JSON and respects the `json` struct tags.
	if err := c.Bind(&req); err != nil {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "無效的 JSON 格式"})
	}
	if req.Account == "" || req.Password == "" {
		return echo.NewHTTPError(http.StatusBadRequest, dto.ErrorMessage{Message: "account 與 password 不可為空"})
	}

	u, err := h.users.FindByAccount(req.Account)
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			// Use the same response for unknown account and wrong password so an
			// attacker cannot enumerate which accounts exist.
			return echo.NewHTTPError(http.StatusUnauthorized, dto.ErrorMessage{Message: "帳號密碼錯誤"})
		}
		// Any other database error is treated as an internal failure.
		c.Logger().Errorf("login db error: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}

	if !auth.CheckPassword(u.Password, req.Password) {
		return echo.NewHTTPError(http.StatusUnauthorized, dto.ErrorMessage{Message: "帳號密碼錯誤"})
	}

	token, err := h.tokens.SignAccessToken(u.ID, u.Account)
	if err != nil {
		c.Logger().Errorf("sign jwt: %v", err)
		return echo.NewHTTPError(http.StatusInternalServerError, dto.ErrorMessage{Message: "系統錯誤"})
	}

	// Requirement: client reads JWT from response header "Authorization".
	// We use the standard Bearer scheme so browsers and proxies recognise it.
	c.Response().Header().Set(echo.HeaderAuthorization, "Bearer "+token)

	return c.JSON(http.StatusOK, dto.LoginResponse{
		Account: u.Account,
		Name:    u.Name,
	})
}

Artigos relacionados