Merge branch 'master' of http://khuhub.khu.ac.kr/2020-2-capstone-design1/JJS_project1
Showing
9 changed files
with
323 additions
and
0 deletions
source/server/.gitignore
0 → 100644
1 | + | ||
2 | +# Created by https://www.toptal.com/developers/gitignore/api/go,vscode | ||
3 | +# Edit at https://www.toptal.com/developers/gitignore?templates=go,vscode | ||
4 | + | ||
5 | +### Go ### | ||
6 | +# Binaries for programs and plugins | ||
7 | +*.exe | ||
8 | +*.exe~ | ||
9 | +*.dll | ||
10 | +*.so | ||
11 | +*.dylib | ||
12 | + | ||
13 | +# Test binary, built with `go test -c` | ||
14 | +*.test | ||
15 | + | ||
16 | +# Output of the go coverage tool, specifically when used with LiteIDE | ||
17 | +*.out | ||
18 | + | ||
19 | +# Dependency directories (remove the comment below to include it) | ||
20 | +# vendor/ | ||
21 | + | ||
22 | +### Go Patch ### | ||
23 | +/vendor/ | ||
24 | +/Godeps/ | ||
25 | + | ||
26 | +### vscode ### | ||
27 | +.vscode/* | ||
28 | +!.vscode/settings.json | ||
29 | +!.vscode/tasks.json | ||
30 | +!.vscode/launch.json | ||
31 | +!.vscode/extensions.json | ||
32 | +*.code-workspace | ||
33 | + | ||
34 | +# End of https://www.toptal.com/developers/gitignore/api/go,vscode | ||
35 | + | ||
36 | +__debug_bin | ||
37 | +config.json | ||
38 | +data | ||
... | \ No newline at end of file | ... | \ No newline at end of file |
source/server/app.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "net/http" | ||
6 | + | ||
7 | + "github.com/gorilla/mux" | ||
8 | + | ||
9 | + _ "github.com/go-sql-driver/mysql" | ||
10 | + "github.com/jmoiron/sqlx" | ||
11 | +) | ||
12 | + | ||
13 | +type Prop int | ||
14 | + | ||
15 | +const ( | ||
16 | + PropUserNo Prop = iota | ||
17 | +) | ||
18 | + | ||
19 | +type App struct { | ||
20 | + Config Config | ||
21 | + db *sqlx.DB | ||
22 | + router *mux.Router | ||
23 | +} | ||
24 | + | ||
25 | +func NewApp(config Config) *App { | ||
26 | + app := new(App) | ||
27 | + app.Config = config | ||
28 | + | ||
29 | + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", config.Database.User, config.Database.Password, config.Database.Host, config.Database.Name) | ||
30 | + app.db = sqlx.MustOpen("mysql", dsn) | ||
31 | + | ||
32 | + app.router = mux.NewRouter() | ||
33 | + app.router.HandleFunc("/users", app.PostUsers).Methods("POST") | ||
34 | + app.router.HandleFunc("/users/tokens", app.PostTokens).Methods("POST") | ||
35 | + app.router.Handle("/extractions", app.WithAuth(app.PostExtractions)).Methods("Post") | ||
36 | + | ||
37 | + return app | ||
38 | +} | ||
39 | + | ||
40 | +func (app *App) Serve() { | ||
41 | + http.ListenAndServe(fmt.Sprintf(":%d", app.Config.Port), app.router) | ||
42 | +} |
source/server/config.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "io/ioutil" | ||
6 | +) | ||
7 | + | ||
8 | +type Config struct { | ||
9 | + Port int `json:"port"` | ||
10 | + Database struct { | ||
11 | + Host string `json:"host"` | ||
12 | + Name string `json:"name"` | ||
13 | + User string `json:"user"` | ||
14 | + Password string `json:"password"` | ||
15 | + } `json:"database"` | ||
16 | + TokenSecret string `json:"token_secret"` | ||
17 | +} | ||
18 | + | ||
19 | +func LoadConfig(path string) (Config, error) { | ||
20 | + config := Config{} | ||
21 | + | ||
22 | + data, err := ioutil.ReadFile(path) | ||
23 | + if err == nil { | ||
24 | + err = json.Unmarshal(data, &config) | ||
25 | + } | ||
26 | + | ||
27 | + return config, err | ||
28 | +} |
source/server/extraction.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "fmt" | ||
5 | + "io" | ||
6 | + "net/http" | ||
7 | + "os" | ||
8 | + "strings" | ||
9 | + | ||
10 | + "github.com/google/uuid" | ||
11 | +) | ||
12 | + | ||
13 | +func (app *App) PostExtractions(w http.ResponseWriter, r *http.Request) { | ||
14 | + userNo := r.Context().Value(PropUserNo).(uint64) | ||
15 | + r.ParseMultipartForm(32 << 20) | ||
16 | + | ||
17 | + form, _, err := r.FormFile("file") | ||
18 | + if err != nil { | ||
19 | + WriteError(w, http.StatusInternalServerError, "Unknown error") | ||
20 | + return | ||
21 | + } | ||
22 | + | ||
23 | + defer form.Close() | ||
24 | + | ||
25 | + dir := fmt.Sprintf("data/%d", userNo) | ||
26 | + os.MkdirAll(dir, 0644) | ||
27 | + | ||
28 | + name := strings.Replace(uuid.New().String(), "-", "", -1) | ||
29 | + file, err := os.Create(fmt.Sprintf("%s/%s", dir, name)) | ||
30 | + if err != nil { | ||
31 | + WriteError(w, http.StatusInternalServerError, "Unknown error") | ||
32 | + return | ||
33 | + } | ||
34 | + defer file.Close() | ||
35 | + | ||
36 | + _, err = io.Copy(file, form) | ||
37 | + if err != nil { | ||
38 | + WriteError(w, http.StatusInternalServerError, "Unknown error") | ||
39 | + return | ||
40 | + } | ||
41 | + | ||
42 | + w.Write([]byte("success")) | ||
43 | +} |
source/server/go.mod
0 → 100644
1 | +module mf-server | ||
2 | + | ||
3 | +go 1.15 | ||
4 | + | ||
5 | +require ( | ||
6 | + github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||
7 | + github.com/go-sql-driver/mysql v1.5.0 | ||
8 | + github.com/google/uuid v1.1.2 | ||
9 | + github.com/gorilla/mux v1.8.0 | ||
10 | + github.com/jmoiron/sqlx v1.2.0 | ||
11 | + golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 | ||
12 | +) |
source/server/go.sum
0 → 100644
1 | +github.com/dgrijalva/jwt-go v1.0.2 h1:KPldsxuKGsS2FPWsNeg9ZO18aCrGKujPoWXn2yo+KQM= | ||
2 | +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= | ||
3 | +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||
4 | +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= | ||
5 | +github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= | ||
6 | +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= | ||
7 | +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= | ||
8 | +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||
9 | +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= | ||
10 | +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= | ||
11 | +github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= | ||
12 | +github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= | ||
13 | +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||
14 | +github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= | ||
15 | +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= | ||
16 | +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E= | ||
17 | +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= | ||
18 | +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= | ||
19 | +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||
20 | +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= | ||
21 | +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||
22 | +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |
source/server/main.go
0 → 100644
source/server/user.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "context" | ||
5 | + "encoding/json" | ||
6 | + "net/http" | ||
7 | + "strings" | ||
8 | + "time" | ||
9 | + | ||
10 | + "github.com/dgrijalva/jwt-go" | ||
11 | + "github.com/go-sql-driver/mysql" | ||
12 | + "golang.org/x/crypto/sha3" | ||
13 | +) | ||
14 | + | ||
15 | +type User struct { | ||
16 | + No uint64 `json:"no"` | ||
17 | + ID string `json:"id"` | ||
18 | + Name string `json:"name"` | ||
19 | + CreatedAt time.Time `json:"created_at"` | ||
20 | + ExpiredAt time.Time `json:"expired_at"` | ||
21 | +} | ||
22 | + | ||
23 | +func (app *App) PostUsers(w http.ResponseWriter, r *http.Request) { | ||
24 | + body := make(map[string]interface{}) | ||
25 | + err := json.NewDecoder(r.Body).Decode(&body) | ||
26 | + if err != nil { | ||
27 | + WriteError(w, http.StatusBadRequest, "Failed to parse request json") | ||
28 | + return | ||
29 | + } | ||
30 | + | ||
31 | + hash := sha3.Sum256([]byte(body["password"].(string))) | ||
32 | + | ||
33 | + res, err := app.db.Exec("INSERT INTO users (`id`, `password`, `name`) VALUES (?, ?, ?)", body["id"], hash[:], body["name"]) | ||
34 | + if err != nil { | ||
35 | + if merr, ok := err.(*mysql.MySQLError); ok { | ||
36 | + if merr.Number == 1062 { | ||
37 | + WriteError(w, http.StatusConflict, "Already registered") | ||
38 | + return | ||
39 | + } | ||
40 | + } | ||
41 | + | ||
42 | + WriteError(w, http.StatusInternalServerError, "Failed to register") | ||
43 | + return | ||
44 | + } | ||
45 | + | ||
46 | + no, _ := res.LastInsertId() | ||
47 | + WriteJson(w, map[string]interface{}{"user_no": no}) | ||
48 | +} | ||
49 | + | ||
50 | +type AuthClaims struct { | ||
51 | + UserNo uint64 `json:"user_no"` | ||
52 | + jwt.StandardClaims | ||
53 | +} | ||
54 | + | ||
55 | +func (app *App) PostTokens(w http.ResponseWriter, r *http.Request) { | ||
56 | + body := make(map[string]interface{}) | ||
57 | + err := json.NewDecoder(r.Body).Decode(&body) | ||
58 | + if err != nil { | ||
59 | + WriteError(w, http.StatusBadRequest, "Failed to parse request json") | ||
60 | + return | ||
61 | + } | ||
62 | + | ||
63 | + hash := sha3.Sum256([]byte(body["password"].(string))) | ||
64 | + rows, err := app.db.Query("SELECT `no` FROM users WHERE `id`=? AND `password`=?", body["id"], hash[:]) | ||
65 | + if err != nil { | ||
66 | + WriteError(w, http.StatusInternalServerError, "Failed to register") | ||
67 | + return | ||
68 | + } | ||
69 | + | ||
70 | + if !rows.Next() { | ||
71 | + WriteError(w, http.StatusUnauthorized, "Login failed") | ||
72 | + return | ||
73 | + } | ||
74 | + | ||
75 | + no := uint64(0) | ||
76 | + rows.Scan(&no) | ||
77 | + | ||
78 | + token := jwt.NewWithClaims(jwt.SigningMethodHS256, AuthClaims{UserNo: no}) | ||
79 | + auth, err := token.SignedString([]byte(app.Config.TokenSecret)) | ||
80 | + if err != nil { | ||
81 | + WriteError(w, http.StatusInternalServerError, "Login failed") | ||
82 | + return | ||
83 | + } | ||
84 | + | ||
85 | + WriteJson(w, map[string]interface{}{"token": auth}) | ||
86 | +} | ||
87 | + | ||
88 | +func (app *App) WithAuth(next func(http.ResponseWriter, *http.Request)) http.Handler { | ||
89 | + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
90 | + auth := r.Header.Get("Authorization") | ||
91 | + if len(auth) > 6 && strings.Index(auth, "Bearer ") == 0 { | ||
92 | + token, err := jwt.ParseWithClaims(auth[7:], &AuthClaims{}, func(token *jwt.Token) (interface{}, error) { | ||
93 | + return []byte(app.Config.TokenSecret), nil | ||
94 | + }) | ||
95 | + | ||
96 | + if err == nil { | ||
97 | + claims := token.Claims.(*AuthClaims) | ||
98 | + ctx := context.WithValue(r.Context(), PropUserNo, claims.UserNo) | ||
99 | + next(w, r.WithContext(ctx)) | ||
100 | + return | ||
101 | + } | ||
102 | + } | ||
103 | + | ||
104 | + WriteError(w, http.StatusUnauthorized, "Authorization failed") | ||
105 | + }) | ||
106 | +} |
source/server/util.go
0 → 100644
1 | +package main | ||
2 | + | ||
3 | +import ( | ||
4 | + "encoding/json" | ||
5 | + "net/http" | ||
6 | +) | ||
7 | + | ||
8 | +func WriteJson(w http.ResponseWriter, data interface{}) { | ||
9 | + w.Header().Set("Content-Type", "application/json") | ||
10 | + json.NewEncoder(w).Encode(data) | ||
11 | +} | ||
12 | + | ||
13 | +func WriteError(w http.ResponseWriter, status int, message string) { | ||
14 | + w.Header().Set("Content-Type", "application/json") | ||
15 | + w.WriteHeader(status) | ||
16 | + json.NewEncoder(w).Encode(map[string]interface{}{"msg": message}) | ||
17 | +} |
-
Please register or login to post a comment