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 addCorsHeaders(w *http.ResponseWriter, req *http.Request) {
 75	(*w).Header().Set("Access-Control-Allow-Origin", "*")
 76	(*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS")
 77	(*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization")
 78}
 79
 80func claimHandler(w http.ResponseWriter, req *http.Request, op string) {
 81	addCorsHeaders(&w, req)
 82
 83	if (*req).Method == "OPTIONS" {
 84		return
 85	}
 86
 87	var c Claim
 88	var resp Result
 89	if ok := checkRequest(w, req); !ok {
 90		return
 91	}
 92	// Decode JSON
 93	err := json.NewDecoder(req.Body).Decode(&c)
 94	if err != nil {
 95		http.Error(w, err.Error(), 400)
 96		return
 97	}
 98
 99	// Process data
100	log.Printf("Received censusID:{%s} claimData:{%s} proofData:{%s} timeStamp:{%s} signature:{%s}\n",
101		c.CensusID, c.ClaimData, c.ProofData, c.TimeStamp, c.Signature)
102	resp.Error = false
103	resp.Response = ""
104
105	if len(c.CensusID) > 0 {
106		T.Namespace = c.CensusID
107	} else {
108		resp.Error = true
109		resp.Response = "censusID is not valid"
110		reply(&resp, w)
111		return
112	}
113
114	if len(c.ClaimData) < 0 {
115		resp.Error = true
116		resp.Response = "data not valid"
117		reply(&resp, w)
118		return
119	}
120
121	if op == "add" {
122		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
123		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
124			if strings.HasPrefix(c.CensusID, "0x") {
125				resp.Error = true
126				resp.Response = "add claim to snapshot is not allowed"
127			} else {
128				err = T.AddClaim([]byte(c.ClaimData))
129			}
130		} else {
131			resp.Error = true
132			resp.Response = "invalid authentication"
133		}
134	}
135
136	if op == "gen" {
137		resp.Response, err = T.GenProof([]byte(c.ClaimData))
138	}
139
140	if op == "root" {
141		resp.Response = T.GetRoot()
142	}
143
144	if op == "dump" {
145		values, err := T.Dump()
146		if err != nil {
147			resp.Error = true
148			resp.Response = fmt.Sprint(err)
149		} else {
150			jValues, err := json.Marshal(values)
151			if err != nil {
152				resp.Error = true
153				resp.Response = fmt.Sprint(err)
154			} else {
155				resp.Response = string(jValues)
156			}
157		}
158	}
159
160	if op == "snapshot" {
161		msg := fmt.Sprintf("%s%s%s", c.CensusID, c.ClaimData, c.TimeStamp)
162		if auth := checkAuth(c.TimeStamp, c.Signature, msg); auth {
163			if strings.HasPrefix(c.CensusID, "0x") {
164				resp.Error = true
165				resp.Response = "snapshot an snapshot makes no sense"
166			} else {
167				snapshotNamespace, err := T.Snapshot()
168				if err != nil {
169					resp.Error = true
170					resp.Response = fmt.Sprint(err)
171				} else {
172					resp.Response = snapshotNamespace
173				}
174			}
175		} else {
176			resp.Error = true
177			resp.Response = "invalid authentication"
178		}
179	}
180
181	if op == "check" {
182		if len(c.ProofData) < 1 {
183			resp.Error = true
184			resp.Response = "proofData not provided"
185		} else {
186			validProof, _ := T.CheckProof([]byte(c.ClaimData), c.ProofData)
187			if validProof {
188				resp.Response = "valid"
189			} else {
190				resp.Response = "invalid"
191			}
192		}
193	}
194
195	reply(&resp, w)
196}
197
198func Listen(port int, proto string, pubKey string) {
199	srv := &http.Server{
200		Addr:              fmt.Sprintf(":%d", port),
201		ReadHeaderTimeout: 4 * time.Second,
202		ReadTimeout:       4 * time.Second,
203		WriteTimeout:      4 * time.Second,
204		IdleTimeout:       3 * time.Second,
205	}
206
207	http.HandleFunc("/addClaim", func(w http.ResponseWriter, r *http.Request) {
208		claimHandler(w, r, "add")
209	})
210	http.HandleFunc("/genProof", func(w http.ResponseWriter, r *http.Request) {
211		claimHandler(w, r, "gen")
212	})
213	http.HandleFunc("/checkProof", func(w http.ResponseWriter, r *http.Request) {
214		claimHandler(w, r, "check")
215	})
216	http.HandleFunc("/getRoot", func(w http.ResponseWriter, r *http.Request) {
217		claimHandler(w, r, "root")
218	})
219	http.HandleFunc("/snapshot", func(w http.ResponseWriter, r *http.Request) {
220		claimHandler(w, r, "snapshot")
221	})
222	http.HandleFunc("/dump", func(w http.ResponseWriter, r *http.Request) {
223		claimHandler(w, r, "dump")
224	})
225
226	if len(pubKey) > 1 {
227		log.Printf("Enabling signature authentication with %s\n", pubKey)
228		authPubKey = pubKey
229	} else {
230		authPubKey = ""
231	}
232
233	if proto == "https" {
234		log.Print("Starting server in https mode")
235		if err := srv.ListenAndServeTLS("server.crt", "server.key"); err != nil {
236			panic(err)
237		}
238	}
239	if proto == "http" {
240		log.Print("Starting server in http mode")
241		srv.SetKeepAlivesEnabled(false)
242		if err := srv.ListenAndServe(); err != nil {
243			panic(err)
244		}
245	}
246}