delete assets when destroying table, clear dice message on roll, clear map on socket disconnect; start registration controller

This commit is contained in:
Iris Lightshard 2023-07-09 23:41:30 -06:00
parent e7081a5320
commit 952f80dbc2
15 changed files with 151 additions and 35 deletions

View file

@ -110,7 +110,7 @@ func apiCreateTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbA
return http.HandlerFunc(handlerFunc)
}
func apiDestroyTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.DbAdapter) http.Handler {
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)
@ -139,6 +139,7 @@ func apiDestroyTable(next http.Handler, udb auth.UserStore, dbAdapter mongodb.Db
}
if destroy {
os.RemoveAll(filepath.Join(uploads, table.Name))
newTables := append(tables[:i], tables[i+1:]...)
util.SetTablesForUser(user, newTables, udb)
w.WriteHeader(204)
@ -338,7 +339,7 @@ func apiDeleteImage(next http.Handler, uploads string, uploadType string, udb au
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("static/error.html"))}
rtr := &router.Router{Fallback: *template.Must(template.ParseFiles("templates/error.html"))}
scopes := map[string]string{}
@ -348,7 +349,7 @@ func CreateAdminInterface(udb auth.UserStore, dbAdapter mongodb.DbAdapter, uploa
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), udb, scopes))
rtr.Delete(`/api/table/(?P<Slug>\S+)`, Validate(apiDestroyTable(renderer.JSON("result"), udb, dbAdapter, uploads), udb, scopes))
// asset management
rtr.Post(`/api/upload/(?P<Slug>\S+)/map/`, Validate(apiUploadImg(renderer.JSON("location"), dbAdapter, uploads, "map", uploadMaxMB), udb, scopes))
@ -357,7 +358,6 @@ func CreateAdminInterface(udb auth.UserStore, dbAdapter mongodb.DbAdapter, uploa
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))
// DELETE /api/upload/<table>/token/<token>
return http.HandlerFunc(rtr.ServeHTTP)
}

View file

@ -10,10 +10,11 @@ import (
)
type Config struct {
Port int
Uploads string
UploadMaxMB int
MongoURI string
Port int
Uploads string
UploadMaxMB int
MongoURI string
RegistrationSecret string
}
func GetConfigLocation() string {
@ -81,6 +82,10 @@ func (self *Config) RunWizard() {
fmt.Printf("Max file upload size (MB)? ")
self.UploadMaxMB = ensureNumberOption(&inputBuf)
fmt.Printf("Encryption secret for admin invite codes? ")
ensureNonEmptyOption(&inputBuf)
self.RegistrationSecret = inputBuf
fmt.Printf("Configuration complete!\n")
self.Write()
}

View file

@ -10,6 +10,7 @@ import (
"hacklab.nilfm.cc/felt/admin"
"hacklab.nilfm.cc/felt/models"
"hacklab.nilfm.cc/felt/mongodb"
"hacklab.nilfm.cc/felt/register"
"hacklab.nilfm.cc/quartzgun/auth"
"hacklab.nilfm.cc/quartzgun/renderer"
"io/ioutil"
@ -37,7 +38,7 @@ type GameTableServer struct {
udb auth.UserStore
}
func New(adapter mongodb.DbAdapter, udb auth.UserStore, uploads string, uploadMaxMB int) *GameTableServer {
func New(adapter mongodb.DbAdapter, udb auth.UserStore, uploads string, uploadMaxMB int, registrationSecret string) *GameTableServer {
srvr := &GameTableServer{
subscribeMessageBuffer: 16,
logf: log.Printf,
@ -49,6 +50,7 @@ func New(adapter mongodb.DbAdapter, udb auth.UserStore, uploads string, uploadMa
srvr.serveMux.Handle("/table/", http.StripPrefix("/table/", renderer.Subtree("./static")))
srvr.serveMux.Handle("/uploads/", http.StripPrefix("/uploads/", renderer.Subtree(uploads)))
srvr.serveMux.Handle("/admin/", http.StripPrefix("/admin", admin.CreateAdminInterface(udb, adapter, uploads, uploadMaxMB)))
srvr.serveMux.Handle("/register/", http.StripPrefix("/register", register.CreateRegistrationInterface(udb, registrationSecret)))
srvr.serveMux.HandleFunc("/subscribe", srvr.subscribeHandler)
srvr.serveMux.HandleFunc("/publish", srvr.publishHandler)

View file

@ -48,7 +48,7 @@ func run() error {
return err
}
gt := gametable.New(dbEngine, udb, cfg.Uploads, cfg.UploadMaxMB)
gt := gametable.New(dbEngine, udb, cfg.Uploads, cfg.UploadMaxMB, cfg.RegistrationSecret)
s := &http.Server{
Handler: gt,
ReadTimeout: time.Second * 10,

91
register/register.go Normal file
View file

@ -0,0 +1,91 @@
package register
import (
"context"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"html/template"
"net/http"
"hacklab.nilfm.cc/quartzgun/auth"
"hacklab.nilfm.cc/quartzgun/renderer"
"hacklab.nilfm.cc/quartzgun/router"
)
var bytes = []byte{99, 207, 33, 57, 28, 01, 50, 76, 01}
type SymmetricCrypto interface {
Encode(b []byte) string
Decode(s string) []byte
Encrypt(text string) (string, error)
Decrypt(text string) (string, error)
}
type SymmetricCrypt struct {
Secret string
}
func (self *SymmetricCrypt) Encode(b []byte) string {
return base64.StdEncoding.EncodeToString(b)
}
func (self *SymmetricCrypt) Decode(s string) []byte {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
panic(err)
}
return data
}
func (self *SymmetricCrypt) Encrypt(text string) (string, error) {
block, err := aes.NewCipher([]byte(self.Secret))
if err != nil {
return "", err
}
plainText := []byte(text)
cfb := cipher.NewCFBEncrypter(block, bytes)
cipherText := make([]byte, len(plainText))
cfb.XORKeyStream(cipherText, plainText)
return self.Encode(cipherText), nil
}
func (self *SymmetricCrypt) Decrypt(text string) (string, error) {
block, err := aes.NewCipher([]byte(self.Secret))
if err != nil {
return "", err
}
cipherText := self.Decode(text)
cfb := cipher.NewCFBDecrypter(block, bytes)
plainText := make([]byte, len(cipherText))
cfb.XORKeyStream(plainText, cipherText)
return string(plainText), nil
}
func WithCrypto(next http.Handler, crypto SymmetricCrypto) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
*req = *req.WithContext(context.WithValue(req.Context(), "crypto", crypto))
next.ServeHTTP(w, req)
}
return http.HandlerFunc(handlerFunc)
}
func WithUserStore(next http.Handler, udb auth.UserStore) http.Handler {
handlerFunc := func(w http.ResponseWriter, req *http.Request) {
*req = *req.WithContext(context.WithValue(req.Context(), "udb", udb))
next.ServeHTTP(w, req)
}
return http.HandlerFunc(handlerFunc)
}
func CreateRegistrationInterface(udb auth.UserStore, secret string) http.Handler {
rtr := &router.Router{Fallback: *template.Must(template.ParseFiles("templates/error.html"))}
crypto := &SymmetricCrypt{Secret: secret}
rtr.Get(`/(?P<cipher>.*)`, WithCrypto(renderer.Template("templates/register.html"), crypto))
rtr.Post(`/(?P<cipher>.*)`, WithUserStore(WithCrypto(renderer.Template("templates/registered.html"), crypto), udb))
return http.HandlerFunc(rtr.ServeHTTP)
}

View file

@ -180,9 +180,11 @@ function drawTokenOrigin() {
function reinitializeSpritePreview() {
const img = document.createElement("img");
img.src = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].value;
const tokenNameParts = tokenSpriteDropdown[tokenSpriteDropdown.selectedIndex].text.split(".");
tokenNameParts.pop();
tokenName.value = tokenNameParts.join(".");
img.onload = () => {
const w = img.naturalWidth;
const h = img.naturalHeight;
@ -191,6 +193,7 @@ function reinitializeSpritePreview() {
tokenHeight.value = h;
scaleSpritePreview();
}
previewZone.innerHTML = "";
previewZone.appendChild(img);
}
@ -425,12 +428,20 @@ async function doLogin() {
adminWrapper.style.display="inline";
adminZone.style.display = "block";
closeErr();
replaceAdminModal();
} else {
setErr("Incorrect credentials");
}
}
}
function replaceAdminModal() {
const adminModal = document.getElementById("admin_modal");
if (adminModal) {
adminModal.innerHTML = "<a href='./'>Logout</a>";
}
}
async function getAdminToken(user, pass) {
const headers = new Headers();
headers.set('Authorization', 'Basic ' + btoa(user + ":" + pass));

View file

@ -30,5 +30,6 @@ function rollDice() {
note: note.value,
timestamp: new Date(),
}});
note.value = "";
}
}

View file

@ -14,7 +14,7 @@
<nav>
<section id="user_section">
<details class="ui_win"><summary>identity</summary>
<details class="ui_win" open><summary>identity</summary>
<label for="name_entry">username</label>
<input id="name_entry" onblur="saveName()">
</details><br/>

View file

@ -7,7 +7,7 @@ function initializeMap(mapImgUrl) {
let init = false;
if (!map) {
init = true;
map = L.map('map', { minZoom: 0, maxZoom: 4, crs: L.CRS.Simple });
map = L.map('map', { minZoom: 0, maxZoom: 4, crs: L.CRS.Simple, attributionControl: true, zoomControl: false });
map.on("zoomend", ()=>{resizeMarkers();scaleSpritePreview();});
}
if (mapImg) {
@ -19,8 +19,6 @@ function initializeMap(mapImgUrl) {
if (init) {
map.setView([0,0], 2);
}
/*
*/
}
// this works but assumes the map is square (reasonable limitation I think)
@ -113,7 +111,7 @@ function NewToken(token) {
});
marker.on("moveend", ()=>{moveToken(token.id)});
return {
t: token,
m: marker,

View file

@ -89,7 +89,7 @@ function makeUpToDate(table) {
if (table.diceRolls) {
logDice(table.diceRolls);
} else if (table.diceRoll) {
logDice([table.diceRoll]);
logDice(table.diceRoll);
}
if (table.tokens) {
updateTokens(table.tokens);
@ -135,10 +135,12 @@ function dial() {
tabletop.style.display = "none";
}
table = null;
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
tokens.shift();
}
while (tokens.some(t=>t)) {
tokens[0].m.removeFrom(map);
tokens.shift();
}
mapImg.removeFrom(map);
mapImg = null;
}
});
conn.addEventListener("open", e => {

View file

@ -1,5 +1,5 @@
:root {
--bg_color: rgba(0,0,0,0.7);
--bg_color: #000000cc;
--fg_color: #ccc;
--main_color: #1f9b92;
--sub_color: #002b36;

View file

@ -1,6 +1,13 @@
const errDiv = document.getElementById("errDiv");
const errWrapper = document.getElementById("errWrapper");
const defaultTheme = [ "#000000cc", "#ccccccff", "#1f9b92ff", "#002b36ff" ];
const saveData = {
username: "",
theme: defaultTheme,
}
function setErr(x) {
if (errDiv) {
errDiv.innerHTML = x;
@ -16,23 +23,22 @@ function closeErr() {
}
}
function loadStorage() {
saveData.username = localStorage.getItem("username");
saveData.theme = JSON.parse(localStorage.getItem("theme"));
const username = document.getElementById("name_entry");
if (username) {
username.value = saveData.username;
}
}
function saveName() {
console.log("saving username");
const username = document.getElementById("name_entry");
if (username) {
document.cookie = "username=" + username.value;
}
}
function loadName() {
const username = document.getElementById("name_entry");
if (username) {
const cookies = document.cookie.split(";")
cookies.forEach(c=>{
if (c.trim().startsWith("username=")) {
username.value = c.trim().split("=")[1];
}
});
saveData.username = username.value;
localStorage.setItem("username", saveData.username);
}
}
@ -47,4 +53,4 @@ function setupDiceAutoScroll() {
}
setupDiceAutoScroll();
loadName();
loadStorage();

0
templates/register.html Normal file
View file

View file