starting admin middleware

This commit is contained in:
Iris Lightshard 2022-12-21 21:45:15 -07:00
parent 6911337ffd
commit 464a159354
Signed by: Iris Lightshard
GPG key ID: 3B7FBC22144E6398
9 changed files with 282 additions and 20 deletions

71
admin/admin.go Normal file
View file

@ -0,0 +1,71 @@
package admin
import (
"json"
"net/http"
"nilfm.cc/git/felt/models"
"nilfm.cc/git/quartzgun/auth"
"nilfm.cc/git/quartzgun/cookie"
"nilfm.cc/git/quartzgun/indentalUserDB"
. "nilfm.cc/git/quartzgun/middleware"
"nilfm.cc/git/quartzgun/renderer"
"nilfm.cc/git/quartzgun/router"
"strings"
)
func getUserFromToken(req *http.Request) string {
authHeader := req.Header.Get("Authorization")
if strings.HasPrefix(authHeader, "Bearer ") {
authToken := strings.Split(authHeader, "Bearer ")[1]
data, err := base64.StdEncoding.DecodeString(token)
if err == nil {
parts := strings.Split(string(data), "\n")
if len(parts) == 2 {
return parts[0]
}
}
}
return nil
}
func apiGetTableData(next http.Handler, udb auth.UserStore) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
// get username from
rawTableData, err := udb.GetData(user, "tables")
if err != nil {
// handle error - return 404 or 500?
}
// split rawTableData - tableName,passCode;tableName,passCode;
tables := strings.Split(rawTableData, ";")
self := make([]models.TableKey)
for _, t := range tables {
parts := strings.Split(t, ",")
if len(parts) == 2 {
self = append(self, models.TableKey{
Name: parts[0],
Passcode: parts[1],
})
}
}
*req = *req.WithContext(context.WithValue(req.Context(), "tableData", self))
next.serveHTTP(w, req)
}
return handlerFunc
}
func CreateAdminInterface(udb auth.UserStore) http.Handler {
// create quartzgun router
rtr := &router.Router{}
rtr.Post("api/auth", Provision(udb, 84))
// initialize routes with admin interface
rtr.Get(`api/table/?P<Slug>\S+)`, Validate(apiGetTableData(renderer.JSON("tableData"), udb)))
return router.ServeHTTP
}

37
cmd/cmd.go Normal file
View file

@ -0,0 +1,37 @@
package cmd
import (
"fmt"
"nilfm.cc/git/quartzgun/auth"
"strings"
)
func ProcessCmd(args []string, userStore auth.UserStore, cfg *Config) bool {
if len(args) == 1 {
return false
}
switch args[1] {
case "adduser":
if len(args) < 4 {
return help()
}
userStore.AddUser(args[2], args[3])
case "rmuser":
if len(args) < 3 {
return help()
}
userStore.DeleteUser(args[2])
case "passwd":
if len(args) < 5 {
return help()
}
userStore.ChangePassword(args[2], args[3], args[4])
default:
help()
}
return true
}
func help() bool {
return true
}

View file

@ -8,8 +8,8 @@ import (
"log" "log"
"net/http" "net/http"
"nhooyr.io/websocket" "nhooyr.io/websocket"
"nilfm.cc/git/felt/mongodb"
"nilfm.cc/git/felt/models" "nilfm.cc/git/felt/models"
"nilfm.cc/git/felt/mongodb"
"nilfm.cc/git/quartzgun/cookie" "nilfm.cc/git/quartzgun/cookie"
"sync" "sync"
"time" "time"
@ -38,7 +38,7 @@ func New(adapter mongodb.DbAdapter) *GameTableServer {
publishLimiter: rate.NewLimiter(rate.Every(time.Millisecond*100), 8), publishLimiter: rate.NewLimiter(rate.Every(time.Millisecond*100), 8),
dbAdapter: adapter, dbAdapter: adapter,
} }
srvr.serveMux.Handle("/", http.FileServer(http.Dir("./static"))) srvr.serveMux.Handle("/table/", http.FileServer(http.Dir("./static")))
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler) srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
srvr.serveMux.HandleFunc("/publish", srvr.publishHandler) srvr.serveMux.HandleFunc("/publish", srvr.publishHandler)

14
main.go
View file

@ -5,8 +5,8 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"nilfm.cc/git/felt/mongodb"
"nilfm.cc/git/felt/gametable" "nilfm.cc/git/felt/gametable"
"nilfm.cc/git/felt/mongodb"
"os" "os"
"os/signal" "os/signal"
"time" "time"
@ -24,14 +24,12 @@ func run() error {
if err != nil { if err != nil {
return err return err
} }
dbEngine := &mongodb.DbEngine{}
err = dbEngine.Init(os.Args[2])
if err != nil {
return err
}
dbEngine := &mongodb.DbEngine{}
err = dbEngine.Init(os.Args[2])
if err != nil {
return err
}
gt := gametable.New(dbEngine) gt := gametable.New(dbEngine)
s := &http.Server{ s := &http.Server{

View file

@ -32,12 +32,12 @@ type Table struct {
DiceRolls []DiceRoll DiceRolls []DiceRoll
Tokens []Token Tokens []Token
AvailableTokens []Token AvailableTokens []Token
AuxMessage string AuxMessage string
} }
type TableMessage struct { type TableMessage struct {
Roll DiceRoll Roll DiceRoll
Token Token Token Token
MapImg string MapImg string
AuxMsg string AuxMsg string
} }

View file

@ -2,13 +2,13 @@ package mongodb
import ( import (
"context" "context"
"errors"
"fmt"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"nilfm.cc/git/felt/models" "nilfm.cc/git/felt/models"
"time" "time"
"errors"
"fmt"
) )
const errNoCollection string = "collection not found: felt.%s" const errNoCollection string = "collection not found: felt.%s"
@ -29,7 +29,7 @@ type DbAdapter interface {
SetMapImageUrl(table models.TableKey, url string) error SetMapImageUrl(table models.TableKey, url string) error
GetMapImageUrl(table models.TableKey) (string, error) GetMapImageUrl(table models.TableKey) (string, error)
SetAuxMessage(table models.TableKey, message string) error SetAuxMessage(table models.TableKey, message string) error
GetAuxMessage(table models.TableKey) (string, error) GetAuxMessage(table models.TableKey) (string, error)
@ -297,7 +297,7 @@ func (self *DbEngine) RemoveToken(table models.TableKey, tokenId string, active
{"passcode", table.Passcode}, {"passcode", table.Passcode},
}, },
bson.D{ bson.D{
{"$pull", bson.D{{tokenArrKey, bson.E{"_id", tokenId}}}}, {"$pull", bson.D{{tokenArrKey, bson.D{{"_id", tokenId}}}}},
}, },
).Decode(&result) ).Decode(&result)
return err return err

30
notes.txt Normal file
View file

@ -0,0 +1,30 @@
admin routes:
- Get /login
- Post api/auth
- Get /dash {
dashboard. show list of tables, new table
}
- Get /new {
new table interface
}
- Post /new {
create new table
}
- Get /table/<name> {
edit given table - standard table view plus admin features
- manage availableTokens via /storage/ routes
- manage map bg
}
- Post/Put /storage/<table>/<type>/<name> {
upload token or map bg
}
- Delete /storage/<table>/<type>/<name> {
delete token
}
- Get /storage/ {
static storage tree
}

50
static/index.html Normal file
View file

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8" />
<title>Felt</title>
<meta name="viewport" content="width=device-width" />
<link href="/style.css" rel="stylesheet" />
</head>
<body>
<nav>
<input id="name_entry">
<button id="goto_table">Change Table</button>
<button id="admin_login">Admin Login</button>
</nav>
<div id="dynamic_modal"></div>
<div id="dice_log"></div>
<select name="num_dice">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
<option>6</option>
<option>7</option>
<option>8</option>
<option>9</option>
<option>10</option>
<option>11</option>
<option>12</option>
<option>13</option>
<option>14</option>
<option>15</option>
<option>16</option>
<option>17</option>
<option>18</option>
<option>19</option>
<option>20</option>
</select>
<label for="dice_faces">d</label>
<select id="dice_faces">
<option>4</option>
<option selected>6</option>
<option>8</option>
<option>10</option>
<option>12</option>
<option>20</option>
</select>
<input id="dice_note"><button id="dice_submit">Roll</button>
<div id="map"></div>
</body>

76
static/index.js Normal file
View file

@ -0,0 +1,76 @@
;(() => {
// expectingMessage is set to true
// if the user has just submitted a message
// and so we should scroll the next message into view when received.
let expectingMessage = false
function dial() {
const conn = new WebSocket(`ws://${location.host}/subscribe`)
conn.addEventListener("close", ev => {
appendLog(`WebSocket Disconnected code: ${ev.code}, reason: ${ev.reason}`, true)
if (ev.code !== 1001) {
appendLog("Reconnecting in 1s", true)
setTimeout(dial, 1000)
}
})
conn.addEventListener("open", ev => {
console.info("websocket connected")
})
// This is where we handle messages received.
conn.addEventListener("message", ev => {
if (typeof ev.data !== "string") {
console.error("unexpected message type", typeof ev.data)
return
}
const p = appendLog(ev.data)
if (expectingMessage) {
p.scrollIntoView()
expectingMessage = false
}
})
}
dial()
const messageLog = document.getElementById("message-log")
const publishForm = document.getElementById("publish-form")
const messageInput = document.getElementById("message-input")
// appendLog appends the passed text to messageLog.
function appendLog(text, error) {
const p = document.createElement("p")
// Adding a timestamp to each message makes the log easier to read.
p.innerText = `${new Date().toLocaleTimeString()}: ${text}`
if (error) {
p.style.color = "red"
p.style.fontStyle = "bold"
}
messageLog.append(p)
return p
}
appendLog("Submit a message to get started!")
// onsubmit publishes the message from the user when the form is submitted.
publishForm.onsubmit = async ev => {
ev.preventDefault()
const msg = messageInput.value
if (msg === "") {
return
}
messageInput.value = ""
expectingMessage = true
try {
const resp = await fetch("/publish", {
method: "POST",
body: msg,
})
if (resp.status !== 202) {
throw new Error(`Unexpected HTTP Status ${resp.status} ${resp.statusText}`)
}
} catch (err) {
appendLog(`Publish failed: ${err.message}`, true)
}
}
})()