git.haldean.org apex / 160942d
things are happening, for sure Haldean Brown 3 years ago
12 changed file(s) with 548 addition(s) and 0 deletion(s). Raw diff Collapse all Expand all
0 session-key.dat
1 env
2 __pycache__
3 *.pyc
0 import flask
1 import redis
2
3 import globals
4
5 app = flask.Flask(__name__)
6 globals.app = app
7 r = redis.Redis(host="localhost", port=6379, db=0)
8 globals.r = r
9
10 import data
11 import users
12
13 with open("session-key.dat", "rb") as session_file:
14 app.secret_key = session_file.read()
15
16
17 @app.route("/static/<path:path>")
18 def static_file(path):
19 return flask.send_from_directory("static")
20
21 @app.route("/")
22 def index():
23 if users.session_user() is None:
24 return flask.render_template("login.html")
25 return flask.redirect(flask.url_for("log"))
26
27 @app.route("/login", methods=["POST"])
28 def login():
29 username = flask.request.form["username"]
30 password = flask.request.form["password"]
31
32 if not users.check_password(username, password):
33 flask.abort(401)
34 return
35 users.set_session_user(username)
36 return flask.redirect(flask.url_for("index"))
37
38 @app.route("/log", methods=["GET", "POST"])
39 def log():
40 if users.session_user() is None:
41 return flask.redirect(flask.url_for("index"))
42 if flask.request.method == "POST":
43 data.store(users.session_user(), flask.request.get_json())
44 return "ok"
45 return flask.render_template("log.html", username=users.session_user())
46
47 @app.route("/adduser", methods=["GET", "POST"])
48 def adduser():
49 if users.session_user() is None:
50 return flask.redirect(flask.url_for("index"))
51 if flask.request.method == "GET":
52 return flask.render_template("adduser.html", username=users.session_user())
53 username = flask.request.form["username"]
54 password = flask.request.form["password"]
55 try:
56 users.add_user(username, password)
57 except BadRequest:
58 flask.abort(400)
59 return
60 flask.flash("User \"{}\" added".format(username))
61 return flask.redirect(flask.url_for("adduser"))
62
63 @app.route("/logout")
64 def logout():
65 users.set_session_user(None)
66 return flask.redirect(flask.url_for("index"))
0 from datetime import datetime, timezone
1 import json
2
3 from globals import *
4
5 valid_fields = set([
6 "hero",
7 "droploc",
8 "endloc",
9 "teamsleft",
10 "minutes",
11 "selfdc",
12 "teamdc",
13 "gun1",
14 "gun2",
15 "attachments",
16 "hacker",
17 "teamquality",
18 ])
19
20 def store(username, gamedata):
21 req_fields = set(gamedata.keys())
22 missing = valid_fields - req_fields
23 extra = req_fields - valid_fields
24 if missing or extra:
25 raise ValueError(
26 "bad game data: missing {}, extra {}".format(missing, extra))
27 now = datetime.now(timezone.utc)
28 now_ts = now.timestamp()
29 gamekey = "apex:games:{}:{}".format(username, now_ts)
30 if r.get(gamekey) is not None:
31 raise ValueError(
32 "game key {} already exists".format(gamekey))
33 gamedata["key"] = gamekey
34 gamedata["date"] = now.isoformat()
35 gamedata["user"] = username
36 r.set(gamekey, json.dumps(gamedata))
0 app = None
1 r = None
2
0 argon2
1 flask
2 redis
0 "use strict";
1
2 $(document).ready(function() {
3 var matchdata = {};
4 var sections = $("section");
5 var currentSection = 0;
6 $("section").hide();
7
8 function gotoNext() {
9 $(sections[currentSection]).hide();
10 currentSection++;
11 $(sections[currentSection]).show();
12 }
13
14 $("div.choice-numbers").each(function (_, d) {
15 var low = d.dataset.low;
16 var high = d.dataset.high;
17 for (var i = low; i <= high; i++) {
18 var a = document.createElement("a");
19 a.href="#";
20 a.dataset.field = d.dataset.field;
21 a.dataset.value = i;
22 a.dataset.isnumeric = true;
23 $(a).addClass("choice").addClass("choicenum").text(i);
24 d.appendChild(a);
25 }
26 });
27
28 $("a.choice").click(function (e) {
29 var data = e.target.dataset;
30 var val = data.value;
31 if (data.isnumeric) {
32 val = +val;
33 }
34 matchdata[data.field] = val;
35 gotoNext();
36 });
37
38 $("a.submit").click(function (e) {
39 $.ajax("log", {
40 contentType: "application/json",
41 data: JSON.stringify(matchdata),
42 method: "POST",
43 success: function() {
44 gotoNext();
45 },
46 error: function(i1, errMsg, errMoreMsg) {
47 console.log(i1, errMsg, errMoreMsg);
48 alert("couldn't add log: " + errMsg + ": " + errMoreMsg);
49 },
50 });
51 });
52
53 $("a.reset").click(function (e) {
54 matchdata = {};
55 $(sections[currentSection]).hide();
56 currentSection = 0;
57 $(sections[currentSection]).show();
58 });
59
60 $(sections[currentSection]).show();
61 });
0 @import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,900,900i');
1
2 body {
3 font-family: Roboto, sans-serif;
4 width: 100%;
5 max-width: 33em;
6 margin: 4em auto;
7 }
8
9 section h2 {
10 width: 100%;
11 text-align: center;
12 }
13
14 a.choice-style {
15 background: #000;
16 box-sizing: border-box;
17 color: #FFF;
18 display: block;
19 font-size: 20pt;
20 font-weight: bold;
21 margin: 1em 0;
22 padding: 1em;
23 text-align: center;
24 text-decoration: none;
25 text-transform: uppercase;
26 width: 100%;
27 }
28
29 a.choice {
30 background: #000;
31 box-sizing: border-box;
32 color: #FFF;
33 display: block;
34 font-size: 20pt;
35 font-weight: bold;
36 margin: 1em 0;
37 padding: 1em;
38 text-align: center;
39 text-decoration: none;
40 text-transform: uppercase;
41 width: 100%;
42 }
43
44 a.choice.choicenum {
45 width: 17%;
46 margin-right: 3%;
47 float: left;
48 }
49
50 a.choice.level4 { background-color: #ffc857; }
51 a.choice.level3 { background-color: #a746c4; }
52 a.choice.level2 { background-color: #297dd1; }
53 a.choice.level1 { background-color: #d6d6d6; }
54 a.choice.level0 { background-color: #5b5b5b; }
55
56 header {
57 margin-bottom: 3em;
58 }
59
60 h1 {
61 margin: 0;
62 }
63
64 form div {
65 margin-bottom: 1em;
66 }
67
68 label {
69 display: block;
70 float: left;
71 min-width: 7em;
72 }
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>apex tracker</title>
4 <link href="static/style.css" rel="stylesheet">
5 </head>
6 <body>
7 <header>
8 <h1>Add user</h1>
9 <cite>Logged in as {{ username }}</cite>
10 </header>
11 <section class="flash">
12 {% with msgs = get_flashed_messages() %}
13 {% for msg in msgs %}
14 <p>{{ msg }}</p>
15 {% endfor %}
16 {% endwith %}
17 </section>
18 <form action="adduser" method="POST">
19 <div>
20 <label for=username>Username:</label>
21 <input type=text name=username>
22 </div>
23 <div>
24 <label for=password>Password:</label>
25 <input type=password name=password>
26 </div>
27 <input type=submit value=Add name=submit>
28 </form>
29 </body>
30 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>apex tracker</title>
4 <link href="static/style.css" rel="stylesheet">
5 </head>
6 <body>
7 <h1>Apex Tracker</h1>
8 <form action="login" method="POST">
9 <label for=username>Username:</label>
10 <input type=text name=username>
11 <label for=password>Password:</label>
12 <input type=password name=password>
13 <input type=submit value=Submit name=submit>
14 </form>
15 </body>
16 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>apex tracker</title>
4 <script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
5 <script src="static/apex.js"></script>
6 <link rel="stylesheet" href="static/style.css">
7 <meta charset="utf-8">
8 </head>
9 <body>
10 <header>
11 <h1>Log a match</h1>
12 <cite>Logged in as {{ username }}</cite>
13 </header>
14 <section>
15 <h2>Which hero were you?</h2>
16 <a class="choice" href="#" data-field="hero" data-value="bangalore">Bangalore</a>
17 <a class="choice" href="#" data-field="hero" data-value="bloodhound">Bloodhound</a>
18 <a class="choice" href="#" data-field="hero" data-value="caustic">Caustic</a>
19 <a class="choice" href="#" data-field="hero" data-value="gibraltar">Gibraltar</a>
20 <a class="choice" href="#" data-field="hero" data-value="lifeline">Lifeline</a>
21 <a class="choice" href="#" data-field="hero" data-value="mirage">Mirage</a>
22 <a class="choice" href="#" data-field="hero" data-value="octane">Octane</a>
23 <a class="choice" href="#" data-field="hero" data-value="pathfinder">Pathfinder</a>
24 <a class="choice" href="#" data-field="hero" data-value="wraith">Wraith</a>
25 </section>
26 <section>
27 <h2>Where did you drop?</h2>
28 <a class="choice" href="#" data-field="droploc" data-value="airbase">Airbase</a>
29 <a class="choice" href="#" data-field="droploc" data-value="artillery">Artillery</a>
30 <a class="choice" href="#" data-field="droploc" data-value="bridges">Bridges</a>
31 <a class="choice" href="#" data-field="droploc" data-value="bunker">Bunker</a>
32 <a class="choice" href="#" data-field="droploc" data-value="cascades">Cascades</a>
33 <a class="choice" href="#" data-field="droploc" data-value="hydro">Hydro Dam</a>
34 <a class="choice" href="#" data-field="droploc" data-value="market">Market</a>
35 <a class="choice" href="#" data-field="droploc" data-value="pit">Pit</a>
36 <a class="choice" href="#" data-field="droploc" data-value="relay">Relay</a>
37 <a class="choice" href="#" data-field="droploc" data-value="repulsor">Repulsor</a>
38 <a class="choice" href="#" data-field="droploc" data-value="runoff">Runoff</a>
39 <a class="choice" href="#" data-field="droploc" data-value="skull">Skull Town</a>
40 <a class="choice" href="#" data-field="droploc" data-value="slum">Slum Lakes</a>
41 <a class="choice" href="#" data-field="droploc" data-value="swamp">Swamps</a>
42 <a class="choice" href="#" data-field="droploc" data-value="thunderdome">Thunderdome</a>
43 <a class="choice" href="#" data-field="droploc" data-value="water">Water Treatment</a>
44 <a class="choice" href="#" data-field="droploc" data-value="wetlands">Wetlands</a>
45 <a class="choice" href="#" data-field="droploc" data-value="nowhere">Middle of Nowhere</a>
46 </section>
47 <section>
48 <h2>Where did you end up?</h2>
49 <a class="choice" href="#" data-field="endloc" data-value="airbase">Airbase</a>
50 <a class="choice" href="#" data-field="endloc" data-value="artillery">Artillery</a>
51 <a class="choice" href="#" data-field="endloc" data-value="bridges">Bridges</a>
52 <a class="choice" href="#" data-field="endloc" data-value="bunker">Bunker</a>
53 <a class="choice" href="#" data-field="endloc" data-value="cascades">Cascades</a>
54 <a class="choice" href="#" data-field="endloc" data-value="hydro">Hydro Dam</a>
55 <a class="choice" href="#" data-field="endloc" data-value="market">Market</a>
56 <a class="choice" href="#" data-field="endloc" data-value="pit">Pit</a>
57 <a class="choice" href="#" data-field="endloc" data-value="relay">Relay</a>
58 <a class="choice" href="#" data-field="endloc" data-value="repulsor">Repulsor</a>
59 <a class="choice" href="#" data-field="endloc" data-value="runoff">Runoff</a>
60 <a class="choice" href="#" data-field="endloc" data-value="skull">Skull Town</a>
61 <a class="choice" href="#" data-field="endloc" data-value="slum">Slum Lakes</a>
62 <a class="choice" href="#" data-field="endloc" data-value="swamp">Swamps</a>
63 <a class="choice" href="#" data-field="endloc" data-value="thunderdome">Thunderdome</a>
64 <a class="choice" href="#" data-field="endloc" data-value="water">Water Treatment</a>
65 <a class="choice" href="#" data-field="endloc" data-value="wetlands">Wetlands</a>
66 <a class="choice" href="#" data-field="endloc" data-value="nowhere">Middle of Nowhere</a>
67 </section>
68 <section>
69 <h2>How many teams left?</h2>
70 <div class="choice-numbers" data-low="0" data-high="19" data-field="teamsleft"></div>
71 </section>
72 <section>
73 <h2>How long were you alive, in minutes?</h2>
74 <div class="choice-numbers" data-low="0" data-high="29" data-field="minutes"></div>
75 </section>
76 <section>
77 <h2>Did you DC?</h2>
78 <a class="choice" href="#" data-field="selfdc" data-value="1" data-isboolean="true">Yes</a>
79 <a class="choice" href="#" data-field="selfdc" data-value="0" data-isboolean="true">No</a>
80 </section>
81 <section>
82 <h2>How many of your buds DCed?</h2>
83 <div class="choice-numbers" data-low="0" data-high="2" data-field="teamdc"></div>
84 </section>
85 <section>
86 <h2>How many friends were you playing with?</h2>
87 <div class="choice-numbers" data-low="0" data-high="2" data-field="teamdc"></div>
88 </section>
89 <section>
90 <h2>What was your first gun at the end?</h2>
91 <a class="choice" href="#" data-field="gun1" data-value="alternator">Alternator</a>
92 <a class="choice" href="#" data-field="gun1" data-value="devotion">Devotion</a>
93 <a class="choice" href="#" data-field="gun1" data-value="eva8">EVA-8</a>
94 <a class="choice" href="#" data-field="gun1" data-value="flatline">Flatline</a>
95 <a class="choice" href="#" data-field="gun1" data-value="havoc">Havoc</a>
96 <a class="choice" href="#" data-field="gun1" data-value="hemlok">Hemlok</a>
97 <a class="choice" href="#" data-field="gun1" data-value="kraber">Kraber</a>
98 <a class="choice" href="#" data-field="gun1" data-value="longbow">Longbow</a>
99 <a class="choice" href="#" data-field="gun1" data-value="mastiff">Mastiff</a>
100 <a class="choice" href="#" data-field="gun1" data-value="mozambique">Mozambique</a>
101 <a class="choice" href="#" data-field="gun1" data-value="p2020">P2020</a>
102 <a class="choice" href="#" data-field="gun1" data-value="peacekeeper">Peacekeeper</a>
103 <a class="choice" href="#" data-field="gun1" data-value="prowler">Prowler</a>
104 <a class="choice" href="#" data-field="gun1" data-value="r301">R-301</a>
105 <a class="choice" href="#" data-field="gun1" data-value="r99">R-99</a>
106 <a class="choice" href="#" data-field="gun1" data-value="re45">RE-45</a>
107 <a class="choice" href="#" data-field="gun1" data-value="scout">Scout</a>
108 <a class="choice" href="#" data-field="gun1" data-value="spitfire">Spitfire</a>
109 <a class="choice" href="#" data-field="gun1" data-value="tripletake">Triple Take</a>
110 <a class="choice" href="#" data-field="gun1" data-value="wingman">Wingman</a>
111 <a class="choice" href="#" data-field="gun1" data-value="none">None</a>
112 </section>
113 <section>
114 <h2>What was your second gun at the end?</h2>
115 <a class="choice" href="#" data-field="gun2" data-value="alternator">Alternator</a>
116 <a class="choice" href="#" data-field="gun2" data-value="devotion">Devotion</a>
117 <a class="choice" href="#" data-field="gun2" data-value="eva8">EVA-8</a>
118 <a class="choice" href="#" data-field="gun2" data-value="flatline">Flatline</a>
119 <a class="choice" href="#" data-field="gun2" data-value="havoc">Havoc</a>
120 <a class="choice" href="#" data-field="gun2" data-value="hemlok">Hemlok</a>
121 <a class="choice" href="#" data-field="gun2" data-value="kraber">Kraber</a>
122 <a class="choice" href="#" data-field="gun2" data-value="longbow">Longbow</a>
123 <a class="choice" href="#" data-field="gun2" data-value="mastiff">Mastiff</a>
124 <a class="choice" href="#" data-field="gun2" data-value="mozambique">Mozambique</a>
125 <a class="choice" href="#" data-field="gun2" data-value="p2020">P2020</a>
126 <a class="choice" href="#" data-field="gun2" data-value="peacekeeper">Peacekeeper</a>
127 <a class="choice" href="#" data-field="gun2" data-value="prowler">Prowler</a>
128 <a class="choice" href="#" data-field="gun2" data-value="r301">R-301</a>
129 <a class="choice" href="#" data-field="gun2" data-value="r99">R-99</a>
130 <a class="choice" href="#" data-field="gun2" data-value="re45">RE-45</a>
131 <a class="choice" href="#" data-field="gun2" data-value="scout">Scout</a>
132 <a class="choice" href="#" data-field="gun2" data-value="spitfire">Spitfire</a>
133 <a class="choice" href="#" data-field="gun2" data-value="tripletake">Triple Take</a>
134 <a class="choice" href="#" data-field="gun2" data-value="wingman">Wingman</a>
135 <a class="choice" href="#" data-field="gun2" data-value="none">None</a>
136 </section>
137 <section>
138 <h2>How good were your attachments?</h2>
139 <a class="choice level4" href="#" data-field="attachments" data-value=4>&nbsp;</a>
140 <a class="choice level3" href="#" data-field="attachments" data-value=3>&nbsp;</a>
141 <a class="choice level2" href="#" data-field="attachments" data-value=2>&nbsp;</a>
142 <a class="choice level1" href="#" data-field="attachments" data-value=1>&nbsp;</a>
143 <a class="choice level0" href="#" data-field="attachments" data-value=0>&nbsp;</a>
144 </section>
145 <section>
146 <h2>Did you see a hacker or cheater?</h2>
147 <a class="choice" href="#" data-field="hacker" data-value="yes">Definitely</a>
148 <a class="choice" href="#" data-field="hacker" data-value="maybe">I think so</a>
149 <a class="choice" href="#" data-field="hacker" data-value="no">No</a>
150 </section>
151 <section>
152 <h2>What were your teammates like?</h2>
153 <a class="choice" href="#" data-field="teamquality" data-value="theycarry">They Carried</a>
154 <a class="choice" href="#" data-field="teamquality" data-value="selfcarry">I Carried</a>
155 <a class="choice" href="#" data-field="teamquality" data-value="good">We were all good</a>
156 <a class="choice" href="#" data-field="teamquality" data-value="annoying">Annoying</a>
157 <a class="choice" href="#" data-field="teamquality" data-value="bad">Bad at game</a>
158 <a class="choice" href="#" data-field="teamquality" data-value="cheater">Cheater</a>
159 <a class="choice" href="#" data-field="teamquality" data-value="spam">Spammy</a>
160 <a class="choice" href="#" data-field="teamquality" data-value="uncommunicative">Uncommunicative</a>
161 </section>
162 <section>
163 <h2>Ready to submit?</h2>
164 <a class="choice-style submit" href="#">Yes</a>
165 <a class="choice-style reset" href="#">Start over</a>
166 </section>
167 <section>
168 <h2>Submitted!</h2>
169 <a class="choice-style reset">Add another</a>
170 </section>
171 </body>
172 </html>
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <title>apex tracker</title>
4 <link href="static/style.css" rel="stylesheet">
5 </head>
6 <body>
7 <header>
8 <h1>Log in</h1>
9 </header>
10 <form action="login" method="POST">
11 <div>
12 <label for=username>Username:</label>
13 <input type=text name=username>
14 </div>
15 <div>
16 <label for=password>Password:</label>
17 <input type=password name=password>
18 </div>
19 <input type=submit value=Submit name=submit>
20 </form>
21 </body>
22 </html>
0 import argon2
1 import base64
2 import flask
3 import hmac
4 import re
5
6 from globals import *
7
8 class AuthenticationFailed(Exception):
9 pass
10
11 def valid_username(username):
12 return re.match("^[a-zA-Z0-9_]{1,32}$", username)
13
14 def hash_password(username, password):
15 salt = username[:32].ljust(32, "$") + "-salt:"
16 return salt.encode("utf-8") + base64.b64encode(argon2.argon2_hash(password, salt))
17
18 def check_password(username, password):
19 if not valid_username(username):
20 raise AuthenticationFailed()
21 phash_stored = r.get("apex:users:{}:password".format(username))
22 if phash_stored is None:
23 # the comparison to this will always fail, but we do the comparison
24 # anyway to maintain constant-time and avoid user enumeration
25 phash_compare = hash_password("", "")
26 else:
27 phash_compare = phash_stored
28 phash_request = hash_password(username, password)
29 return hmac.compare_digest(phash_compare, phash_request)
30
31 def add_user(username, password):
32 if not valid_username(username):
33 flask.abort(400)
34 return
35 hash = hash_password(username, password)
36 r.set("apex:users:{}:password".format(username), hash)
37
38 def session_user():
39 if "username" in flask.session:
40 return flask.session["username"]
41 return None
42
43 def set_session_user(username):
44 flask.session["username"] = username
45
46 def user_is_admin(username=None):
47 if username is None:
48 username = session_user()
49 if username is None:
50 return False
51 return bool(r.get("apex:users:{}:admin".format(username)))
52
53 def user_set_admin(username):
54 r.set("apex:users:{}:admin".format(username), True)