Files
workorders/internal/api/handlers/auth.go
T

111 lines
2.8 KiB
Go

package handlers
import (
"net/http"
"strconv"
"time"
"github.com/golang-jwt/jwt/v5"
"github.com/jmoiron/sqlx"
"golang.org/x/crypto/bcrypt"
"workorders/internal/config"
"workorders/internal/model"
)
type AuthHandler struct {
db *sqlx.DB
cfg *config.Config
}
func NewAuthHandler(db *sqlx.DB, cfg *config.Config) *AuthHandler {
return &AuthHandler{db: db, cfg: cfg}
}
func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
var body struct {
Username string `json:"username"`
Password string `json:"password"`
}
if err := decode(r, &body); err != nil || body.Username == "" || body.Password == "" {
respondError(w, http.StatusBadRequest, "username and password required")
return
}
var user model.User
err := h.db.Get(&user,
`SELECT * FROM users WHERE (username=@p1 OR email=@p1) AND active=1`,
body.Username)
if err != nil {
respondError(w, http.StatusUnauthorized, "invalid credentials")
return
}
if err := bcrypt.CompareHashAndPassword([]byte(user.PasswordHash), []byte(body.Password)); err != nil {
respondError(w, http.StatusUnauthorized, "invalid credentials")
return
}
token, err := generateToken(user, h.cfg.JWTSecret)
if err != nil {
respondError(w, http.StatusInternalServerError, "token generation failed")
return
}
h.db.Exec(`UPDATE users SET last_login=GETUTCDATE() WHERE id=@p1`, user.ID)
respond(w, http.StatusOK, map[string]any{
"token": token,
"user": publicUser(user),
})
}
func (h *AuthHandler) Refresh(w http.ResponseWriter, r *http.Request) {
user := userFromCtx(r)
var dbUser model.User
if err := h.db.Get(&dbUser, `SELECT * FROM users WHERE id=@p1 AND active=1`, user.UserID); err != nil {
respondError(w, http.StatusUnauthorized, "user not found")
return
}
token, err := generateToken(dbUser, h.cfg.JWTSecret)
if err != nil {
respondError(w, http.StatusInternalServerError, "token generation failed")
return
}
respond(w, http.StatusOK, map[string]any{"token": token})
}
func Me(w http.ResponseWriter, r *http.Request) {
respond(w, http.StatusOK, userFromCtx(r))
}
func generateToken(user model.User, secret string) (string, error) {
claims := model.LocalClaims{
RegisteredClaims: jwt.RegisteredClaims{
Subject: strconv.Itoa(user.ID),
ExpiresAt: jwt.NewNumericDate(time.Now().Add(8 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
},
UserID: user.ID,
Username: user.Username,
Email: user.Email,
DisplayName: user.DisplayName,
Role: user.Role,
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(secret))
}
func publicUser(u model.User) map[string]any {
return map[string]any{
"id": u.ID,
"username": u.Username,
"email": u.Email,
"display_name": u.DisplayName,
"role": u.Role,
"avatar_url": u.AvatarURL,
}
}