/
tickets.go
234 lines (190 loc) · 5.6 KB
/
tickets.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
package qrtickets
import (
"bytes"
"code.google.com/p/rsc/qr"
"crypto/ecdsa"
"crypto/rand"
"fmt"
"github.com/gorilla/mux"
"golang.org/x/net/context"
"google.golang.org/appengine"
"google.golang.org/appengine/datastore"
"math/big"
"net/http"
"time"
)
// Ticket - Outlines a digital ticket
type Ticket struct {
OrderID string `json:"order_id"`
EventKey *datastore.Key `json:"event" datastore:"-"`
Valid bool `json:"valid"`
Claimed bool `json:"claimed"`
DatastoreKey datastore.Key `datastore:"-"`
DateAdded time.Time `json:"date_added"`
}
// Load - Takes a datastore.Key provided and loads it into the current Ticket object
func (t *Ticket) Load(ctx context.Context, k datastore.Key) error {
err := datastore.Get(ctx, &k, t)
t.DatastoreKey = k
t.EventKey = k.Parent()
if err = datastore.Get(ctx, &k, t); err != nil {
return err
}
return nil
}
// Store - Store the ticket entry in google datastore
func (t *Ticket) Store(ctx context.Context) (*datastore.Key, error) {
var k *datastore.Key
// See if a key exists, or if a new one is required
if t.DatastoreKey.Incomplete() {
k = datastore.NewIncompleteKey(ctx, "Ticket", t.EventKey)
t.DateAdded = time.Now()
} else {
k = &t.DatastoreKey
}
// Stash the entry in the datastore
key, err := datastore.Put(ctx, k, t)
if err != nil {
return nil, err
}
return key, nil
}
// TicketNumber - Plain text
type TicketNumber struct {
ID []byte
Sig1, Sig2 *big.Int
}
// sign - Generates the signatures for the ticket ID utilizing the PrivateKey loaded from app.yaml
func (n *TicketNumber) sign() {
conf := ConfLoad()
sig1, sig2, err := ecdsa.Sign(rand.Reader, &conf.PrivateKey, n.ID)
if err != nil {
panic(err)
}
// Add the signature to the ticket number entry
n.Sig1, n.Sig2 = sig1, sig2
}
// verify - Verifies the ticket's signatures against it's ID using the PublicKey loaded from app.yaml
func (n *TicketNumber) verify() bool {
conf := ConfLoad()
return ecdsa.Verify(&conf.PublicKey, n.ID, n.Sig1, n.Sig2)
}
// AddTicket - Adds a valid ticket for the event and stores it in the datastore
func AddTicket(w http.ResponseWriter, r *http.Request) {
var buffer bytes.Buffer
vars := mux.Vars(r)
// Load the event datastore key
event, err := datastore.DecodeKey(vars["eventId"])
if err != nil {
panic(err)
}
// Create an appengine context
ctx := appengine.NewContext(r)
// fmt.Fprintf("%#v",ctx)
// Build the ticket entry
t := Ticket{
OrderID: r.FormValue("order_id"),
EventKey: event,
Valid: true,
}
// Store the ticket
k, err := t.Store(ctx)
if err != nil {
panic(err)
}
// Create the Ticket Num
var ticketnum = TicketNumber{ID: []byte(k.Encode())}
ticketnum.sign()
// Generate the text string to encode
buffer.WriteString(ticketnum.Sig1.String())
buffer.WriteString("/")
buffer.WriteString(ticketnum.Sig2.String())
buffer.WriteString("/")
buffer.WriteString(string(k.Encode()))
// Generate the QR code for the hash and two signatures
code, err := qr.Encode(buffer.String(), qr.L)
code.Scale = 2
if err != nil {
panic(err)
}
imgByte := code.PNG()
w.Header().Set("Content-Type", "image/png")
w.Header().Set("Content-Disposition", `inline; filename="`+k.Encode()+`"`)
w.WriteHeader(http.StatusOK)
w.Write(imgByte)
}
// GenTicket - Sign the ticket number provided through the URL and Generate a QR Code
func GenTicket(w http.ResponseWriter, r *http.Request) {
var buffer bytes.Buffer
// Load the variables from the path using mux
vars := mux.Vars(r)
// Setup the Ticket and sign it
var ticketnum = TicketNumber{ID: []byte(vars["hash"])}
ticketnum.sign()
// Generate the text string to encode
buffer.WriteString(ticketnum.Sig1.String())
buffer.WriteString("/")
buffer.WriteString(ticketnum.Sig2.String())
buffer.WriteString("/")
buffer.WriteString(vars["hash"])
// Generate the QR code for the hash and two signatures
code, err := qr.Encode(buffer.String(), qr.H)
if err != nil {
panic(err)
}
imgByte := code.PNG()
w.Header().Set("Content-Type", "image/png")
w.WriteHeader(http.StatusOK)
w.Write(imgByte)
// fmt.Fprintf(w, "sig1: %#v \n sig2: %#v \n message: %#v",ticketnum.Sig1,ticketnum.Sig2,vars["hash"])
}
// ClaimTicket - Verify the ticket and claim it
func ClaimTicket(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
// Define container variables
var hash []byte
sig1 := new(big.Int)
sig2 := new(big.Int)
// Assign the variables
sig1.SetString(vars["sig1"], 10)
sig2.SetString(vars["sig2"], 10)
hash = []byte(vars["hash"])
if VerifySignature(sig1, sig2, hash) {
// Signature has been successfully verified, Claim it in the datastore
ctx := appengine.NewContext(r)
var t Ticket
key, err := datastore.DecodeKey(vars["hash"])
// Check for decoding errors
if err != nil {
JSONError(&w, "Unable to Decode Key from provided hash")
return
}
// Map the results to the receiving object
if err = t.Load(ctx, *key); err != nil {
JSONError(&w, "Unable to Retrieve Key")
return
}
// Check for ticket validity
if t.Valid == false {
JSONError(&w, "Ticket has been invalidated")
return
}
if t.Claimed == true {
JSONError(&w, "Ticket has already been claimed")
return
}
// All good, update the ticket to claimed and resave it
t.Claimed = true
t.Store(ctx)
fmt.Fprintf(w, "%#v", t)
return
}
JSONError(&w, "Unable to Verify Signature")
return
}
// VerifySignature - Read hash, sig1, and sig2 from HTTP handler and verify
func VerifySignature(s1, s2 *big.Int, hash []byte) bool {
// Setup the signatures
ticketnum := TicketNumber{ID: hash, Sig1: s1, Sig2: s2}
return ticketnum.verify()
}