package handlers import ( "database/sql" "encoding/json" "net/http" "time" "github.com/golang-jwt/jwt/v5" "golang.org/x/crypto/bcrypt" "boc/models" ) const tokenExpiry = 24 * time.Hour type AuthHandler struct { DB *sql.DB JWTSecret []byte } type loginRequest struct { Email string `json:"email"` Password string `json:"password"` } type userResponse struct { ID string `json:"id"` Email string `json:"email"` Name string `json:"name"` Role string `json:"role"` } func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) { var req loginRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Email == "" || req.Password == "" { writeError(w, http.StatusBadRequest, "email and password required") return } var ( id string name string role string passwordHash string ) err := h.DB.QueryRowContext(r.Context(), `SELECT id, name, role, password_hash FROM boc_users WHERE email = $1`, req.Email, ).Scan(&id, &name, &role, &passwordHash) if err == sql.ErrNoRows { writeError(w, http.StatusUnauthorized, "invalid credentials") return } if err != nil { writeError(w, http.StatusInternalServerError, "internal error") return } if err := bcrypt.CompareHashAndPassword([]byte(passwordHash), []byte(req.Password)); err != nil { writeError(w, http.StatusUnauthorized, "invalid credentials") return } now := time.Now() claims := models.Claims{ UserID: id, Email: req.Email, Role: role, RegisteredClaims: jwt.RegisteredClaims{ IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiry)), Subject: id, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) signed, err := token.SignedString(h.JWTSecret) if err != nil { writeError(w, http.StatusInternalServerError, "could not sign token") return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "token": signed, "user": userResponse{ ID: id, Email: req.Email, Name: name, Role: role, }, }) } func (h *AuthHandler) Me(w http.ResponseWriter, r *http.Request) { claims, ok := r.Context().Value("user").(*models.Claims) if !ok { writeError(w, http.StatusUnauthorized, "unauthorized") return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]interface{}{ "id": claims.UserID, "email": claims.Email, "role": claims.Role, }) }