forked from Teamwork/s3zipper
/
main.go
137 lines (108 loc) · 3.03 KB
/
main.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
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"os"
"strconv"
"time"
"net/http"
zipper "github.com/cloudacademy/s3zipper/core"
"github.com/cloudacademy/s3zipper/s3"
"github.com/dgrijalva/jwt-go"
)
var s3bucket *s3.S3Bucket
var configFile string
var conf *Configuration
type Configuration struct {
Bucket string
Region string
Port int
JWTSharedKey string
}
func init() {
flag.StringVar(&configFile, "c", "./conf.json", "config file path")
}
func main() {
flag.Parse()
configJSON, err := os.Open(configFile)
checkError(err)
decoder := json.NewDecoder(configJSON)
conf = &Configuration{}
err = decoder.Decode(conf)
checkError(err)
s3bucket, err = s3.New(conf.Region, conf.Bucket)
checkError(err)
fmt.Println("Running on port", conf.Port)
http.HandleFunc("/", handler)
http.ListenAndServe(":"+strconv.Itoa(conf.Port), nil)
}
func handler(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Get "id" URL params
ids, ok := r.URL.Query()["id"]
if !ok || len(ids) < 1 {
http.Error(w, "S3 File Zipper. Pass JWT token with ?id=", 400)
return
}
prefix, err := parseJWT(ids[0])
if err != nil {
log.Printf("Error decoding JWT: %s\n", err)
http.Error(w, "Invalid JWT signature", 400)
return
}
zipname := prefix
if name, ok := r.URL.Query()["name"]; ok && len(name) >= 1 {
zipname = name[0]
}
exists := s3bucket.CacheExists(prefix)
if exists {
cache_url, err := s3bucket.CacheSignedUrl(prefix)
if err != nil {
http.Error(w, err.Error(), 404)
return
}
//TODO must be converted to Permanent redirection code
http.Redirect(w, r, cache_url, 302)
return
}
// Start processing the response
w.Header().Add("Content-Disposition", "attachment; filename=\""+zipname+".zip\"")
w.Header().Add("Content-Type", "application/zip")
zipper.Process(s3bucket, w, prefix)
log.Printf("%s\t%s\t%s", r.Method, r.RequestURI, time.Since(start))
}
func checkError(err error) {
if err != nil {
log.Fatal("Error: ", err)
}
}
func parseJWT(tokenString string) (string, error) {
// Parse takes the token string and a function for looking up the key. The latter is especially
// useful if you use multiple keys for your application. The standard is to use 'kid' in the
// head of the token to identify which key to use, but the parsed token (head and claims) is provided
// to the callback, providing flexibility.
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return "", fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return []byte(conf.JWTSharedKey), nil
})
if err != nil {
return "", err
}
if !token.Valid {
return "", fmt.Errorf("invalid token: %v", token.Raw)
}
claims, ok := token.Claims.(jwt.MapClaims)
if !ok {
return "", fmt.Errorf("claims not a valid map")
}
id, ok := claims["id"]
if !ok {
return "", fmt.Errorf("missing `id` property in jwt token")
}
return id.(string), nil
}