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)

10
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"
@ -25,14 +25,12 @@ func run() error {
return err return err
} }
dbEngine := &mongodb.DbEngine{} dbEngine := &mongodb.DbEngine{}
err = dbEngine.Init(os.Args[2]) err = dbEngine.Init(os.Args[2])
if err != nil { if err != nil {
return err return err
} }
gt := gametable.New(dbEngine) gt := gametable.New(dbEngine)
s := &http.Server{ s := &http.Server{
Handler: gt, Handler: gt,

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"
@ -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)
}
}
})()