김가영
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
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 +}
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 +}
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 +}
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 +)
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=
1 +package main
2 +
3 +import (
4 + "log"
5 +)
6 +
7 +func main() {
8 + config, err := LoadConfig("config.json")
9 + if err != nil {
10 + log.Fatal(err)
11 + }
12 +
13 + app := NewApp(config)
14 + app.Serve()
15 +}
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 +}
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 +}