2021-02-23 13:14:29 -08:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
from flask import Flask, render_template, redirect, request
|
|
|
|
import requests
|
|
|
|
from typing import List
|
|
|
|
from datetime import datetime
|
|
|
|
import os
|
|
|
|
import json
|
|
|
|
|
|
|
|
END_URL = os.getenv('END_URL')
|
|
|
|
COEFFICIENTS_AND_MOD = json.loads(os.getenv('COEFFICIENTS_AND_MOD')) # For example, [3, 5, 23] represents the function f(x)=3x+5 mod 23
|
|
|
|
DOMAIN = os.getenv('DOMAIN')
|
|
|
|
DAYS_TO_ALLOW = os.getenv('DAYS_TO_ALLOW') # How many days to GET endpoint until death code functions
|
|
|
|
if DAYS_TO_ALLOW:
|
|
|
|
DAYS_TO_ALLOW = int(DAYS_TO_ALLOW)
|
|
|
|
# DAYS_TO_ALLOW = 7
|
|
|
|
ALIVE_PATH = os.getenv('ALIVE_PATH') # Path to deny allowing attempts immediately and start the countdown (optional)
|
|
|
|
# ALIVE_PATH = "i-am-alive"
|
|
|
|
DEAD_PATH = os.getenv('DEAD_PATH') # Path to start allowing attemps immediately (optional)
|
|
|
|
# DEAD_PATH = "i-am-dead"
|
|
|
|
|
|
|
|
LAST_ENDPOINT_GET: datetime = None
|
|
|
|
|
|
|
|
app = Flask(__name__)
|
|
|
|
|
|
|
|
@app.route("/", methods=["GET", "POST"])
|
|
|
|
def index():
|
|
|
|
global POLYNOMIAL, LAST_ENDPOINT_GET, DAYS_TO_ALLOW
|
2021-02-23 17:00:47 -08:00
|
|
|
try:
|
|
|
|
if request.method == "POST":
|
|
|
|
if ALIVE_PATH and DAYS_TO_ALLOW and LAST_ENDPOINT_GET and (datetime.today() - LAST_ENDPOINT_GET).days < DAYS_TO_ALLOW:
|
|
|
|
return render_template("message.html", message="Andrew is still alive. Try again another time")
|
|
|
|
captcha_id = request.form.get('captcha-id')
|
|
|
|
captcha_solution = request.form.get('captcha-solution')
|
|
|
|
v = captcha_validate(captcha_id, captcha_solution)
|
|
|
|
if not v[0]:
|
|
|
|
return render_template('message.html', message = "Failed captcha", attempts_left = v[1])
|
|
|
|
try:
|
|
|
|
coords = []
|
|
|
|
for key in request.form:
|
|
|
|
if 'x' == key[0]:
|
|
|
|
coords.append(Coordinate(int(request.form[key]), int(request.form['y' + key[1]])))
|
|
|
|
if POLYNOMIAL.valid_combination(coords):
|
|
|
|
return render_template("congrats.html", polynomial=POLYNOMIAL, domain=DOMAIN)
|
|
|
|
return render_template("message.html", message="Those points weren't valid", attempts_left = v[1])
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
return render_template("message.html", message="Invalid data")
|
|
|
|
captcha = captcha_get(ttl = 300)
|
|
|
|
return render_template("index.html", polynomial = POLYNOMIAL, domain=DOMAIN, captcha_id = captcha[0], captcha_png = captcha[1])
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
return render_template('message.html', message="Error ocurred")
|
2021-02-23 13:14:29 -08:00
|
|
|
|
|
|
|
@app.route("/<attempt_num>", methods=["GET", "POST"])
|
|
|
|
def attempt(attempt_num):
|
|
|
|
global POLYNOMIAL, LAST_ENDPOINT_GET, DAYS_TO_ALLOW
|
|
|
|
try:
|
|
|
|
attempt_num = int(attempt_num)
|
|
|
|
if request.method == "POST":
|
|
|
|
captcha_id = request.form.get('captcha-id')
|
|
|
|
captcha_solution = request.form.get('captcha-solution')
|
|
|
|
v = captcha_validate(captcha_id, captcha_solution)
|
|
|
|
if v[0]:
|
|
|
|
if ALIVE_PATH and LAST_ENDPOINT_GET and (datetime.today() - LAST_ENDPOINT_GET).days < DAYS_TO_ALLOW:
|
|
|
|
return render_template("message.html", message="Andrew is still alive. Try again another time")
|
|
|
|
num = int(attempt_num)
|
|
|
|
if num == POLYNOMIAL.x_zero_point:
|
|
|
|
return redirect(END_URL, code=302)
|
|
|
|
return render_template('message.html', message="Incorrect guess for f(0)", attempts_left = v[1])
|
|
|
|
return render_template('message.html', message="Failed captcha", attempts_left = v[1])
|
|
|
|
captcha = captcha_get(ttl = 30, difficulty="hard")
|
|
|
|
return render_template('attempt.html', captcha_id = captcha[0], captcha_png = captcha[1])
|
|
|
|
except ValueError as e:
|
|
|
|
print(e)
|
|
|
|
return render_template('message.html', message="URL path must be an integer")
|
|
|
|
except Exception as e:
|
|
|
|
print(e)
|
|
|
|
return render_template('message.html', message="Error ocurred")
|
|
|
|
|
|
|
|
if ALIVE_PATH and DAYS_TO_ALLOW:
|
|
|
|
@app.route("/" + ALIVE_PATH, methods=["GET"])
|
|
|
|
def am_alive():
|
|
|
|
global LAST_ENDPOINT_GET
|
|
|
|
LAST_ENDPOINT_GET = datetime.today()
|
|
|
|
return "OK"
|
|
|
|
|
|
|
|
if DEAD_PATH:
|
|
|
|
@app.route("/" + DEAD_PATH, methods=["GET"])
|
|
|
|
def am_dead():
|
|
|
|
global LAST_ENDPOINT_GET
|
|
|
|
LAST_ENDPOINT_GET = None
|
|
|
|
return "OK"
|
|
|
|
|
|
|
|
def captcha_validate(captcha_id: str, captcha_solution: str) -> List:
|
|
|
|
""" Validates a captcha and returns [success, trials_left] """
|
|
|
|
response = requests.post(f"http://rust-captcha:8000/solution/{captcha_id}/{captcha_solution}", headers={'X-Client-ID': 'Death Code'}).json()
|
|
|
|
if response["error_code"] != 0:
|
|
|
|
print(f"http://rust-captcha:8000/solution/{captcha_id}/{captcha_solution}")
|
|
|
|
raise Exception(response)
|
|
|
|
if response["result"]["solution"] == "accepted":
|
|
|
|
return [True, 0]
|
|
|
|
return [False, response["result"]["trials_left"]]
|
|
|
|
|
|
|
|
def captcha_get(max_tries: int = 3, ttl: int = 120, difficulty: str = "medium") -> List[str]:
|
|
|
|
""" Creates a captcha and returns [id, base64 encoded png] """
|
|
|
|
response = requests.post(f"http://rust-captcha:8000/new/{difficulty}/{max_tries}/{ttl}", headers={'X-Client-ID': 'Death Code'}).json()
|
|
|
|
if response["error_code"] != 0:
|
|
|
|
raise Exception(response)
|
|
|
|
return [response["result"]["id"], response["result"]["png"]]
|
|
|
|
|
|
|
|
class Coordinate:
|
|
|
|
def __init__(self, x: int, y: int):
|
|
|
|
self.x = x
|
|
|
|
self.y = y
|
|
|
|
|
|
|
|
def __repr__(self) -> str:
|
|
|
|
return "({}, {})".format(self.x, self.y)
|
|
|
|
|
|
|
|
def equals(self, coord):
|
|
|
|
""" Returns whether this coordinate is equal to the other """
|
|
|
|
return self.x == coord.x and self.y == coord.y
|
|
|
|
|
|
|
|
class Polynomial:
|
|
|
|
def __init__(self, coefficients: List, modulo: int):
|
|
|
|
while (coefficients[0] == 0):
|
|
|
|
coefficients = coefficients[1:]
|
|
|
|
|
|
|
|
self.degree = len(coefficients) - 1
|
|
|
|
self.coefficients = coefficients
|
|
|
|
self.modulo = modulo
|
|
|
|
self.x_zero_point = coefficients[-1] % modulo
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
i = 0
|
|
|
|
s = "y="
|
|
|
|
exponent = self.degree
|
|
|
|
while exponent > 1:
|
|
|
|
if s != "y=":
|
|
|
|
s += "+"
|
|
|
|
s += str(self.coefficients[i]) + "x^" + str(exponent)
|
|
|
|
exponent -= 1
|
|
|
|
i += 1
|
|
|
|
if i != len(self.coefficients):
|
|
|
|
if s != "y=":
|
|
|
|
s += "+"
|
|
|
|
s += str(self.coefficients[i]) + "x"
|
|
|
|
i += 1
|
|
|
|
if i != len(self.coefficients):
|
|
|
|
if s != "y=":
|
|
|
|
s += "+"
|
|
|
|
s += str(self.coefficients[-1])
|
|
|
|
return s + " mod " + str(self.modulo)
|
|
|
|
|
|
|
|
def valid_combination(self, coordinates: List[Coordinate]) -> bool:
|
|
|
|
""" Returns whether there are enough valid coordinates in `coordinates` to extract this polyomial """
|
|
|
|
count = 0
|
|
|
|
seen_coords = []
|
|
|
|
for coord in coordinates:
|
|
|
|
if self.valid_coord(coord) and all([not coord.equals(coordinate) for coordinate in seen_coords]):
|
|
|
|
count += 1
|
|
|
|
seen_coords.append(coord)
|
|
|
|
return count >= self.degree + 1
|
|
|
|
|
|
|
|
def valid_coord(self, coord: Coordinate) -> bool:
|
|
|
|
""" Returns whether `coord` is a valid coordinate for this polynomial """
|
|
|
|
exponent = self.degree
|
|
|
|
coefficients = self.coefficients
|
|
|
|
value = 0
|
|
|
|
i = 0
|
|
|
|
while exponent >= 0:
|
|
|
|
value += coefficients[i] * pow(coord.x, exponent)
|
|
|
|
exponent -= 1
|
|
|
|
i += 1
|
|
|
|
return value % self.modulo == coord.y
|
|
|
|
|
|
|
|
POLYNOMIAL = Polynomial(COEFFICIENTS_AND_MOD[:-1], COEFFICIENTS_AND_MOD[-1])
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
app.run(host="0.0.0.0", debug=True, port=8888)
|