This repository has been archived by the owner on Jan 13, 2021. It is now read-only.
/
utils.go
315 lines (281 loc) · 9.64 KB
/
utils.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
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package v2
import (
"archive/zip"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/RTradeLtd/Temporal/eh"
"github.com/RTradeLtd/database/models"
"github.com/RTradeLtd/gorm"
"github.com/c2h5oh/datasize"
"github.com/gin-gonic/gin"
jwt "gopkg.in/dgrijalva/jwt-go.v3"
)
var nilTime time.Time
const (
// FilesUploadBucket is the bucket files are stored into before being processed
FilesUploadBucket = "filesuploadbucket"
// RtcCostUsd is the price of a single RTC in USD
RtcCostUsd = 0.125
)
// CheckAccessForPrivateNetwork checks if a user has access to a private network
func CheckAccessForPrivateNetwork(username, networkName string, db *gorm.DB) error {
um := models.NewUserManager(db)
canUpload, err := um.CheckIfUserHasAccessToNetwork(username, networkName)
if err != nil {
return err
}
if !canUpload {
return errors.New("unauthorized access to private network")
}
return nil
}
// GetIPFSEndpoint is used to construct the api url to connect to
// for private ipfs networks. in the case of dev mode it returns
// an default, non nexus based ipfs api address
func (api *API) GetIPFSEndpoint(networkName string) string {
if dev {
return api.cfg.IPFS.APIConnection.Host + ":" + api.cfg.IPFS.APIConnection.Port
}
return api.cfg.Nexus.Host + ":" + api.cfg.Nexus.Delegator.Port + "/network/" + networkName
}
// FileSizeCheck is used to check and validate the size of the uploaded file
func (api *API) FileSizeCheck(size int64) error {
sizeInt, err := strconv.ParseInt(
api.cfg.API.SizeLimitInGigaBytes,
10,
64,
)
if err != nil {
return err
}
gbInt := int64(datasize.GB.Bytes()) * sizeInt
if size > gbInt {
return errors.New(eh.FileTooBigError)
}
return nil
}
func (api *API) validateBlockchain(blockchain string) bool {
switch blockchain {
case "ethereum", "bitcoin", "litecoin", "monero", "dash":
return true
}
return false
}
// generateEmailJWTToken is used to generate a jwt token used to validate emails
func (api *API) generateEmailJWTToken(username, verificationString string) (string, error) {
// generate a jwt with claims to verify email
verificationJWT := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
"user": username,
"emailVerificationString": verificationString,
"expire": time.Now().Add(time.Hour * 24).UTC().String(),
})
// return a signed version of the jwt
return verificationJWT.SignedString([]byte(api.cfg.API.JWT.Key))
}
func (api *API) verifyEmailJWTToken(jwtString, username string) error {
// parse the jwt for a token
token, err := jwt.Parse(jwtString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if method, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unable to validate signing method: %v", token.Header["alg"])
} else if method != jwt.SigningMethodHS512 {
return nil, errors.New("expect hs512 signing method")
}
// return byte version of signing key
return []byte(api.cfg.JWT.Key), nil
})
// verify jwt was parsed properly
if err != nil {
return err
}
// verify that the token is valid
if !token.Valid {
return errors.New("failed to validate token")
}
// extract claims from token
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return errors.New("failed to parse claims")
}
// verify the username matches what we are expected
if claims["user"] != username {
return fmt.Errorf("username from claim does not match expected user of %s", username)
}
// get user model so we can validate the email verification string
user, err := api.um.FindByUserName(username)
if err != nil {
return errors.New(eh.UserSearchError)
}
emailVerificationString, ok := claims["emailVerificationString"].(string)
if !ok {
return errors.New("failed to convert verification token to string")
}
// validate email verification string
if claims["emailVerificationString"] != user.EmailVerificationToken {
return errors.New("failed to validate email verification token")
}
// ensure we can cast claims["expire"] to string type
expireString, ok := claims["expire"].(string)
if !ok {
return errors.New("failed to convert expire value to string")
}
// parse expire string into time.Time
expireTime, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", expireString)
if err != nil {
return err
}
// validate that the token hasn't expired
if time.Now().UTC().Unix() > expireTime.Unix() {
return errors.New("token is expired")
}
// enable email activity
if _, err := api.um.ValidateEmailVerificationToken(username, emailVerificationString); err != nil {
return err
}
return nil
}
// validateUserCredits is used to validate whether or not a user has enough credits to pay for an action
// and if they do, it is deducted from their account
func (api *API) validateUserCredits(username string, cost float64) error {
availableCredits, err := api.um.GetCreditsForUser(username)
if err != nil {
return err
}
if availableCredits < cost {
return errors.New(eh.InvalidBalanceError)
}
if _, err := api.um.RemoveCredits(username, cost); err != nil {
return err
}
return nil
}
// refundUserCredits is used to trigger a credit refund for a user, in the event of an API level processing failure.
// Note that we do not do any error handling here, instead we will log the information so that we may manually
// remediate the situation
func (api *API) refundUserCredits(username, callType string, cost float64) {
if _, err := api.um.AddCredits(username, cost); err != nil {
api.l.With("user", username, "call_type", callType, "error", err.Error()).Error(eh.CreditRefundError)
}
}
// validateAdminRequest is used to validate whether or not the requesting user is an administrator
func (api *API) validateAdminRequest(username string) error {
isAdmin, err := api.um.CheckIfAdmin(username)
if err != nil {
return err
}
if !isAdmin {
return errors.New(eh.UnAuthorizedAdminAccess)
}
return nil
}
func (api *API) formatUploadErrorMessage(file string, currentDataUsedBytes, maxDataAllowedBytes uint64) string {
currentDataUsedGB := float64(currentDataUsedBytes) / float64(datasize.GB.Bytes())
maxDataAllowedGB := float64(maxDataAllowedBytes) / float64(datasize.GB.Bytes())
return fmt.Sprintf(
"uploading object %s would breach your current data limit of %vGB as you are currently using %vGB, please upload a smaller object",
file, maxDataAllowedGB, currentDataUsedGB,
)
}
// used to extract needed post forms that should be provided with api calls.
// if the second return parameter, the string is non-empty, this is the name of the field which was missing
// we then use this to fail with a meaningful message
func (api *API) extractPostForms(c *gin.Context, formNames ...string) (map[string]string, string) {
forms := make(map[string]string)
for _, name := range formNames {
value, exists := c.GetPostForm(name)
if !exists {
return nil, name
}
forms[name] = value
}
return forms, ""
}
// ValidateHoldTime is used to perform parsing of requested hold times,
// returning an int64 type of the provded hold time
func (api *API) validateHoldTime(username, holdTime string) (int64, error) {
var (
// 1 month
freeHoldTimeLimitInMonths int64 = 1
// two years
nonFreeHoldTimeLimitInMonths int64 = 24
)
holdTimeInt, err := strconv.ParseInt(holdTime, 10, 64)
if err != nil {
return 0, err
}
usageTier, err := api.usage.FindByUserName(username)
if err != nil {
return 0, err
}
if usageTier.Tier == models.Free && holdTimeInt > freeHoldTimeLimitInMonths {
return 0, errors.New("free accounts are limited to maximum hold times of 1 month")
} else if usageTier.Tier != models.Free && holdTimeInt > nonFreeHoldTimeLimitInMonths {
return 0, errors.New("non free accounts are limited to a maximum hold time of 24 months")
}
return holdTimeInt, nil
}
func (api *API) ensureTwoYearMax(upload *models.Upload, holdTime int64) error {
// get current time
now := time.Now()
// get future time while factoring for additional hold time
then := upload.GarbageCollectDate.AddDate(0, int(holdTime), 0)
// get the time difference and ensure its less than the 2 year limit
if then.Sub(now).Hours() > 17520 {
return errors.New(eh.MaxHoldTimeError)
}
return nil
}
// Unzip will decompress a zip archive, moving all files and folders
// within the zip file (parameter 1) to an output directory (parameter 2).
// from https://golangcode.com/unzip-files-in-go/
func Unzip(src string, dest string) ([]string, error) {
var filenames []string
r, err := zip.OpenReader(src)
if err != nil {
return filenames, err
}
defer r.Close()
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
return filenames, err
}
defer rc.Close()
// Store filename/path for returning and using later on
fpath := filepath.Join(dest, f.Name)
// Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
if !strings.HasPrefix(fpath, filepath.Clean(dest)+string(os.PathSeparator)) {
return filenames, fmt.Errorf("%s: illegal file path", fpath)
}
filenames = append(filenames, fpath)
if f.FileInfo().IsDir() {
// Make Folder
os.MkdirAll(fpath, os.ModePerm)
} else {
// Make File
if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
return filenames, err
}
outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return filenames, err
}
bytesWritten, err := io.Copy(outFile, rc)
if bytesWritten == 0 {
return nil, errors.New("an error occur during unzipping which resulted in 0 bytes being written")
}
// Close the file without defer to close before next iteration of loop
outFile.Close()
if err != nil {
return filenames, err
}
}
}
return filenames, nil
}