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}