/
register.go
205 lines (184 loc) · 5.99 KB
/
register.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
// Copyright Banrai LLC. All rights reserved. Use of this source code is
// governed by the license that can be found in the LICENSE file.
package api
import (
"bytes"
"database/sql"
"encoding/json"
"fmt"
"github.com/Banrai/PiScan/server/database/barcodes"
"github.com/Banrai/PiScan/server/digest"
"github.com/Banrai/PiScan/server/emailer"
"net/http"
"net/url"
"text/template"
)
const (
SERVER_SENDER = "openproductdata@saruzai.com" // used by other email notifications
VERIFY_SUBJECT = "Please verify your email address"
VERIFY_MESSAGE = `<p>Thank you for registering to contribute to the Open Product Database.</p>
<p>Please confirm your email address by clicking on this link:</p>
<p><a href="{{.APIServer}}/verify/{{.APICode}}">{{.APIServer}}/verify/{{.APICode}}</a></p>
<p>You only have to do this once per email address.</p>`
)
type RegistrationLink struct {
APIServer string
APICode string
}
func SendVerificationEmail(serverLink, email, code string) error {
var msg bytes.Buffer
t := template.Must(template.New("VERIFY_MESSAGE").Parse(VERIFY_MESSAGE))
context := RegistrationLink{serverLink, code}
err := t.Execute(&msg, context)
if err == nil {
sender := emailer.EmailAddress{Address: SERVER_SENDER}
recipient := emailer.EmailAddress{Address: email}
err = emailer.Send(VERIFY_SUBJECT, msg.String(), "text/html", &sender, &recipient, []*emailer.EmailAttachment{})
}
return err
}
func RegisterAccount(r *http.Request, db DBConnection, serverLink string) string {
// the result is a simple json ack
ack := new(SimpleMessage)
// this function only responds to GET requests with
// an hmac digest of the contents
if "GET" == r.Method {
params, paramErr := url.ParseQuery(r.URL.RawQuery)
if paramErr != nil {
ack.Err = paramErr
} else {
email := params.Get("email")
apiCode := params.Get("api")
hmacDigest := params.Get("hmac")
if email != "" && apiCode != "" {
// confirm the digest matches
params.Del("hmac") // separate the digest from the rest
if digest.DigestMatches(email, params.Encode(), hmacDigest) {
// the request is valid
registerFn := func(statements map[string]*sql.Stmt) {
lookupStmt, lookupStmtExists := statements[barcodes.ACCOUNT_LOOKUP_BY_EMAIL]
insertStmt, insertStmtExists := statements[barcodes.ACCOUNT_INSERT]
if lookupStmtExists && insertStmtExists {
// see if the email is available
acc, accErr := barcodes.LookupAccount(lookupStmt, email, false)
if accErr != nil {
ack.Err = accErr
} else {
if acc.Id != "" {
// this account has already been registered
ack.Ack = fmt.Sprintf("exists: %s", acc.Id)
if !acc.Verified {
// but it has yet to be verified, so send an email
ack.Err = SendVerificationEmail(serverLink, email, acc.Id)
}
} else {
// can proceed with the registration (add this email + api combination)
acc.Email = email
acc.APICode = apiCode
pk, addErr := acc.Add(insertStmt)
if addErr != nil {
ack.Err = accErr
} else {
// the account is created, but unverified
// send an email for verfication
ack.Err = SendVerificationEmail(serverLink, email, pk)
// and update this json reply
ack.Ack = fmt.Sprintf("ok: %s", pk)
}
}
}
}
}
WithServerDatabase(db, registerFn)
}
}
}
}
result, err := json.Marshal(ack)
if err != nil {
fmt.Println(err)
}
return string(result)
}
func VerifyAccount(r *http.Request, db DBConnection) string {
// accumulate the result in a simple ack struct
ack := new(SimpleMessage)
// this function only responds to GET requests
if "GET" == r.Method {
// get the verification code from the query string
code := r.URL.Path[len("/verify/"):]
verifyFn := func(statements map[string]*sql.Stmt) {
lookupStmt, lookupStmtExists := statements[barcodes.ACCOUNT_LOOKUP_BY_ID]
updateStmt, updateStmtExists := statements[barcodes.ACCOUNT_UPDATE]
if lookupStmtExists && updateStmtExists {
// see if the account for this code exists
acc, accErr := barcodes.LookupAccount(lookupStmt, code, true)
if accErr != nil {
ack.Err = accErr
} else {
if acc.Id == code {
// can proceed with the verification
acc.Verified = true
ack.Err = acc.Update(updateStmt)
}
}
}
}
WithServerDatabase(db, verifyFn)
}
// need to return a simple html string in reply
if ack.Err != nil {
return ack.Err.Error()
} else {
return "Thank you for verifying your email address"
}
}
func GetAccountStatus(r *http.Request, db DBConnection) string {
// the result is a simple json ack
ack := new(SimpleMessage)
// this function only responds to GET requests with
// an hmac digest of the contents
if "GET" == r.Method {
params, paramErr := url.ParseQuery(r.URL.RawQuery)
if paramErr != nil {
ack.Err = paramErr
} else {
email := params.Get("email")
hmacDigest := params.Get("hmac")
if email != "" && hmacDigest != "" {
// confirm the digest matches
params.Del("hmac") // separate the digest from the rest
if digest.DigestMatches(email, params.Encode(), hmacDigest) {
// the request is valid
statusFn := func(statements map[string]*sql.Stmt) {
lookupStmt, lookupStmtExists := statements[barcodes.ACCOUNT_LOOKUP_BY_EMAIL]
if lookupStmtExists {
// see if the email corresponds to an account
acc, accErr := barcodes.LookupAccount(lookupStmt, email, false)
if accErr != nil {
ack.Err = accErr
} else {
if acc.Id != "" {
// this account has already been registered
// so return its verified status in the message
if acc.Verified {
ack.Ack = "true"
} else {
ack.Ack = "false"
}
ack.Err = nil
}
}
}
}
WithServerDatabase(db, statusFn)
}
}
}
}
result, err := json.Marshal(ack)
if err != nil {
fmt.Println(err)
}
return string(result)
}