delete assets when destroying table, clear dice message on roll, clear map on socket disconnect; start registration controller
This commit is contained in:
parent
e7081a5320
commit
952f80dbc2
15 changed files with 151 additions and 35 deletions
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ type Config struct {
|
|||
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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
2
main.go
2
main.go
|
@ -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
91
register/register.go
Normal 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)
|
||||
}
|
|
@ -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));
|
||||
|
|
|
@ -30,5 +30,6 @@ function rollDice() {
|
|||
note: note.value,
|
||||
timestamp: new Date(),
|
||||
}});
|
||||
note.value = "";
|
||||
}
|
||||
}
|
|
@ -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/>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
|
@ -139,6 +139,8 @@ function dial() {
|
|||
tokens[0].m.removeFrom(map);
|
||||
tokens.shift();
|
||||
}
|
||||
mapImg.removeFrom(map);
|
||||
mapImg = null;
|
||||
}
|
||||
});
|
||||
conn.addEventListener("open", e => {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
0
templates/register.html
Normal file
0
templates/registered.html
Normal file
0
templates/registered.html
Normal file
Loading…
Reference in a new issue