package main import ( "database/sql" "encoding/json" "net/http" "strings" "time" ) func jsonOK(w http.ResponseWriter, v interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(v) } func jsonErr(w http.ResponseWriter, code int, msg string) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(code) json.NewEncoder(w).Encode(map[string]string{"error": msg}) } func (s *Server) requireAPIKey(w http.ResponseWriter, r *http.Request) bool { key := r.Header.Get("Authorization") key = strings.TrimPrefix(key, "Bearer ") if key == "" { key = r.URL.Query().Get("api_key") } if key == "" { jsonErr(w, 401, "API key required") return false } // First try device token (hashed). hash := sha256Hex(key) var deviceID, userID, revokedAt sql.NullString err := s.db.QueryRow("SELECT id, user_id, revoked_at FROM server_devices WHERE token_hash=?", hash).Scan(&deviceID, &userID, &revokedAt) if err == nil { if revokedAt.Valid && revokedAt.String != "" { jsonErr(w, 401, "device revoked") return false } // Check user not blocked. var blocked int if userID.Valid && userID.String != "" { s.db.QueryRow("SELECT blocked FROM server_users WHERE id=?", userID.String).Scan(&blocked) if blocked != 0 { jsonErr(w, 403, "user blocked") return false } } r.Header.Set("X-Device-ID", deviceID.String) r.Header.Set("X-User-ID", userID.String) s.db.Exec("UPDATE server_devices SET last_seen=? WHERE id=?", time.Now().UTC().Format(time.RFC3339), deviceID.String) return true } // Fallback to plain api_key (legacy). var count int err = s.db.QueryRow("SELECT COUNT(*) FROM server_devices WHERE api_key=?", key).Scan(&count) if err != nil || count == 0 { jsonErr(w, 401, "invalid API key") return false } return true } func (s *Server) requireAdmin(w http.ResponseWriter, r *http.Request) bool { cookie, err := r.Cookie("session") if err != nil || !s.tokens.Check(cookie.Value) { http.Redirect(w, r, "/admin/login", http.StatusFound) return false } return true } type PasswordError string const ( ErrPasswordTooShort PasswordError = "PASSWORD_TOO_SHORT" ErrPasswordTooLong PasswordError = "PASSWORD_TOO_LONG" ) func validatePassword(password string) string { if len(password) < 8 { return string(ErrPasswordTooShort) } if len(password) > 256 { return string(ErrPasswordTooLong) } return "" } func (s *Server) requireUser(w http.ResponseWriter, r *http.Request) (string, bool) { key := r.Header.Get("Authorization") key = strings.TrimPrefix(key, "Bearer ") if key == "" { jsonErr(w, 401, "authorization required") return "", false } userID, ok := s.userTokens.Check(key) if !ok { jsonErr(w, 401, "invalid or expired token") return "", false } return userID, true }