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, } }