package admin import ( "encoding/json" "fmt" "forge.lightcrystal.systems/nilix/felt/admin/util" "forge.lightcrystal.systems/nilix/felt/models" "forge.lightcrystal.systems/nilix/felt/mongodb" "forge.lightcrystal.systems/nilix/quartzgun/auth" . "forge.lightcrystal.systems/nilix/quartzgun/middleware" "forge.lightcrystal.systems/nilix/quartzgun/rateLimiter" "forge.lightcrystal.systems/nilix/quartzgun/renderer" "forge.lightcrystal.systems/nilix/quartzgun/router" . "forge.lightcrystal.systems/nilix/quartzgun/util" "html/template" "io/ioutil" "net/http" "os" "path/filepath" "regexp" "strings" ) func apiGetTableList(next http.Handler, udb auth.UserStore) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { user := util.GetUserFromToken(req) self, err := util.GetTablesByUser(user, udb) if err != nil { w.WriteHeader(404) } else { AddContextValue(req, "tableList", self) } next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiGetTableData(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbAdapter) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { urlParams := req.Context().Value("params").(map[string]string) tableName := urlParams["Slug"] tableKey := models.TableKey{ Name: tableName, Passcode: req.FormValue("passcode"), } if dbAdapter.CheckTable(tableKey) { mapUrl, _ := dbAdapter.GetMapImageUrl(tableKey) auxMessage, _ := dbAdapter.GetAuxMessage(tableKey) tokens, _ := dbAdapter.GetTokens(tableKey, false) diceRolls, _ := dbAdapter.GetDiceRolls(tableKey) AddContextValue(req, "tableData", models.Table{ Name: tableKey.Name, Passcode: tableKey.Passcode, DiceRolls: diceRolls, MapImageUrl: mapUrl, Tokens: tokens, AuxMessage: auxMessage, }) } else { w.WriteHeader(404) } next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiCreateTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbAdapter) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { tableKey := models.TableKey{} err := json.NewDecoder(req.Body).Decode(&tableKey) if err != nil { w.WriteHeader(400) next.ServeHTTP(w, req) return } r := regexp.MustCompile("^[a-zA-Z0-9_]+$") if !r.MatchString(tableKey.Name) || !r.MatchString(tableKey.Passcode) { w.WriteHeader(422) next.ServeHTTP(w, req) return } // table name is primary key so w edon't need to check err = dbAdapter.CreateTable(tableKey) if err != nil { AddContextValue(req, "result", err.Error()) // TODO: parse error and change the status w.WriteHeader(500) } else { user := util.GetUserFromToken(req) tables, err := util.GetTablesByUser(user, udb) tables = append(tables, tableKey) err = util.SetTablesForUser(user, tables, udb) if err != nil { w.WriteHeader(500) } else { w.WriteHeader(201) } } next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiDestroyTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbAdapter, uploads string) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { // check table actually belongs to this user user := util.GetUserFromToken(req) tables, err := util.GetTablesByUser(user, udb) if err == nil { destroy := false i := 0 table := models.TableKey{} err = json.NewDecoder(req.Body).Decode(&table) if err != nil { w.WriteHeader(400) return } for j, t := range tables { if t.Name == table.Name && t.Passcode == table.Passcode { // try to destroy it destroy = dbAdapter.DestroyTable(table) == nil i = j break } } if destroy { os.RemoveAll(filepath.Join(uploads, table.Name)) newTables := append(tables[:i], tables[i+1:]...) util.SetTablesForUser(user, newTables, udb) w.WriteHeader(204) } else { w.WriteHeader(404) } next.ServeHTTP(w, req) } else { w.WriteHeader(500) } next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiUploadImg(next http.Handler, dbAdapter mongodb.DbAdapter, uploads, uploadType string, uploadMax int) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { // get table from request body r, err := req.MultipartReader() if err != nil { fmt.Println(err.Error()) } f, err := r.ReadForm(int64(uploadMax << 20)) if err != nil { fmt.Println(err.Error()) } tableKey := models.TableKey{ Name: f.Value["name"][0], Passcode: f.Value["passcode"][0], } // check if this table exists if !dbAdapter.CheckTable(tableKey) { w.WriteHeader(404) next.ServeHTTP(w, req) return } // ensure data storage dir exists info, err := os.Stat(uploads) if !info.IsDir() { fmt.Println("uploads dir aint no dir") w.WriteHeader(500) next.ServeHTTP(w, req) return } else if err != nil { err = os.MkdirAll(uploads, 0755) if err != nil { fmt.Println(err.Error()) w.WriteHeader(500) next.ServeHTTP(w, req) } } err = os.MkdirAll(filepath.Join(uploads, tableKey.Name, uploadType), 0755) // check for filename; call create to overwrite regardless // get file data from multipart form header := f.File["file"][0] if strings.Contains(header.Filename, "/") { w.WriteHeader(422) next.ServeHTTP(w, req) return } file, err := header.Open() if err != nil { fmt.Println(err.Error()) w.WriteHeader(500) next.ServeHTTP(w, req) return } fileData, err := ioutil.ReadAll(file) if err != nil { w.WriteHeader(500) next.ServeHTTP(w, req) return } // write to file destPath := filepath.Join(uploads, tableKey.Name, uploadType, header.Filename) fmt.Println(destPath) dest, err := os.Create(destPath) if err != nil { fmt.Println(err.Error()) w.WriteHeader(500) next.ServeHTTP(w, req) return } dest.Write(fileData) dest.Close() // respond with URL so UI can update AddContextValue(req, "location", "/uploads/"+tableKey.Name+"/"+uploadType+"/"+header.Filename) next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiListImages(next http.Handler, uploads string, uploadType string, udb auth.UserStore, dbAdapter mongodb.DbAdapter) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { urlParams := req.Context().Value("params").(map[string]string) tableName := urlParams["Slug"] tableKey := models.TableKey{ Name: tableName, Passcode: req.FormValue("passcode"), } // check table actually belongs to this user user := util.GetUserFromToken(req) tables, err := util.GetTablesByUser(user, udb) if err == nil { ok := false for _, t := range tables { if t.Name == tableKey.Name && t.Passcode == tableKey.Passcode { ok = true } } if ok { if dbAdapter.CheckTable(tableKey) { destPath := filepath.Join(uploads, tableName, uploadType) files, err := ioutil.ReadDir(destPath) if err != nil { w.WriteHeader(500) next.ServeHTTP(w, req) return } fileNames := []string{} for _, file := range files { if !file.IsDir() { fileNames = append(fileNames, "/uploads/"+tableName+"/"+uploadType+"/"+file.Name()) } } AddContextValue(req, "files", fileNames) next.ServeHTTP(w, req) return } } } w.WriteHeader(422) next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func apiDeleteImage(next http.Handler, uploads string, uploadType string, udb auth.UserStore, dbAdapter mongodb.DbAdapter) http.Handler { handlerFunc := func(w http.ResponseWriter, req *http.Request) { // put the path together urlParams := req.Context().Value("params").(map[string]string) tableName := urlParams["table"] tableKey := models.TableKey{ Name: tableName, Passcode: req.FormValue("passcode"), } // check table actually belongs to this user user := util.GetUserFromToken(req) tables, err := util.GetTablesByUser(user, udb) if err == nil { ok := false for _, t := range tables { if t.Name == tableKey.Name && t.Passcode == tableKey.Passcode { ok = true } } if ok { if dbAdapter.CheckTable(tableKey) { // if the file exists, delete it and return 201 filename := urlParams["file"] if strings.Contains(filename, "/") { w.WriteHeader(422) next.ServeHTTP(w, req) return } fullPath := filepath.Join(uploads, tableName, uploadType, filename) s, err := os.Stat(fullPath) if err == nil && !s.IsDir() { err = os.Remove(fullPath) if err == nil { w.WriteHeader(201) next.ServeHTTP(w, req) return } } } } } // otherwise, return an error w.WriteHeader(500) next.ServeHTTP(w, req) } return http.HandlerFunc(handlerFunc) } func CreateAdminInterface(udb auth.UserStore, dbAdapter mongodb.DbAdapter, uploads string, uploadMaxMB int) http.Handler { // create quartzgun router rtr := &router.Router{Fallback: *template.Must(template.ParseFiles("templates/error.html"))} rl := rateLimiter.IpRateLimiter{ Data: map[string]*rateLimiter.RateLimitData{}, Seconds: 5, AttemptsAllowed: 5, } scopes := map[string]string{} rtr.Post("/api/auth/", Throttle(Provision(udb, 84), rl.RateLimit)) // table management rtr.Get("/api/table/", Validate(apiGetTableList(renderer.JSON("tableList"), udb), udb, scopes)) rtr.Get(`/api/table/(?P\S+)`, Validate(apiGetTableData(renderer.JSON("tableData"), udb, dbAdapter), udb, scopes)) rtr.Post("/api/table/", Validate(apiCreateTable(renderer.JSON("result"), udb, dbAdapter), udb, scopes)) rtr.Delete(`/api/table/(?P\S+)`, Validate(apiDestroyTable(renderer.JSON("result"), udb, dbAdapter, uploads), udb, scopes)) // asset management rtr.Post(`/api/upload/(?P\S+)/map/`, Validate(apiUploadImg(renderer.JSON("location"), dbAdapter, uploads, "map", uploadMaxMB), udb, scopes)) rtr.Get(`/api/upload/(?P\S+)/map/`, Validate(apiListImages(renderer.JSON("files"), uploads, "map", udb, dbAdapter), udb, scopes)) rtr.Delete(`/api/upload/(?P\S+)/map/(?P\S+)`, Validate(apiDeleteImage(renderer.JSON("deleted"), uploads, "map", udb, dbAdapter), udb, scopes)) rtr.Delete(`/api/upload/(?P
\S+)/token/(?P\S+)`, Validate(apiDeleteImage(renderer.JSON("deleted"), uploads, "token", udb, dbAdapter), udb, scopes)) rtr.Post(`/api/upload/(?P\S+)/token/`, Validate(apiUploadImg(renderer.JSON("location"), dbAdapter, uploads, "token", uploadMaxMB), udb, scopes)) rtr.Get(`/api/upload/(?P\S+)/token/`, Validate(apiListImages(renderer.JSON("files"), uploads, "token", udb, dbAdapter), udb, scopes)) return http.HandlerFunc(rtr.ServeHTTP) }