arnaucube
arnaucube/go-dvotePublic Self-hosted repository
go-dvote/service/censusmanager.go
service/censusmanager.goRaw
  1package censusmanager
  2
  3import (
  4	"encoding/json"
  5	"fmt"
  6	"log"
  7	"net/http"
  8	"strconv"
  9	"strings"
 10	"time"
 11
 12	"github.com/vocdoni/dvote-census/tree"
 13	"github.com/vocdoni/dvote-relay/crypto/signature"
 14)
 15
 16const authTimeWindow = 10 // Time window (seconds) in which TimeStamp will be accepted if auth enabled
 17
 18var authPubKey string
 19
 20var T tree.Tree          // MerkleTree dvote-census library
 21var S signature.SignKeys // Signature dvote-relay library
 22
 23type Claim struct {
 24	CensusID  string `json:"censusID"`  // References to MerkleTree namespace
 25	ClaimData string `json:"claimData"` // Data to add to the MerkleTree
 26	ProofData string `json:"proofData"` // MerkleProof to check
 27	TimeStamp string `json:"timeStamp"` // Unix TimeStamp in seconds
 28	Signature string `json:"signature"` // Signature as Hexadecimal String
 29}
 30
 31type Result struct {
 32	Error    bool   `json:"error"`
 33	Response string `json:"response"`
 34}
 35
 36func reply(resp *Result, w http.ResponseWriter) {
 37	err := json.NewEncoder(w).Encode(resp)
 38	if err != nil {
 39		http.Error(w, err.Error(), 500)
 40	} else {
 41		w.Header().Set("content-type", "application/json")
 42	}
 43}
 44
 45func checkRequest(w http.ResponseWriter, req *http.Request) bool {
 46	if req.Body == nil {
 47		http.Error(w, "Please send a request body", 400)
 48		return false
 49	}
 50	return true
 51}
 52
 53func checkAuth(timestamp, signature, message string) bool {
 54	if len(authPubKey) < 1 {
 55		return true
 56	}
 57	currentTime := int64(time.Now().Unix())
 58	timeStampRemote, err := strconv.ParseInt(timestamp, 10, 32)
 59	if err != nil {
 60		log.Printf("Cannot parse timestamp data %s\n", err)
 61		return false
 62	}
 63	if timeStampRemote < currentTime+authTimeWindow &&
 64		timeStampRemote > currentTime-authTimeWindow {
 65		v, err := S.Verify(message, signature, authPubKey)
 66		if err != nil {
 67			log.Printf("Verification error: %s\n", err)
 68		}
 69		return v
 70	}
 71	return false
 72}
 73
 74func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
 75	var c Claim
 76	var resp Result
 77	if ok := checkRequest(w, req); !ok {
 78		return
 79	}
 80	// Decode JSON
 81	err := json.NewDecoder(req.Body).Decode(&c)
 82	if err != nil {
 83		http.Error(w, err.Error(), 400)
 84		return
 85	}
 86
 87	// Process data
 88	log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
 89		c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
 90	resp.Error = false
 91	resp.Response = ""
 92
 93	if len(c.CensusID) > 0 {
 94		T.Namespace = c.CensusID
 95	} else {
 96		resp.Error = true
 97		resp.Response = "censusID is not valid"
 98		reply(&resp, w)
 99		return
100	}
101
102	if len(c.ClaimData) < 0 {
103		resp.Error = true
104		resp.Response = "data not valid"
105		reply(&resp, w)
106		return
107	}
108
109	if op == "add" {
110		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
111		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
112			if strings.HasPrefix(c.CensusID, "0x") {
113				resp.Error = true
114				resp.Response = "add claim to snapshot is not allowed"
115			} else {
116				err = T.AddClaim([]byte(c.ClaimData))
117			}
118		} else {
119			resp.Error = true
120			resp.Response = "invalid authentication"
121		}
122	}
123
124	if op == "gen" {
125		resp.Response, err = T.GenProof([]byte(c.ClaimData))
126	}
127
128	if op == "root" {
129		resp.Response = T.GetRoot()
130	}
131
132	if op == "dump" {
133		values, err := T.Dump()
134		if err != nil {
135			resp.Error = true
136			resp.Response = fmt.Sprint(err)
137		} else {
138			jValues, err := json.Marshal(values)
139			if err != nil {
140				resp.Error = true
141				resp.Response = fmt.Sprint(err)
142			} else {
143				resp.Response = string(jValues)
144			}
145		}
146	}
147
148	if op == "snapshot" {
149		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
150		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
151			if strings.HasPrefix(c.CensusID, "0x") {
152				resp.Error = true
153				resp.Response = "snapshot an snapshot makes no sense"
154			} else {
155				snapshotNamespace, err := T.Snapshot()
156				if err != nil {
157					resp.Error = true
158					resp.Response = fmt.Sprint(err)
159				} else {
160					resp.Response = snapshotNamespace
161				}
162			}
163		} else {
164			resp.Error = true
165			resp.Response = "invalid authentication"
166		}
167	}
168
169	if op == "check" {
170		if len(c.ProofData) < 1 {
171			resp.Error = true
172			resp.Response = "proofData not provided"
173		} else {
174			validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
175			if validProof {
176				resp.Response = "valid"
177			} else {
178				resp.Response = "invalid"
179			}
180		}
181	}
182
183	reply(&resp, w)
184}
185
186func addCorsHeaders(w *http.ResponseWriter, req *http.Request) {
187	(*w).Header().Set("Access-Control-Allow-Origin", "*")
188	(*w).Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
189	(*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
190}
191
192func Listen(port int, proto string, pubKey string) {
193	srv := &http.Server{
194		Addr:              fmt.Sprintf(":%d", port),
195		ReadHeaderTimeout: 4 * time.Second,
196		ReadTimeout:       4 * time.Second,
197		WriteTimeout:      4 * time.Second,
198		IdleTimeout:       3 * time.Second,
199	}
200
201	http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) {
202		addCorsHeaders(&w, r)
203
204		if r.Method == http.MethodPost {
205			claimHandler(w, r, "add")
206		} else if r.Method != http.MethodOptions {
207			http.Error(w, "Not found", http.StatusNotFound)
208		}
209	})
210	http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) {
211		addCorsHeaders(&w, r)
212
213		if r.Method == http.MethodPost {
214			claimHandler(w, r, "gen")
215		} else if r.Method != http.MethodOptions {
216			http.Error(w, "Not found", http.StatusNotFound)
217		}
218	})
219	http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) {
220		addCorsHeaders(&w, r)
221
222		if r.Method == http.MethodPost {
223			claimHandler(w, r, "check")
224		} else if r.Method != http.MethodOptions {
225			http.Error(w, "Not found", http.StatusNotFound)
226		}
227	})
228	http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) {
229		addCorsHeaders(&w, r)
230
231		if r.Method == http.MethodPost {
232			claimHandler(w, r, "root")
233		} else if r.Method != http.MethodOptions {
234			http.Error(w, "Not found", http.StatusNotFound)
235		}
236	})
237	http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
238		addCorsHeaders(&w, r)
239
240		if r.Method == http.MethodPost {
241			claimHandler(w, r, "snapshot")
242		} else if r.Method != http.MethodOptions {
243			http.Error(w, "Not found", http.StatusNotFound)
244		}
245	})
246	http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
247		addCorsHeaders(&w, r)
248
249		if r.Method == http.MethodPost {
250			claimHandler(w, r, "dump")
251		} else if r.Method != http.MethodOptions {
252			http.Error(w, "Not found", http.StatusNotFound)
253		}
254	})
255
256	if len(pubKey) > 1 {
257		log.Printf("Enabling signature authentication with %s\n", pubKey)
258		authPubKey = pubKey
259	} else {
260		authPubKey = ""
261	}
262
263	if proto == "https" {
264		log.Print("Starting server in https mode")
265		if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {
266			panic(err)
267		}
268	}
269	if proto == "http" {
270		log.Print("Starting server in http mode")
271		srv.SetKeepAlivesEnabled(false)
272		if err := srv.ListenAndServe(); err != nil {
273			panic(err)
274		}
275	}
276}