felt/admin/admin.go

383 lines
11 KiB
Go
Raw Normal View History

2022-12-22 04:45:15 +00:00
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"
2023-03-04 05:05:57 +00:00
"io/ioutil"
"net/http"
2023-03-04 05:05:57 +00:00
"os"
"path/filepath"
"regexp"
2024-05-04 01:21:51 +00:00
"strings"
2022-12-22 04:45:15 +00:00
)
2022-12-22 16:39:39 +00:00
func apiGetTableList(next http.Handler, udb auth.UserStore) http.Handler {
2022-12-22 04:45:15 +00:00
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
user := util.GetUserFromToken(req)
self, err := util.GetTablesByUser(user, udb)
2022-12-22 04:45:15 +00:00
if err != nil {
w.WriteHeader(404)
} else {
AddContextValue(req, "tableList", self)
2022-12-22 04:45:15 +00:00
}
next.ServeHTTP(w, req)
}
2022-12-22 04:45:15 +00:00
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{
2023-07-08 05:56:41 +00:00
Name: tableKey.Name,
Passcode: tableKey.Passcode,
DiceRolls: diceRolls,
MapImageUrl: mapUrl,
Tokens: tokens,
AuxMessage: auxMessage,
})
} else {
w.WriteHeader(404)
2022-12-22 04:45:15 +00:00
}
2022-12-22 16:39:39 +00:00
next.ServeHTTP(w, req)
2022-12-22 04:45:15 +00:00
}
2022-12-22 16:39:39 +00:00
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
2023-01-05 04:59:54 +00:00
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
2023-01-05 04:59:54 +00:00
}
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
}
2023-01-05 04:59:54 +00:00
}
if destroy {
os.RemoveAll(filepath.Join(uploads, table.Name))
2023-01-05 04:59:54 +00:00
newTables := append(tables[:i], tables[i+1:]...)
util.SetTablesForUser(user, newTables, udb)
w.WriteHeader(204)
} else {
w.WriteHeader(404)
}
2023-03-04 05:05:57 +00:00
next.ServeHTTP(w, req)
2023-01-05 04:59:54 +00:00
} else {
w.WriteHeader(500)
}
next.ServeHTTP(w, req)
}
return http.HandlerFunc(handlerFunc)
2022-12-22 04:45:15 +00:00
}
func apiUploadImg(next http.Handler, dbAdapter mongodb.DbAdapter, uploads, uploadType string, uploadMax int) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
2023-03-04 05:05:57 +00:00
// get table from request body
r, err := req.MultipartReader()
2023-03-04 05:05:57 +00:00
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],
2023-03-04 05:05:57 +00:00
}
// check if this table exists
if !dbAdapter.CheckTable(tableKey) {
w.WriteHeader(404)
2023-03-04 05:05:57 +00:00
next.ServeHTTP(w, req)
return
}
// ensure data storage dir exists
2023-03-04 05:05:57 +00:00
info, err := os.Stat(uploads)
if !info.IsDir() {
fmt.Println("uploads dir aint no dir")
2023-03-04 05:05:57 +00:00
w.WriteHeader(500)
next.ServeHTTP(w, req)
return
2023-03-04 05:05:57 +00:00
} else if err != nil {
err = os.MkdirAll(uploads, 0755)
2023-03-04 05:05:57 +00:00
if err != nil {
fmt.Println(err.Error())
2023-03-04 05:05:57 +00:00
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]
2024-05-04 01:21:51 +00:00
if strings.Contains(header.Filename, "/") {
w.WriteHeader(422)
next.ServeHTTP(w, req)
return
2024-05-04 01:21:51 +00:00
}
file, err := header.Open()
2023-03-04 05:05:57 +00:00
if err != nil {
fmt.Println(err.Error())
2023-03-04 05:05:57 +00:00
w.WriteHeader(500)
next.ServeHTTP(w, req)
2024-05-04 01:21:51 +00:00
return
2023-03-04 05:05:57 +00:00
}
fileData, err := ioutil.ReadAll(file)
if err != nil {
w.WriteHeader(500)
next.ServeHTTP(w, req)
2024-05-04 01:21:51 +00:00
return
2023-03-04 05:05:57 +00:00
}
// write to file
2023-03-04 05:05:57 +00:00
destPath := filepath.Join(uploads, tableKey.Name, uploadType, header.Filename)
fmt.Println(destPath)
2023-03-04 05:05:57 +00:00
dest, err := os.Create(destPath)
if err != nil {
fmt.Println(err.Error())
2023-03-04 05:05:57 +00:00
w.WriteHeader(500)
next.ServeHTTP(w, req)
return
2023-03-04 05:05:57 +00:00
}
dest.Write(fileData)
dest.Close()
2023-03-04 05:05:57 +00:00
// respond with URL so UI can update
AddContextValue(req, "location", "/uploads/"+tableKey.Name+"/"+uploadType+"/"+header.Filename)
next.ServeHTTP(w, req)
}
2023-02-26 21:22:18 +00:00
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
}
}
}
2023-07-05 07:20:56 +00:00
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
2023-07-05 07:20:56 +00:00
urlParams := req.Context().Value("params").(map[string]string)
2023-07-05 05:54:43 +00:00
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) {
2023-07-05 07:20:56 +00:00
// if the file exists, delete it and return 201
filename := urlParams["file"]
2024-05-04 01:21:51 +00:00
if strings.Contains(filename, "/") {
w.WriteHeader(422)
next.ServeHTTP(w, req)
return
2024-05-04 01:21:51 +00:00
}
2023-07-05 07:20:56 +00:00
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
2023-07-05 05:54:43 +00:00
w.WriteHeader(500)
next.ServeHTTP(w, req)
}
return http.HandlerFunc(handlerFunc)
}
2023-02-26 21:22:18 +00:00
func CreateAdminInterface(udb auth.UserStore, dbAdapter mongodb.DbAdapter, uploads string, uploadMaxMB int) http.Handler {
2022-12-22 04:45:15 +00:00
// 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,
}
2022-12-22 16:39:39 +00:00
scopes := map[string]string{}
2022-12-22 04:45:15 +00:00
rtr.Post("/api/auth/", Throttle(Provision(udb, 84), rl.RateLimit))
2023-02-26 21:22:18 +00:00
// table management
rtr.Get("/api/table/", Validate(apiGetTableList(renderer.JSON("tableList"), udb), udb, scopes))
rtr.Get(`/api/table/(?P<Slug>\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<Slug>\S+)`, Validate(apiDestroyTable(renderer.JSON("result"), udb, dbAdapter, uploads), udb, scopes))
2022-12-22 04:45:15 +00:00
2023-02-26 21:22:18 +00:00
// asset management
rtr.Post(`/api/upload/(?P<Slug>\S+)/map/`, Validate(apiUploadImg(renderer.JSON("location"), dbAdapter, uploads, "map", uploadMaxMB), udb, scopes))
rtr.Get(`/api/upload/(?P<Slug>\S+)/map/`, Validate(apiListImages(renderer.JSON("files"), uploads, "map", udb, dbAdapter), udb, scopes))
rtr.Delete(`/api/upload/(?P<table>\S+)/map/(?P<file>\S+)`, Validate(apiDeleteImage(renderer.JSON("deleted"), uploads, "map", udb, dbAdapter), udb, scopes))
rtr.Delete(`/api/upload/(?P<table>\S+)/token/(?P<file>\S+)`, Validate(apiDeleteImage(renderer.JSON("deleted"), uploads, "token", udb, dbAdapter), udb, scopes))
rtr.Post(`/api/upload/(?P<Slug>\S+)/token/`, Validate(apiUploadImg(renderer.JSON("location"), dbAdapter, uploads, "token", uploadMaxMB), udb, scopes))
rtr.Get(`/api/upload/(?P<Slug>\S+)/token/`, Validate(apiListImages(renderer.JSON("files"), uploads, "token", udb, dbAdapter), udb, scopes))
2023-02-26 21:22:18 +00:00
2022-12-22 16:39:39 +00:00
return http.HandlerFunc(rtr.ServeHTTP)
2022-12-22 04:45:15 +00:00
}