package handlers import ( "database/sql" "encoding/json" "log/slog" "net/http" "os" "time" "golang.org/x/crypto/bcrypt" "person-home/internal/auth" "person-home/internal/models" ) type AuthHandler struct { DB *sql.DB JWTSecret []byte } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { writeError(w, http.StatusMethodNotAllowed, "method not allowed") return } var req models.LoginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } var id int64 var username, hash string err := h.DB.QueryRow("SELECT id, username, password_hash FROM users WHERE username = ?", req.Username).Scan(&id, &username, &hash) if err == sql.ErrNoRows { writeError(w, http.StatusUnauthorized, "invalid credentials") return } if err != nil { slog.Error("login query", "error", err) writeError(w, http.StatusInternalServerError, "internal error") return } if err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(req.Password)); err != nil { writeError(w, http.StatusUnauthorized, "invalid credentials") return } token, expiresAt, err := auth.GenerateToken(username, h.JWTSecret, 24*time.Hour) if err != nil { slog.Error("generate token", "error", err) writeError(w, http.StatusInternalServerError, "internal error") return } writeJSON(w, http.StatusOK, models.LoginResponse{Token: token, ExpiresAt: expiresAt.Format(time.RFC3339)}) } func init() { if os.Getenv("ADMIN_PASSWORD") == "" { slog.Warn("ADMIN_PASSWORD not set; admin login will not work") } }