summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-01-26 07:03:53 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-01-26 07:03:53 -1000
commit8c2b8c352f8c980c79bb4bb4772e8cbc02d14164 (patch)
tree6913a38cf462df397b24ba0c6c4c18f128562429 /internal
parentff7339acfdf533110f3ab1f902e153df739eed1b (diff)
Phase 3: Error handling and security hardening
- Handle JSON marshal errors in sqlite.go (log + fallback to empty array) - Add 30s timeout to Google Calendar client initialization - Fix CSRF timing attack by using subtle.ConstantTimeCompare Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'internal')
-rw-r--r--internal/auth/middleware.go4
-rw-r--r--internal/store/sqlite.go22
2 files changed, 19 insertions, 7 deletions
diff --git a/internal/auth/middleware.go b/internal/auth/middleware.go
index b440032..ecdde82 100644
--- a/internal/auth/middleware.go
+++ b/internal/auth/middleware.go
@@ -3,6 +3,7 @@ package auth
import (
"context"
"crypto/rand"
+ "crypto/subtle"
"encoding/base64"
"net/http"
@@ -82,7 +83,8 @@ func (m *Middleware) CSRFProtect(next http.Handler) http.Handler {
requestToken = r.FormValue("csrf_token")
}
- if requestToken == "" || requestToken != token {
+ // Use constant-time comparison to prevent timing attacks
+ if requestToken == "" || subtle.ConstantTimeCompare([]byte(requestToken), []byte(token)) != 1 {
http.Error(w, "Forbidden - CSRF Token Mismatch", http.StatusForbidden)
return
}
diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go
index a9a0597..12aa1ce 100644
--- a/internal/store/sqlite.go
+++ b/internal/store/sqlite.go
@@ -3,6 +3,7 @@ package store
import (
"database/sql"
"encoding/json"
+ "errors"
"fmt"
"log"
"os"
@@ -11,6 +12,7 @@ import (
"time"
_ "github.com/mattn/go-sqlite3"
+
"task-dashboard/internal/models"
)
@@ -169,8 +171,12 @@ func (s *Store) SaveTasks(tasks []models.Task) error {
defer func() { _ = stmt.Close() }()
for _, task := range tasks {
- labelsJSON, _ := json.Marshal(task.Labels)
- _, err := stmt.Exec(
+ labelsJSON, err := json.Marshal(task.Labels)
+ if err != nil {
+ log.Printf("Warning: failed to marshal labels for task %s: %v", task.ID, err)
+ labelsJSON = []byte("[]")
+ }
+ _, err = stmt.Exec(
task.ID,
task.Content,
task.Description,
@@ -213,8 +219,12 @@ func (s *Store) DeleteTask(id string) error {
// UpsertTask inserts or updates a single task
func (s *Store) UpsertTask(task models.Task) error {
- labelsJSON, _ := json.Marshal(task.Labels)
- _, err := s.db.Exec(`
+ labelsJSON, err := json.Marshal(task.Labels)
+ if err != nil {
+ log.Printf("Warning: failed to marshal labels for task %s: %v", task.ID, err)
+ labelsJSON = []byte("[]")
+ }
+ _, err = s.db.Exec(`
INSERT OR REPLACE INTO tasks
(id, content, description, project_id, project_name, due_date, priority, completed, labels, url, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
@@ -344,7 +354,7 @@ func (s *Store) GetCacheMetadata(key string) (*models.CacheMetadata, error) {
WHERE key = ?
`, key).Scan(&cm.Key, &cm.LastFetch, &cm.TTLMinutes)
- if err == sql.ErrNoRows {
+ if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
if err != nil {
@@ -552,7 +562,7 @@ func (s *Store) DeleteCard(id string) error {
func (s *Store) GetSyncToken(service string) (string, error) {
var token string
err := s.db.QueryRow(`SELECT token FROM sync_tokens WHERE service = ?`, service).Scan(&token)
- if err == sql.ErrNoRows {
+ if errors.Is(err, sql.ErrNoRows) {
return "", nil
}
if err != nil {