Golang port is now MOTHv3

This commit is contained in:
Neale Pickett 2018-09-20 16:17:42 +00:00
parent f75e3882d9
commit 5abdfa09e0
2 changed files with 0 additions and 350 deletions

View File

@ -1,215 +0,0 @@
MOTHv3 API
==========
MOTH, by design, uses a small number of API endpoints.
Whenever possible,
we decided to push complexity into the client,
keeping the server as simple as we could make it.
After all,
this is a hacking contest.
If a participant finds a vulnerability in code running on their own machine,
the people running the server don't care.
Specification
=============
You make requests as HTTP GET query arguments:
https://server/path/elements/api/v3/endpoint?var1=val1&var2=val2
The server returns a
[JSend](https://labs.omniti.com/labs/jsend) response:
{
"status": "success",
"data": "Any JS data type here"
}
Client State
============
The client (or user interacting with the client) needs to remember only one thing:
* teamId: the team ID used to register
A naive client,
like the one we used from 2009-2018,
can ask the user to type in the team ID for every submission.
This is fine.
Endpoints
=========
RegisterTeam(teamId, teamName)
-------------------------------
Register a team name with a team hash.
### Parameters
* teamId: Team's unique identifier (usually a hex value)
* teamName: Team's human-readable name
On success, no data is returned.
On failure, message contains an English explanation of why.
### Example
https://server/api/v3/RegisterTeam?teamId=8b1292ca&teamName=Lexical+Pedants
{
"status": "success",
"data": null
}
GetState()
----------
Return all current state of the puzzle server.
### Parameters
None
### Return data
* puzzles: dictionary mapping from category to one of the following:
* list of point values currently open
* URL to puzzle root (intended for token-based puzzles)
* teams: mapping from anonymized team ID to team name
* log: list of (timestamp, team number, category, points)
* notices: list of HTML broadcast notices to display to the user
* now: current server time (unix epoch)
### Example
https://server/api/v3/GetState
{
"status": "success",
"data": {
"puzzles": {
"sequence": [1, 2],
"codebreaking": [10],
"wopr": "https://appspot.com/dooted-bagel-8372/entry"
},
"teams": {
"0": "Zelda",
"1": "Defender"
},
"log": [
[1526478368, "0", "sequence", 1],
[1526478524, "1", "sequence", 1],
[1526478536, "0", "nocode", 1]
],
"notices": [
"<a href=\"https://appspot.com/dooted-bagel-8372/entry\">WOPR category</a> is now open",
"Event closes at 18:00 today, and will resume tomorrow at 08:00"
],
"now": 1527170088
}
}
GetPuzzle(category, points)
--------------------
Return a puzzle.
### Parameters
* category: name of category to fetch from
* points: point value of the puzzle to fetch
### Return data
* authors: List of puzzle authors
* hashes: list of djbhash values of acceptable answers
* files: dictionary of puzzle-associated filenames and their URLs
* body: HTML body of the puzzle
### Example
https://server/api/v3/GetPuzzle?category=sequence&points=1
{
"status": "success",
"data": {
"authors": ["neale"],
"hashes": [177627],
"files": {
"happy.png": "https://cdn/assets/0904cf3a437a348bea2c49d56a3087c26a01a63c.png"
},
"body": "<pre><code>1 2 3 4 5 _\n</code></pre>\n"
}
SubmitAnswer(teamId, category, points, answer)
----------------------
Submit an answer to a puzzle.
### Parameters
* teamId: Team ID (optional: if ommitted, answer is verified but no points are awarded)
* category: category name of puzzle
* points: point value of puzzle
* answer: attempted answer
### Return Data
* epilog: HTML to display upon successfully answering the puzzle
### Example
https://server/api/v3/SubmitAnswer?teamId=8b1292ca&category=sequence&points=1&answer=6
{
"status": "success",
"data": {
"epilog": "That's right: in base 10, 5 + 1 = 6."
}
}
SubmitToken(teamId, token)
---------------------
Submit a token for points
### Parameters
* teamId: Team ID
* token: Token being submitted
### Return data
* category: category for which this token awarded points
* points: number of points awarded
* epilog: HTML to display upon successfully answering the puzzle
### Example
https://server/api/v3/SubmitToken?teamId=8b1292ca&token=wat:30:xylep-radar-nanox
{
"status": "success",
"data": {
"category": "wat",
"points": 30,
"epilog": ""
}
}

View File

@ -1,135 +0,0 @@
#! /usr/bin/python3
from aiohttp import web
import time
async def fake_register(request):
teamId = request.query.get("teamId")
teamName = request.query.get("teamName")
if teamId == "ffff" and teamName == "dirtbags":
resp = {
"status": "success",
"data": None,
}
elif teamId and teamName:
resp = {
"status": "error",
"message": "Query was correctly formed but I'm feeling cranky"
}
else:
resp = {
"status": "fail",
"data": "You must send teamId and teamName",
}
return web.json_response(resp)
async def fake_state(request):
resp = {
"status": "success",
"data": {
"puzzles": {
"sequence": [1, 2],
"codebreaking": [10],
"wopr": "https://appspot.com/dooted-bagel-8372/entry"
},
"teams": {
"0": "Zelda",
"1": "Defender"
},
"log": [
[1526478368, "0", "sequence", 1],
[1526478524, "1", "sequence", 1],
[1526478536, "0", "nocode", 1]
],
"notices": [
"<a href=\"https://appspot.com/dooted-bagel-8372/entry\">WOPR category</a> is now open",
"Event closes at 18:00 today, and will resume tomorrow at 08:00"
],
"now": int(time.time()),
}
}
return web.json_response(resp)
async def fake_getpuzzle(request):
category = request.query.get("category")
points = request.query.get("points")
if category == "sequence" and points == "1":
resp = {
"status": "success",
"data": {
"authors": ["neale"],
"hashes": [177627],
"files": {
"happy.png": "https://cdn/assets/0904cf3a437a348bea2c49d56a3087c26a01a63c.png"
},
"body": "<pre><code>1 2 3 4 5 _\n</code></pre>\n",
}
}
elif category and points:
resp = {
"status": "error",
"message": "Query was correctly formed but I'm feeling cranky"
}
else:
resp = {
"status": "fail",
"data": "You must send category and points"
}
return web.json_response(resp)
async def fake_submitanswer(request):
teamId = request.query.get("teamId")
category = request.query.get("category")
points = request.query.get("points")
answer = request.query.get("answer")
if category == "sequence" and points == "1" and answer == "6":
resp = {
"status": "success",
"data": {
"epilog": "Now you know the answer, and knowing is half the battle. Go Joe!"
}
}
elif category and points and answer:
resp = {
"status": "error",
"message": "Query was correctly formed but I'm feeling cranky"
}
else:
resp = {
"status": "fail",
"data": "You must send category and points"
}
return web.json_response(resp)
async def fake_submittoken(request):
teamId = request.query.get("teamId")
token = request.query.get("token")
if token == "wat:30:xylep-radar-nanox":
resp = {
"status": "success",
"data": {
"category": "wat",
"points": 30,
"epilog": ""
}
}
elif category and points and answer:
resp = {
"status": "error",
"message": "Query was correctly formed but I'm feeling cranky"
}
else:
resp = {
"status": "fail",
"data": "You must send category and points"
}
return web.json_response(resp)
if __name__ == "__main__":
app = web.Application()
app.router.add_route("GET", "/api/v3/RegisterTeam", fake_register)
app.router.add_route("GET", "/api/v3/GetState", fake_state)
app.router.add_route("GET", "/api/v3/GetPuzzle", fake_getpuzzle)
app.router.add_route("GET", "/api/v3/SubmitAnswer", fake_submitanswer)
app.router.add_route("GET", "/api/v3/SubmitToken", fake_submittoken)
web.run_app(app)