Now you can hit enter to sign in

This commit is contained in:
Neale Pickett 2019-02-22 19:09:38 -07:00
parent 02ed2162c2
commit 8e67abe0c0
9 changed files with 80 additions and 43 deletions

View File

@ -28,6 +28,16 @@ def get_seed(request):
else: else:
return int(seedstr) return int(seedstr)
def get_puzzle(request):
seed = get_seed(request)
category = request.match_info.get("category")
points = int(request.match_info.get("points"))
filename = request.match_info.get("filename")
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
puzzle = cat.puzzle(points)
return puzzle
async def handle_puzzlelist(request): async def handle_puzzlelist(request):
seed = get_seed(request) seed = get_seed(request)
@ -87,6 +97,16 @@ async def handle_puzzlefile(request):
content_type=content_type, content_type=content_type,
) )
async def handle_answer(request):
seed = get_seed(request)
category = request.match_info.get("category")
points = int(request.match_info.get("points"))
filename = request.match_info.get("filename")
cat = moth.Category(request.app["puzzles_dir"].joinpath(category), seed)
puzzle = cat.puzzle(points)
async def handle_mothballer(request): async def handle_mothballer(request):
seed = get_seed(request) seed = get_seed(request)
@ -113,19 +133,37 @@ async def handle_index(request):
seed = random.getrandbits(32) seed = random.getrandbits(32)
body = """<!DOCTYPE html> body = """<!DOCTYPE html>
<html> <html>
<head><title>Dev Server</title></head> <head>
<title>Dev Server</title>
<script>
// Skip trying to log in
sessionStorage.setItem("id", "Hello from the development server")
</script>
</head>
<body> <body>
<h1>Dev Server</h1> <h1>Dev Server</h1>
<p> <p>
You need to provide the contest seed in the URL. Pick a seed:
If you don't have a contest seed in mind,
why not try <a href="{seed}/">{seed}</a>?
</p> </p>
<ul>
<li><a href="{seed}/">{seed}</a>: a special seed I made just for you!</li>
<li><a href="random/">random</a>: will use a different seed every time you load a page (could be useful for debugging)</li>
<li>You can also hack your own seed into the URL, if you want to.</li>
</ul>
<p> <p>
If you are chaotic, Puzzles can be generated from Python code: these puzzles can use a random number generator if they want.
you could even take your chances with a The seed is used to create these random numbers.
<a href="random/">random seed</a> for every HTTP request. </p>
This means generated files will get a different seed than the puzzle itself!
<p>
We like to make a new seed for every contest,
and re-use that seed whenever we regenerate a category during an event
(say to fix a bug).
By using the same seed,
we make sure that all the dynamically-generated puzzles have the same values
in any new packages we build.
</p> </p>
</body> </body>
</html> </html>
@ -140,12 +178,8 @@ async def handle_static(request):
themes = request.app["theme_dir"] themes = request.app["theme_dir"]
fn = request.match_info.get("filename") fn = request.match_info.get("filename")
if not fn: if not fn:
for fn in ("puzzle-list.html", "index.html"): fn = "index.html"
path = themes.joinpath(fn) path = themes.joinpath(fn)
if path.exists():
break
else:
path = themes.joinpath(fn)
return web.FileResponse(path) return web.FileResponse(path)
@ -182,7 +216,7 @@ if __name__ == '__main__':
app["puzzles_dir"] = pathlib.Path(args.puzzles) app["puzzles_dir"] = pathlib.Path(args.puzzles)
app["theme_dir"] = pathlib.Path(args.theme) app["theme_dir"] = pathlib.Path(args.theme)
app.router.add_route("GET", "/", handle_index) app.router.add_route("GET", "/", handle_index)
app.router.add_route("GET", "/{seed}/puzzles.json", handle_puzzlelist) app.router.add_route("*", "/{seed}/puzzles.json", handle_puzzlelist)
app.router.add_route("GET", "/{seed}/content/{category}/{points}/puzzle.json", handle_puzzle) app.router.add_route("GET", "/{seed}/content/{category}/{points}/puzzle.json", handle_puzzle)
app.router.add_route("GET", "/{seed}/content/{category}/{points}/{filename}", handle_puzzlefile) app.router.add_route("GET", "/{seed}/content/{category}/{points}/{filename}", handle_puzzlefile)
app.router.add_route("GET", "/{seed}/mothballer/{category}", handle_mothballer) app.router.add_route("GET", "/{seed}/mothballer/{category}", handle_mothballer)

View File

@ -175,6 +175,12 @@ func (ctx *Instance) answerHandler(w http.ResponseWriter, req *http.Request) {
} }
func (ctx *Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) { func (ctx *Instance) puzzlesHandler(w http.ResponseWriter, req *http.Request) {
teamid := req.FormValue("id")
if _, err := ctx.TeamName(teamid); err != nil {
http.Error(w, "Unauthorized: must provide team ID", http.StatusUnauthorized)
return
}
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
w.Write(ctx.jPuzzleList) w.Write(ctx.jPuzzleList)
@ -273,13 +279,7 @@ func (ctx *Instance) ServeHTTP(wOrig http.ResponseWriter, r *http.Request) {
w: wOrig, w: wOrig,
statusCode: new(int), statusCode: new(int),
} }
w.Header().Set("WWW-Authenticate", "Basic") ctx.mux.ServeHTTP(w, r)
_, password, _ := r.BasicAuth()
if password != ctx.Password {
http.Error(w, "Authentication Required", 401)
} else {
ctx.mux.ServeHTTP(w, r)
}
log.Printf( log.Printf(
"%s %s %s %d\n", "%s %s %s %d\n",
r.RemoteAddr, r.RemoteAddr,

View File

@ -19,7 +19,6 @@ type Instance struct {
MothballDir string MothballDir string
StateDir string StateDir string
ResourcesDir string ResourcesDir string
Password string
Categories map[string]*Mothball Categories map[string]*Mothball
update chan bool update chan bool
jPuzzleList []byte jPuzzleList []byte
@ -27,13 +26,12 @@ type Instance struct {
mux *http.ServeMux mux *http.ServeMux
} }
func NewInstance(base, mothballDir, stateDir, resourcesDir, password string) (*Instance, error) { func NewInstance(base, mothballDir, stateDir, resourcesDir string) (*Instance, error) {
ctx := &Instance{ ctx := &Instance{
Base: strings.TrimRight(base, "/"), Base: strings.TrimRight(base, "/"),
MothballDir: mothballDir, MothballDir: mothballDir,
StateDir: stateDir, StateDir: stateDir,
ResourcesDir: resourcesDir, ResourcesDir: resourcesDir,
Password: password,
Categories: map[string]*Mothball{}, Categories: map[string]*Mothball{},
update: make(chan bool, 10), update: make(chan bool, 10),
mux: http.NewServeMux(), mux: http.NewServeMux(),

View File

@ -37,11 +37,6 @@ func main() {
"/theme", "/theme",
"Path to static theme resources (HTML, images, css, ...)", "Path to static theme resources (HTML, images, css, ...)",
) )
password := flag.String(
"password",
"sesame",
"Pass Word (in the 1920s sense) to view the site. Not a secure passphrase.",
)
maintenanceInterval := flag.Duration( maintenanceInterval := flag.Duration(
"maint", "maint",
20*time.Second, 20*time.Second,
@ -58,7 +53,7 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir, *password) ctx, err := NewInstance(*base, *mothballDir, *stateDir, *themeDir)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -2,6 +2,7 @@
<html> <html>
<head> <head>
<title>MOTH</title> <title>MOTH</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="basic.css"> <link rel="stylesheet" href="basic.css">
<script src="moth.js"></script> <script src="moth.js"></script>
@ -11,11 +12,11 @@
<section> <section>
<div id="messages"></div> <div id="messages"></div>
<div id="login"> <form id="login">
Team name: <input name="name"> Team name: <input name="name">
Team ID: <input name="id"> <br> Team ID: <input name="id"> <br>
<button id="submit">Sign In</button> <input type="submit" value="Sign In">
</div> </form>
<div id="puzzles"></div> <div id="puzzles"></div>
@ -23,6 +24,7 @@
<nav> <nav>
<ul> <ul>
<li><a href="scoreboard.html">Scoreboard</a></li> <li><a href="scoreboard.html">Scoreboard</a></li>
<li><a href="logout.html">Sign Out</a></li>
</ul> </ul>
</nav> </nav>
</body> </body>

View File

@ -15,7 +15,7 @@ sessionStorage.removeItem("id")
</section> </section>
<nav> <nav>
<ul> <ul>
<li><a href="index.html">login</a></li> <li><a href="index.html">Sign In</a></li>
<li><a href="scoreboard.html">Scoreboard</a></li> <li><a href="scoreboard.html">Scoreboard</a></li>
</ul> </ul>
</nav> </nav>

View File

@ -64,6 +64,7 @@ function renderPuzzles(obj) {
i.appendChild(a) i.appendChild(a)
a.textContent = points a.textContent = points
a.href = "puzzle.html?cat=" + cat + "&points=" + points + "&pid=" + id a.href = "puzzle.html?cat=" + cat + "&points=" + points + "&pid=" + id
a.target = "_blank"
} }
} }
@ -76,13 +77,10 @@ function renderPuzzles(obj) {
container.firstChild.remove() container.firstChild.remove()
} }
container.appendChild(puzzlesElement) container.appendChild(puzzlesElement)
container.style.display = "none"
document.getElementById("login").style.display = "block"
} }
function heartbeat(teamId) { function heartbeat(teamId) {
rpc("puzzles.json", {teamid: teamId}) rpc("puzzles.json", {id: teamId})
.then(resp => { .then(resp => {
if (resp.ok) { if (resp.ok) {
resp.json() resp.json()
@ -103,16 +101,20 @@ function showPuzzles(teamId) {
let spinner = document.createElement("span") let spinner = document.createElement("span")
spinner.classList.add("spinner") spinner.classList.add("spinner")
sessionStorage.setItem("id", teamId)
document.getElementById("login").style.display = "none" document.getElementById("login").style.display = "none"
document.getElementById("puzzles").appendChild(spinner) document.getElementById("puzzles").appendChild(spinner)
heartbeat(teamId) heartbeat(teamId)
setInterval(e => { heartbeat(teamId) }, 40000) setInterval(e => { heartbeat(teamId) }, 40000)
} }
function login() { function login(e) {
let name = document.querySelector("[name=name]").value let name = document.querySelector("[name=name]").value
let id = document.querySelector("[name=id]").value let id = document.querySelector("[name=id]").value
e.preventDefault()
rpc("register", { rpc("register", {
name: name, name: name,
id: id, id: id,
@ -158,7 +160,13 @@ function toast(message, timeout=5000) {
} }
function init() { function init() {
document.getElementById("submit").addEventListener("click", login) // Already signed in?
let id = sessionStorage.getItem("id")
if (id) {
showPuzzles(id)
}
document.getElementById("login").addEventListener("submit", login)
} }
if (document.readyState === "loading") { if (document.readyState === "loading") {

View File

@ -94,7 +94,7 @@ if (document.readyState === "loading") {
<div id="devel"></div> <div id="devel"></div>
<nav> <nav>
<ul> <ul>
<li><a href="puzzle-list.html">Puzzles</a></li> <li><a href="index.html">Puzzles</a></li>
<li><a href="scoreboard.html">Scoreboard</a></li> <li><a href="scoreboard.html">Scoreboard</a></li>
</ul> </ul>
</nav> </nav>

View File

@ -128,7 +128,7 @@ if (document.readyState === "loading") {
</section> </section>
<nav> <nav>
<ul> <ul>
<li><a href="puzzle-list.html">Puzzles</a></li> <li><a href="index.html">Puzzles</a></li>
<li><a href="scoreboard.html">Scoreboard</a></li> <li><a href="scoreboard.html">Scoreboard</a></li>
</ul> </ul>
</nav> </nav>