feat: 管理后台登录
Some checks failed
Backend Deploy (Go + Docker) / deploy (push) Successful in 1m4s
Extension Build & Release / build (push) Failing after 46s

This commit is contained in:
zs
2026-03-02 23:54:59 +08:00
parent 4e5147fb13
commit d2b330c0c9
7 changed files with 115 additions and 5 deletions

View File

@@ -6,7 +6,8 @@ on:
paths: paths:
- 'extension/**' - 'extension/**'
- '.gitea/workflows/extension-build.yml' - '.gitea/workflows/extension-build.yml'
- 'extension/.env'
jobs: jobs:
build: build:
runs-on: arm runs-on: arm

View File

@@ -1 +1 @@
VITE_API_BASE_URL=http://localhost:8080/api/v1 VITE_API_BASE_URL=https://insight.buildapp.eu.org/api/v1

View File

@@ -60,6 +60,9 @@ func main() {
userSvc := service.NewUserService(userRepo) userSvc := service.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userSvc) userHandler := handler.NewUserHandler(userSvc)
authSvc := service.NewAuthService(userRepo)
authHandler := handler.NewAuthHandler(authSvc)
profileRepo := repository.NewProductProfileRepository(db) profileRepo := repository.NewProductProfileRepository(db)
profileSvc := service.NewProductProfileService(profileRepo) profileSvc := service.NewProductProfileService(profileRepo)
profileHandler := handler.NewProductProfileHandler(profileSvc) profileHandler := handler.NewProductProfileHandler(profileSvc)
@@ -120,6 +123,7 @@ func main() {
r.Route("/api/v1", func(r chi.Router) { r.Route("/api/v1", func(r chi.Router) {
// Public routes // Public routes
r.Post("/users/register", userHandler.Register) r.Post("/users/register", userHandler.Register)
r.Post("/auth/login", authHandler.Login)
// Protected routes // Protected routes
r.Group(func(r chi.Router) { r.Group(func(r chi.Router) {

View File

@@ -0,0 +1,44 @@
package handler
import (
"encoding/json"
"net/http"
"github.com/zs/InsightReply/internal/service"
)
type AuthHandler struct {
svc *service.AuthService
}
func NewAuthHandler(svc *service.AuthService) *AuthHandler {
return &AuthHandler{svc: svc}
}
// Login handles user authentication and returns a JWT token
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var body struct {
Email string `json:"email"`
Password string `json:"password"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
SendError(w, http.StatusBadRequest, 4001, "Invalid request body")
return
}
if body.Email == "" || body.Password == "" {
SendError(w, http.StatusBadRequest, 4001, "Email and Password are required")
return
}
token, err := h.svc.Login(body.Email, body.Password)
if err != nil {
SendError(w, http.StatusUnauthorized, 4001, err.Error())
return
}
SendSuccess(w, map[string]string{
"token": token,
})
}

View File

@@ -18,6 +18,7 @@ func NewUserHandler(svc *service.UserService) *UserHandler {
func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) { func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
var body struct { var body struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"`
Identity string `json:"identity"` Identity string `json:"identity"`
} }
@@ -26,7 +27,7 @@ func (h *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
return return
} }
user, err := h.svc.Register(body.Email, body.Identity) user, err := h.svc.Register(body.Email, body.Password, body.Identity)
if err != nil { if err != nil {
SendError(w, http.StatusInternalServerError, 5001, "Failed to register user") SendError(w, http.StatusInternalServerError, 5001, "Failed to register user")
return return

View File

@@ -0,0 +1,53 @@
package service
import (
"errors"
"os"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/zs/InsightReply/internal/repository"
"golang.org/x/crypto/bcrypt"
)
type AuthService struct {
userRepo *repository.UserRepository
}
func NewAuthService(userRepo *repository.UserRepository) *AuthService {
return &AuthService{userRepo: userRepo}
}
func (s *AuthService) Login(email, password string) (string, error) {
// 1. Fetch user by email
user, err := s.userRepo.GetByEmail(email)
if err != nil {
return "", errors.New("invalid email or password")
}
// 2. Compare password hash
err = bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(password))
if err != nil {
return "", errors.New("invalid email or password")
}
// 3. Generate JWT Token
secret := os.Getenv("JWT_SECRET")
if secret == "" {
secret = "fallback_secret_key_change_in_production"
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": user.ID.String(),
"email": user.Email,
"exp": time.Now().Add(time.Hour * 72).Unix(), // 3 days expiration
"iat": time.Now().Unix(),
})
tokenString, err := token.SignedString([]byte(secret))
if err != nil {
return "", err
}
return tokenString, nil
}

View File

@@ -3,6 +3,7 @@ package service
import ( import (
"github.com/zs/InsightReply/internal/model" "github.com/zs/InsightReply/internal/model"
"github.com/zs/InsightReply/internal/repository" "github.com/zs/InsightReply/internal/repository"
"golang.org/x/crypto/bcrypt"
) )
type UserService struct { type UserService struct {
@@ -13,12 +14,18 @@ func NewUserService(repo *repository.UserRepository) *UserService {
return &UserService{repo: repo} return &UserService{repo: repo}
} }
func (s *UserService) Register(email string, identity string) (*model.User, error) { func (s *UserService) Register(email string, password string, identity string) (*model.User, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return nil, err
}
user := &model.User{ user := &model.User{
Email: email, Email: email,
PasswordHash: string(hashedPassword),
IdentityLabel: identity, IdentityLabel: identity,
} }
err := s.repo.Create(user) err = s.repo.Create(user)
return user, err return user, err
} }