commit
177d4bf4ca
26 changed files with 889 additions and 609 deletions
|
@ -13,7 +13,11 @@ server {
|
|||
# }
|
||||
|
||||
# Put all the pages here so Angular doesn't fail.
|
||||
location ~^/(about|login|register|scoreboard|chat|updates|problems|programming|shell|rules|admin/problems)$ {
|
||||
location ~^/(about|chat|help|learn|login|profile|register|scoreboard|settings|team)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
location ~^/admin/(problems)$ {
|
||||
default_type text/html;
|
||||
try_files /index.html /index.html;
|
||||
}
|
||||
|
|
4
deploy
4
deploy
|
@ -5,7 +5,9 @@ pkill gunicorn
|
|||
sudo service nginx stop
|
||||
tmux kill-session -t ctf 2> /dev/null
|
||||
|
||||
sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
|
||||
|
||||
echo "Starting the server..."
|
||||
cd /home/vagrant/server
|
||||
sudo service nginx start
|
||||
tmux new-session -s ctf -d 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'
|
||||
tmux new-session -s ctf -d 'gunicorn "app:app" -c /home/vagrant/scripts/gunicorn.py.ini'
|
|
@ -4,3 +4,4 @@ Flask-SQLAlchemy
|
|||
SQLAlchemy
|
||||
gunicorn
|
||||
requests
|
||||
voluptuous
|
31
server/api.html
Normal file
31
server/api.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||||
<html><head><title>Python: package api</title>
|
||||
<meta charset="utf-8">
|
||||
</head><body bgcolor="#f0f0f8">
|
||||
|
||||
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="heading">
|
||||
<tr bgcolor="#7799ee">
|
||||
<td valign=bottom> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"> <br><big><big><strong>api</strong></big></big></font></td
|
||||
><td align=right valign=bottom
|
||||
><font color="#ffffff" face="helvetica, arial"><a href=".">index</a><br><a href="file:/home/vagrant/server/api/__init__.py">/home/vagrant/server/api/__init__.py</a></font></td></tr></table>
|
||||
<p></p>
|
||||
<p>
|
||||
<table width="100%" cellspacing=0 cellpadding=2 border=0 summary="section">
|
||||
<tr bgcolor="#aa55cc">
|
||||
<td colspan=3 valign=bottom> <br>
|
||||
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
|
||||
|
||||
<tr><td bgcolor="#aa55cc"><tt> </tt></td><td> </td>
|
||||
<td width="100%"><table width="100%" summary="list"><tr><td width="25%" valign=top><a href="api.admin.html">admin</a><br>
|
||||
<a href="api.api.html">api</a><br>
|
||||
<a href="api.decorators.html">decorators</a><br>
|
||||
</td><td width="25%" valign=top><a href="api.logger.html">logger</a><br>
|
||||
<a href="api.models.html">models</a><br>
|
||||
<a href="api.problem.html">problem</a><br>
|
||||
</td><td width="25%" valign=top><a href="api.schemas.html">schemas</a><br>
|
||||
<a href="api.user.html">user</a><br>
|
||||
<a href="api.utils.html">utils</a><br>
|
||||
</td><td width="25%" valign=top></td></tr></table></td></tr></table>
|
||||
</body></html>
|
|
@ -1,19 +1,26 @@
|
|||
from flask import Blueprint, jsonify
|
||||
from decorators import admins_only, api_wrapper, login_required
|
||||
from decorators import admins_only, api_wrapper
|
||||
from models import db, Problems, Files
|
||||
|
||||
import json
|
||||
|
||||
blueprint = Blueprint("admin", __name__)
|
||||
|
||||
@blueprint.route("/problem/data", methods=["POST"])
|
||||
#@api_wrapper # Disable atm due to json serialization issues: will fix
|
||||
@blueprint.route("/problems/list", methods=["POST"])
|
||||
@api_wrapper
|
||||
@admins_only
|
||||
@login_required
|
||||
def problem_data():
|
||||
problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves", "disabled", "flag").order_by(Problems.value).all()
|
||||
jason = []
|
||||
|
||||
for problem in problems:
|
||||
problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ]
|
||||
jason.append({"pid": problem[1], "name": problem[2] ,"category": problem[3], "description": problem[4], "hint": problem[5], "value": problem[6], "solves": problem[7], "disabled": problem[8], "flag": problem[9], "files": problem_files})
|
||||
|
||||
return jsonify(data=jason)
|
||||
problems = Problems.query.order_by(Problems.value).all()
|
||||
problems_return = [ ]
|
||||
for problem in problems:
|
||||
problems_return.append({
|
||||
"pid": problem.pid,
|
||||
"name": problem.name,
|
||||
"category": problem.category,
|
||||
"description": problem.description,
|
||||
"hint": problem.hint,
|
||||
"value": problem.value,
|
||||
"threshold": problem.threshold,
|
||||
"weightmap": json.loads(problem.weightmap)
|
||||
})
|
||||
return { "success": 1, "problems": problems_return }
|
|
@ -19,7 +19,7 @@ def api_wrapper(f):
|
|||
except Exception as error:
|
||||
response = 200
|
||||
traceback.print_exc()
|
||||
web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately. %s: %s" % (error, traceback.format_exc()) }
|
||||
web_result = { "success": 0, "message": "Something went wrong! Please notify us about this immediately.", "error": [ str(error), traceback.format_exc() ] }
|
||||
return json.dumps(web_result), response, { "Content-Type": "application/json; charset=utf-8" }
|
||||
return wrapper
|
||||
|
||||
|
|
|
@ -1,73 +1,104 @@
|
|||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
import time
|
||||
import utils
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
class Users(db.Model):
|
||||
uid = db.Column(db.Integer, primary_key=True)
|
||||
tid = db.Column(db.Integer)
|
||||
name = db.Column(db.String(64))
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
username_lower = db.Column(db.String(64), unique=True)
|
||||
email = db.Column(db.String(64), unique=True)
|
||||
password = db.Column(db.String(128))
|
||||
admin = db.Column(db.Boolean, default=False)
|
||||
uid = db.Column(db.Integer, unique=True, primary_key=True)
|
||||
tid = db.Column(db.Integer)
|
||||
name = db.Column(db.String(64))
|
||||
username = db.Column(db.String(64), unique=True)
|
||||
username_lower = db.Column(db.String(64), unique=True)
|
||||
email = db.Column(db.String(64), unique=True)
|
||||
password = db.Column(db.String(128))
|
||||
admin = db.Column(db.Boolean)
|
||||
utype = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer)
|
||||
registertime = db.Column(db.Integer)
|
||||
|
||||
def __init__(self, name, username, email, password):
|
||||
self.name = name
|
||||
self.username = username
|
||||
self.username_lower = username.lower()
|
||||
self.email = email.lower()
|
||||
self.password = utils.hash_password(password)
|
||||
def __init__(self, name, username, email, password, utype=1):
|
||||
self.name = name
|
||||
self.username = username
|
||||
self.username_lower = username.lower()
|
||||
self.email = email.lower()
|
||||
self.password = utils.hash_password(password)
|
||||
self.utype = utype
|
||||
self.admin = False
|
||||
self.registertime = int(time.time())
|
||||
|
||||
class Teams(db.Model):
|
||||
tid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(64), unique=True)
|
||||
join_code = db.Column(db.String(128), unique=True)
|
||||
school = db.Column(db.Text)
|
||||
size = db.Column(db.Integer)
|
||||
score = db.Column(db.Integer)
|
||||
observer = db.Column(db.Boolean)
|
||||
owner = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(64), unique=True)
|
||||
join_code = db.Column(db.String(128), unique=True)
|
||||
school = db.Column(db.Text)
|
||||
size = db.Column(db.Integer)
|
||||
score = db.Column(db.Integer)
|
||||
observer = db.Column(db.Boolean)
|
||||
owner = db.Column(db.Integer)
|
||||
|
||||
def __init__(self, name, school):
|
||||
self.name = name
|
||||
self.school = school
|
||||
def __init__(self, name, school):
|
||||
self.name = name
|
||||
self.school = school
|
||||
|
||||
class Problems(db.Model):
|
||||
pid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128))
|
||||
category = db.Column(db.String(128))
|
||||
description = db.Column(db.Text)
|
||||
hint = db.Column(db.Text)
|
||||
flag = db.Column(db.Text)
|
||||
disabled = db.Column(db.Boolean, default=False)
|
||||
value = db.Column(db.Integer)
|
||||
solves = db.Column(db.Integer, default=0)
|
||||
pid = db.Column(db.Integer, primary_key=True)
|
||||
name = db.Column(db.String(128))
|
||||
category = db.Column(db.String(128))
|
||||
description = db.Column(db.Text)
|
||||
hint = db.Column(db.Text)
|
||||
flag = db.Column(db.Text)
|
||||
disabled = db.Column(db.Boolean, default=False)
|
||||
value = db.Column(db.Integer)
|
||||
solves = db.Column(db.Integer, default=0)
|
||||
|
||||
def __init__(self, name, category, description, hint, flag, value):
|
||||
self.name = name
|
||||
self.category = category
|
||||
self.description = description
|
||||
self.hint = hint
|
||||
self.flag = flag
|
||||
self.value = value
|
||||
def __init__(self, name, category, description, hint, flag, value):
|
||||
self.name = name
|
||||
self.category = category
|
||||
self.description = description
|
||||
self.hint = hint
|
||||
self.flag = flag
|
||||
self.value = value
|
||||
|
||||
class Files(db.Model):
|
||||
fid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
location = db.Column(db.Text)
|
||||
fid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
location = db.Column(db.Text)
|
||||
|
||||
def __init__(self, pid, location):
|
||||
self.pid = pid
|
||||
self.location = location
|
||||
def __init__(self, pid, location):
|
||||
self.pid = pid
|
||||
self.location = location
|
||||
|
||||
class Solves(db.Model):
|
||||
sid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer)
|
||||
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
||||
sid = db.Column(db.Integer, primary_key=True)
|
||||
pid = db.Column(db.Integer)
|
||||
tid = db.Column(db.Integer)
|
||||
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
||||
|
||||
def __init__(self, pid, tid):
|
||||
self.pid = pid
|
||||
self.tid = tid
|
||||
def __init__(self, pid, tid):
|
||||
self.pid = pid
|
||||
self.tid = tid
|
||||
|
||||
##########
|
||||
# TOKENS #
|
||||
##########
|
||||
|
||||
class LoginTokens(db.Model):
|
||||
sid = db.Column(db.String(64), unique=True, primary_key=True)
|
||||
uid = db.Column(db.Integer)
|
||||
username = db.Column(db.String(32))
|
||||
active = db.Column(db.Boolean)
|
||||
issued = db.Column(db.Integer)
|
||||
expiry = db.Column(db.Integer)
|
||||
ua = db.Column(db.String(128))
|
||||
ip = db.Column(db.String(16))
|
||||
|
||||
def __init__(self, uid, username, expiry=int(time.time()), active=True, ua=None, ip=None):
|
||||
self.sid = utils.generate_string()
|
||||
self.uid = uid
|
||||
self.username = username
|
||||
self.issued = int(time.time())
|
||||
self.expiry = expiry
|
||||
self.active = active
|
||||
self.ua = ua
|
||||
self.ip = ip
|
24
server/api/schemas.py
Normal file
24
server/api/schemas.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import api
|
||||
import re
|
||||
|
||||
from voluptuous import Required, Length, Schema, Invalid, MultipleInvalid
|
||||
from decorators import WebException
|
||||
|
||||
def check(*callback_tuples):
|
||||
def v(value):
|
||||
for callbacks, msg in callback_tuples:
|
||||
for callback in callbacks:
|
||||
try:
|
||||
result = callback(value)
|
||||
if not result and type(result) == bool:
|
||||
raise Invalid(msg)
|
||||
except Exception:
|
||||
raise WebException(msg)
|
||||
return value
|
||||
return v
|
||||
|
||||
def verify_to_schema(schema, data):
|
||||
try:
|
||||
schema(data)
|
||||
except MultipleInvalid as error:
|
||||
raise WebException(str(error))
|
|
@ -1,96 +1,208 @@
|
|||
from flask import Blueprint, session, request, redirect, url_for
|
||||
from flask import current_app as app
|
||||
from voluptuous import Schema, Length, Required
|
||||
|
||||
from models import db, Users
|
||||
from decorators import api_wrapper
|
||||
from models import db, LoginTokens, Users
|
||||
from decorators import api_wrapper, WebException
|
||||
from schemas import verify_to_schema, check
|
||||
|
||||
import datetime
|
||||
import logger
|
||||
import re
|
||||
import requests
|
||||
import utils
|
||||
|
||||
###############
|
||||
# USER ROUTES #
|
||||
###############
|
||||
|
||||
blueprint = Blueprint("user", __name__)
|
||||
|
||||
@blueprint.route("/register", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_register():
|
||||
# if not validate_captcha(request.form):
|
||||
# return { "success": 0, "message": "Please do the captcha." }
|
||||
params = utils.flat_multi(request.form)
|
||||
|
||||
name = request.form["name"]
|
||||
username = request.form["username"]
|
||||
password = request.form["password"]
|
||||
password_confirm = request.form["password_confirm"]
|
||||
email = request.form["email"]
|
||||
name = params.get("name")
|
||||
email = params.get("email")
|
||||
username = params.get("username")
|
||||
password = params.get("password")
|
||||
password_confirm = params.get("password_confirm")
|
||||
utype = int(params.get("type"))
|
||||
|
||||
username_exists = Users.query.add_columns("name", "uid").filter_by(username_lower=username.lower()).first()
|
||||
email_exists = Users.query.add_columns("name", "uid").filter_by(email=email.lower()).first()
|
||||
if password != password_confirm:
|
||||
raise WebException("Passwords do not match.")
|
||||
verify_to_schema(UserSchema, params)
|
||||
|
||||
if password != password_confirm:
|
||||
return { "success": 0, "message": "Passwords do not match." }
|
||||
if len(password) > 128:
|
||||
return { "success": 0, "message": "Password is too long." }
|
||||
if len(password) == 0:
|
||||
return { "success": 0, "message": "Password is too short." }
|
||||
if len(username) > 64:
|
||||
return { "success": 0, "message": "Username is too long." }
|
||||
if username_exists:
|
||||
return { "success": 0, "message": "Username is already taken." }
|
||||
if email_exists:
|
||||
return { "success": 0, "message": "Email has already been used." }
|
||||
user = Users(name, username, email, password, utype)
|
||||
with app.app_context():
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
add_user(name, username, email, password)
|
||||
logger.log("registrations", logger.INFO, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
|
||||
logger.log("registrations", logger.INFO, "%s registered with %s" % (name.encode("utf-8"), email.encode("utf-8")))
|
||||
login_user(username, password)
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/logout", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_logout():
|
||||
session.clear()
|
||||
sid = session["sid"]
|
||||
username = session["username"]
|
||||
with app.app_context():
|
||||
expired = LoginTokens.query.filter_by(username=username).all()
|
||||
for expired_token in expired: db.session.delete(expired_token)
|
||||
db.session.commit()
|
||||
session.clear()
|
||||
|
||||
@blueprint.route("/login", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_login():
|
||||
email = request.form["email"]
|
||||
password = request.form["password"]
|
||||
user = Users.query.filter_by(email=email).first()
|
||||
if user is None:
|
||||
return { "success": 0, "message": "Invalid credentials." }
|
||||
params = utils.flat_multi(request.form)
|
||||
|
||||
if utils.check_password(user.password, password):
|
||||
session["username"] = user.username
|
||||
if user.admin:
|
||||
session["admin"] = True
|
||||
session["logged_in"] = True
|
||||
return { "success": 1, "message": "Success!" }
|
||||
else:
|
||||
return { "success": 0, "message": "Invalid credentials." }
|
||||
username = params.get("username")
|
||||
password = params.get("password")
|
||||
|
||||
result = login_user(username, password)
|
||||
if result != True:
|
||||
raise WebException("Please check if your username/password are correct.")
|
||||
|
||||
return { "success": 1, "message": "Success!" }
|
||||
|
||||
@blueprint.route("/status", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_status():
|
||||
status = {
|
||||
"logged_in": is_logged_in(),
|
||||
"admin": is_admin(),
|
||||
"username": session["username"] if is_logged_in() else "",
|
||||
}
|
||||
return status
|
||||
logged_in = is_logged_in()
|
||||
result = {
|
||||
"success": 1,
|
||||
"logged_in": logged_in,
|
||||
"admin": is_admin(),
|
||||
"username": session["username"] if logged_in else "",
|
||||
}
|
||||
return result
|
||||
|
||||
@blueprint.route("/info", methods=["POST"])
|
||||
@api_wrapper
|
||||
def user_info():
|
||||
logged_in = is_logged_in()
|
||||
username = utils.flat_multi(request.form).get("username")
|
||||
if username is None:
|
||||
if logged_in:
|
||||
username = session["username"]
|
||||
if username is None:
|
||||
raise WebException("No user specified.")
|
||||
me = username.lower() == session["username"].lower()
|
||||
user = get_user(username_lower=username.lower()).first()
|
||||
if user is None:
|
||||
raise WebException("User not found.")
|
||||
|
||||
show_email = me if logged_in else False
|
||||
userdata = {
|
||||
"user_found": True,
|
||||
"name": user.name,
|
||||
"username": user.username,
|
||||
"type": ["Student", "Instructor", "Observer"][user.utype - 1],
|
||||
"admin": user.admin,
|
||||
"registertime": datetime.datetime.fromtimestamp(user.registertime).isoformat() + "Z",
|
||||
"me": me,
|
||||
"show_email": show_email
|
||||
}
|
||||
if show_email:
|
||||
userdata["email"] = user.email
|
||||
return { "success": 1, "user": userdata }
|
||||
|
||||
##################
|
||||
# USER FUNCTIONS #
|
||||
##################
|
||||
|
||||
__check_email_format = lambda email: re.match(".+@.+\..{2,}", email) is not None
|
||||
__check_ascii = lambda s: all(ord(c) < 128 for c in s)
|
||||
__check_username = lambda username: get_user(username_lower=username.lower()).first() is None
|
||||
__check_email = lambda email: get_user(email=email.lower()).first() is None
|
||||
|
||||
UserSchema = Schema({
|
||||
Required("email"): check(
|
||||
([str, Length(min=4, max=128)], "Your email should be between 4 and 128 characters long."),
|
||||
([__check_email], "Someone already registered this email."),
|
||||
([__check_email_format], "Please enter a legit email.")
|
||||
),
|
||||
Required("name"): check(
|
||||
([str, Length(min=4, max=128)], "Your name should be between 4 and 128 characters long.")
|
||||
),
|
||||
Required("username"): check(
|
||||
([str, Length(min=4, max=32)], "Your username should be between 4 and 32 characters long."),
|
||||
([__check_ascii], "Please only use ASCII characters in your username."),
|
||||
([__check_username], "This username is taken, did you forget your password?")
|
||||
),
|
||||
Required("password"): check(
|
||||
([str, Length(min=4, max=64)], "Your password should be between 4 and 64 characters long."),
|
||||
([__check_ascii], "Please only use ASCII characters in your password."),
|
||||
),
|
||||
Required("type"): check(
|
||||
([str, lambda x: x.isdigit()], "Please use the online form.")
|
||||
),
|
||||
"notify": str
|
||||
}, extra=True)
|
||||
|
||||
def get_user(username=None, username_lower=None, email=None, uid=None):
|
||||
match = {}
|
||||
if username != None:
|
||||
match.update({ "username": username })
|
||||
elif username_lower != None:
|
||||
match.update({ "username_lower": username_lower })
|
||||
elif uid != None:
|
||||
match.update({ "uid": uid })
|
||||
elif email != None:
|
||||
match.update({ "email": email })
|
||||
# elif api.auth.is_logged_in():
|
||||
# match.update({ "uid": api.auth.get_uid() })
|
||||
with app.app_context():
|
||||
result = Users.query.filter_by(**match)
|
||||
return result
|
||||
|
||||
def login_user(username, password):
|
||||
user = get_user(username_lower=username.lower()).first()
|
||||
if user is None: return False
|
||||
correct = utils.check_password(user.password, password)
|
||||
if not correct: return False
|
||||
|
||||
useragent = request.headers.get("User-Agent")
|
||||
ip = request.remote_addr
|
||||
|
||||
with app.app_context():
|
||||
expired = LoginTokens.query.filter_by(username=username).all()
|
||||
for expired_token in expired: db.session.delete(expired_token)
|
||||
|
||||
token = LoginTokens(user.uid, user.username, ua=useragent, ip=ip)
|
||||
db.session.add(token)
|
||||
db.session.commit()
|
||||
|
||||
session["sid"] = token.sid
|
||||
session["username"] = token.username
|
||||
session["admin"] = user.admin == True
|
||||
|
||||
return True
|
||||
|
||||
def is_logged_in():
|
||||
return "logged_in" in session and session["logged_in"]
|
||||
sid = session["sid"]
|
||||
username = session["username"]
|
||||
token = LoginTokens.query.filter_by(sid=sid).first()
|
||||
if token is None: return False
|
||||
|
||||
useragent = request.headers.get("User-Agent")
|
||||
ip = request.remote_addr
|
||||
|
||||
if token.username != username: return False
|
||||
if token.ua != useragent: return False
|
||||
return True
|
||||
|
||||
def is_admin():
|
||||
return "admin" in session and session["admin"]
|
||||
|
||||
def add_user(name, username, email, password):
|
||||
user = Users(name, username, email, password)
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
return is_logged_in() and "admin" in session and session["admin"]
|
||||
|
||||
def validate_captcha(form):
|
||||
if "captcha_response" not in form:
|
||||
return False
|
||||
captcha_response = form["captcha_response"]
|
||||
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
|
||||
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
|
||||
return response.json()["success"]
|
||||
if "captcha_response" not in form:
|
||||
return False
|
||||
captcha_response = form["captcha_response"]
|
||||
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
|
||||
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
|
||||
return response.json()["success"]
|
|
@ -3,6 +3,7 @@ import json
|
|||
import random
|
||||
import string
|
||||
import traceback
|
||||
import unicodedata
|
||||
|
||||
from functools import wraps
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
@ -13,8 +14,8 @@ def hash_password(s):
|
|||
def check_password(hashed_password, try_password):
|
||||
return check_password_hash(hashed_password, try_password)
|
||||
|
||||
def generate_string(length):
|
||||
return "".join([random.choice(string.letters + string.digits) for x in range(length)])
|
||||
def generate_string(length=32, alpha=string.hexdigits):
|
||||
return "".join([random.choice(alpha) for x in range(length)])
|
||||
|
||||
def unix_time_millis(dt):
|
||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||
|
@ -22,3 +23,10 @@ def unix_time_millis(dt):
|
|||
|
||||
def get_time_since_epoch():
|
||||
return unix_time_millis(datetime.datetime.now())
|
||||
|
||||
def flat_multi(multidict):
|
||||
flat = {}
|
||||
for key, values in multidict.items():
|
||||
value = values[0] if type(values) == list and len(values) == 1 else values
|
||||
flat[key] = unicodedata.normalize("NFKD", value).encode("ascii", "ignore")
|
||||
return flat
|
|
@ -6,6 +6,8 @@ import config
|
|||
import json
|
||||
import os
|
||||
|
||||
from api.decorators import api_wrapper
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
app.config["SQLALCHEMY_DATABASE_URI"] = config.SQLALCHEMY_DATABASE_URI
|
||||
|
@ -28,8 +30,9 @@ app.register_blueprint(api.problem.blueprint, url_prefix="/api/problem")
|
|||
api.logger.initialize_logs()
|
||||
|
||||
@app.route("/api")
|
||||
@api_wrapper
|
||||
def api_main():
|
||||
return json.dumps({ "success": 1, "message": "The API is online." })
|
||||
return { "success": 1, "message": "The API is online." }
|
||||
|
||||
if __name__ == "__main__":
|
||||
with app.app_context():
|
||||
|
|
23
web/css/easyctf.css
Normal file
23
web/css/easyctf.css
Normal file
|
@ -0,0 +1,23 @@
|
|||
@font-face {
|
||||
font-family: "Proxima Nova";
|
||||
src: url("/fonts/ProximaNova.woff2");
|
||||
}
|
||||
@font-face {
|
||||
font-family: "Proxima Nova";
|
||||
src: url("/fonts/ProximaNovaBold.woff2");
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
* {
|
||||
font-family: "Proxima Nova";
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
padding: 10px;
|
||||
> .tab-pane {
|
||||
display: none;
|
||||
}
|
||||
> .active {
|
||||
display: block;
|
||||
}
|
||||
}
|
BIN
web/fonts/ProximaNova.woff2
Normal file
BIN
web/fonts/ProximaNova.woff2
Normal file
Binary file not shown.
BIN
web/fonts/ProximaNovaBold.woff2
Normal file
BIN
web/fonts/ProximaNovaBold.woff2
Normal file
Binary file not shown.
127
web/index.html
127
web/index.html
|
@ -8,15 +8,24 @@
|
|||
<script src="js/d3.v3.min.js" charset="utf-8"></script>
|
||||
<script src="js/c3.min.js"></script>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">
|
||||
<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
|
||||
<link type="text/css" rel="stylesheet" href="css/style.css">
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" integrity="sha384-yNuQMX46Gcak2eQsUzmBYgJ3eBeWYNKhnjyiBqLd1vvtE9kuMtgw6bjwN8J0JauQ" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/css/bootstrap-select.css" integrity="sha384-X6BCz/5YN8CudtAN21w+htVJP5lJNqXP0VBfbGzrX7A/FhjpPIoMtiRRHxRYiKU6" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="/css/easyctf.css" />
|
||||
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" integrity="sha384-r1y8TJcloKTvouxnYsi4PJAx+nHNr90ibsEn3zznzDzWBN9X3o3kbHLSgcIPtzAp" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular-route.min.js" integrity="sha384-9MZDoFf10trgrfsQOs9GJhf/mP/sh5weVp3FDSi8h/4TEaV6dloEDkpxGTaOmAs6" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/js/bootstrap-select.min.js" integrity="sha384-1qZEXZBmj54fSiiWT8bZQGEpCumJWDrAoEqMdg6N5bTTLCkU5RXoNeUsKWekRYob" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/1.4.3/jquery.timeago.min.js" integrity="sha384-Bap3DetwPgo4GEFvaIDVSIrz5G0mUAUsfCUcEsi+JrrNu7dyj3gBmuAG4hDIGg/4" crossorigin="anonymous"></script>
|
||||
<script src="/js/easyctf.js"></script>
|
||||
</head>
|
||||
|
||||
<body ng-controller="mainController" class="mainbody">
|
||||
<nav class="navbar navbar-default navbar-fixed-top">
|
||||
<div id="style1" class="container-fluid">
|
||||
<body ng-controller="mainController">
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container">
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="main-navbar" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
|
@ -24,91 +33,49 @@
|
|||
</button>
|
||||
<a class="navbar-brand" href="/">EasyCTF</a>
|
||||
</div>
|
||||
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav navbar-fixed">
|
||||
<li>
|
||||
<a href="/about">
|
||||
<span class="fa fa-info-circle"></span> About</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/rules">
|
||||
<span class="fa fa-book"></span> Rules</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/updates">
|
||||
<span class="fa fa-bullhorn"></span> Updates</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/chat">
|
||||
<span class="fa fa-comments"></span> Chat</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/scoreboard">
|
||||
<span class="fa fa-trophy"></span> Scoreboard</a>
|
||||
</li>
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/about">About</a></li>
|
||||
<li><a href="/scoreboard">Scoreboard</a></li>
|
||||
<li><a href="/learn">Learn</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
<li>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-pencil"></span> Problems <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" id="menubox">
|
||||
<li>
|
||||
<a href="/problems">
|
||||
<span class="fa fa-pencil"></span> Problems</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/programming">
|
||||
<span class="fa fa-code"></span> Programming</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/shell">
|
||||
<span class="fa fa-terminal"></span> Shell</a>
|
||||
</li>
|
||||
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==false">
|
||||
<li><a href="/register">Register</a></li>
|
||||
<li><a href="/login">Login</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==true">
|
||||
<div ng-show="config.navbar['competition_started']==true">
|
||||
</div>
|
||||
<li><a href="/chat">Chat</a></li>
|
||||
<li class="dropdown" ng-show="config.navbar['admin']==true">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Admin <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/admin/stats">Statistics</a></li>
|
||||
<li><a href="/admin/problems">Problem Editor</a></li>
|
||||
<li><a href="/admin/teams">Team Management</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="/admin/settings">CTF Settings</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li id="login_link" style="display:none">
|
||||
<a href="/login">
|
||||
<span class="fa fa-sign-in"></span> Login</a>
|
||||
</li>
|
||||
<li id="register_link" style="display:none">
|
||||
<a href="/register">
|
||||
<span class="fa fa-pencil"></span> Register</a>
|
||||
</li>
|
||||
<li id="account_link" style="display:none">
|
||||
<a href="/account">
|
||||
<span class="fa fa-pencil"></span> Account</a>
|
||||
</li>
|
||||
<li id="logout" style="display:none">
|
||||
<a href="/api/user/logout">
|
||||
<span class="fa fa-pencil"></span> Logout</a>
|
||||
</li>
|
||||
<li id="admin_dropdown" style="display:none">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-pencil"></span> Admin<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" id="menubox">
|
||||
<li>
|
||||
<a href="/admin/problems">
|
||||
<span class="fa fa-pencil"></span> Problems</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="/admin/teams">
|
||||
<span class="fa fa-code"></span> Teams</a>
|
||||
</li>
|
||||
<li class="dropdown">
|
||||
<a href="javascript:void(0);" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{{ config.navbar["username"] }} <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="/profile">Profile</a></li>
|
||||
<li><a href="/team">Team</a></li>
|
||||
<li><a href="/help">Help</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="/settings">Settings</a></li>
|
||||
<li><a href="/logout">Logout</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<br>
|
||||
<br>
|
||||
<div id="mainContent" class="ui container">
|
||||
|
||||
<div id="mainContent" class="container">
|
||||
<div ng-view></div>
|
||||
</div>
|
||||
<script src="https://code.jquery.com/jquery-2.1.4.min.js" integrity="sha384-R4/ztc4ZlRqWjqIuvf6RX5yb/v90qNGx6fS48N0tRxiGkqveZETq72KgDVJCp2TC" crossorigin="anonymous"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js" integrity="sha384-r1y8TJcloKTvouxnYsi4PJAx+nHNr90ibsEn3zznzDzWBN9X3o3kbHLSgcIPtzAp" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0-rc.0/angular-route.min.js" integrity="sha384-9MZDoFf10trgrfsQOs9GJhf/mP/sh5weVp3FDSi8h/4TEaV6dloEDkpxGTaOmAs6" crossorigin="anonymous"></script>
|
||||
<script src="js/easyctf.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -1,121 +0,0 @@
|
|||
function render_problems() {
|
||||
$.post("/api/admin/problem/data", {
|
||||
}, function(data) {
|
||||
data = data["data"];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
files = data[i]["files"];
|
||||
var checked = "";
|
||||
if (data[i]["disabled"]) {
|
||||
checked = "checked";
|
||||
}
|
||||
problem =
|
||||
`<div class="panel panel-info">
|
||||
<form method="POST" onsubmit="return false;">
|
||||
<input type="hidden" name="pid" value="` + data[i]["pid"] + `">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="name" placeholder="Name" autocomplete="on" class="form-control" value="` + data[i]["name"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="category" placeholder="Category" autocomplete="on" class="form-control" value="` + data[i]["category"] + `">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<textarea type="text" name="description" placeholder="Description" autocomplete="on" class="form-control">` + data[i]["description"] + `</textarea>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="flag" placeholder="Flag" autocomplete="off" class="form-control" value="` + data[i]["flag"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="hint" placeholder="Hint" autocomplete="off" class="form-control" value="` + data[i]["hint"] + `">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="number" name="value" placeholder="Value" autocomplete="off" class="form-control-number" value="` + data[i]["value"] + `">
|
||||
<label><input type="checkbox" name="disabled" value="1"` + checked + `>Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">`
|
||||
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
file_name = files[j].split("/").pop();
|
||||
problem +=
|
||||
`<a href="` + files[j] + `" class="filelink" target="_blank">
|
||||
<h4 class="probfile">` + file_name + `</h4>
|
||||
</a>`
|
||||
}
|
||||
|
||||
problem += `<br>
|
||||
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
|
||||
<div class="row" id="status_` + data[i]["pid"] + `"></div><br>
|
||||
<input class="btn btn-success" type="submit" name="update" value="Update">
|
||||
<input class="btn btn-danger" name="delete-modal" type="button" data-toggle="modal" data-target="#delete-modal" value="Delete">
|
||||
</div></form></div>`
|
||||
$("#problems").append(problem);
|
||||
}
|
||||
$("[name=update]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var name = $("input[name=name]", problem).val();
|
||||
var description = $("textarea[name=description]", problem).val();
|
||||
var hint = $("input[name=hint]", problem).val();
|
||||
var category = $("input[name=category]", problem).val();
|
||||
var value = $("input[name=value]", problem).val();
|
||||
var flag = $("input[name=flag]", problem).val();
|
||||
var disabled = $("input[name=disabled]", problem).prop("checked") ? 1 : 0;
|
||||
update_problem(pid, name, category, description, hint, flag, disabled, value);
|
||||
});
|
||||
$("[name=delete-modal]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var div = $(this).closest("div.panel");
|
||||
$("#delete").off().click(function(e) {
|
||||
delete_problem(pid, div);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function update_problem(pid, name, category, description, hint, flag, disabled, value) {
|
||||
$.post("/api/problem/update", {
|
||||
pid: pid,
|
||||
name: name,
|
||||
category: category,
|
||||
description: description,
|
||||
hint: hint,
|
||||
flag: flag,
|
||||
disabled: disabled,
|
||||
value: value
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("status_" + pid, "success", data.message, function() {});
|
||||
} else {
|
||||
display_message("status_" + pid, "danger", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delete_problem(pid, div) {
|
||||
$.post("/api/problem/delete", {
|
||||
pid: pid
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("delete_status", "success", data.message, function() {
|
||||
div.slideUp("normal", function() {
|
||||
$(this).remove();
|
||||
$("#delete-modal").modal("hide");
|
||||
} );
|
||||
});
|
||||
} else {
|
||||
display_message("delete_status", "warning", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
render_problems();
|
||||
});
|
121
web/js/admin/problems.old.js
Normal file
121
web/js/admin/problems.old.js
Normal file
|
@ -0,0 +1,121 @@
|
|||
function render_problems() {
|
||||
$.post("/api/admin/problem/data", {
|
||||
}, function(data) {
|
||||
data = data["data"];
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
files = data[i]["files"];
|
||||
var checked = "";
|
||||
if (data[i]["disabled"]) {
|
||||
checked = "checked";
|
||||
}
|
||||
problem =
|
||||
`<div class="panel panel-info">
|
||||
<form method="POST" onsubmit="return false;">
|
||||
<input type="hidden" name="pid" value="` + data[i]["pid"] + `">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="name" placeholder="Name" autocomplete="on" class="form-control" value="` + data[i]["name"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="category" placeholder="Category" autocomplete="on" class="form-control" value="` + data[i]["category"] + `">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<textarea type="text" name="description" placeholder="Description" autocomplete="on" class="form-control">` + data[i]["description"] + `</textarea>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="flag" placeholder="Flag" autocomplete="off" class="form-control" value="` + data[i]["flag"] + `">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="hint" placeholder="Hint" autocomplete="off" class="form-control" value="` + data[i]["hint"] + `">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="number" name="value" placeholder="Value" autocomplete="off" class="form-control-number" value="` + data[i]["value"] + `">
|
||||
<label><input type="checkbox" name="disabled" value="1"` + checked + `>Disabled</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">`
|
||||
|
||||
for (var j = 0; j < files.length; j++) {
|
||||
file_name = files[j].split("/").pop();
|
||||
problem +=
|
||||
`<a href="` + files[j] + `" class="filelink" target="_blank">
|
||||
<h4 class="probfile">` + file_name + `</h4>
|
||||
</a>`
|
||||
}
|
||||
|
||||
problem += `<br>
|
||||
<div id="hint_` + data[i]["pid"] + `" style="display:none">` + data[i]["hint"] + `</div>
|
||||
<div class="row" id="status_` + data[i]["pid"] + `"></div><br>
|
||||
<input class="btn btn-success" type="submit" name="update" value="Update">
|
||||
<input class="btn btn-danger" name="delete-modal" type="button" data-toggle="modal" data-target="#delete-modal" value="Delete">
|
||||
</div></form></div>`
|
||||
$("#problems").append(problem);
|
||||
}
|
||||
$("[name=update]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var name = $("input[name=name]", problem).val();
|
||||
var description = $("textarea[name=description]", problem).val();
|
||||
var hint = $("input[name=hint]", problem).val();
|
||||
var category = $("input[name=category]", problem).val();
|
||||
var value = $("input[name=value]", problem).val();
|
||||
var flag = $("input[name=flag]", problem).val();
|
||||
var disabled = $("input[name=disabled]", problem).prop("checked") ? 1 : 0;
|
||||
update_problem(pid, name, category, description, hint, flag, disabled, value);
|
||||
});
|
||||
$("[name=delete-modal]").click(function(e) {
|
||||
var problem = $(this).parents("form:first");
|
||||
var pid = $("input[name=pid]", problem).val();
|
||||
var div = $(this).closest("div.panel");
|
||||
$("#delete").off().click(function(e) {
|
||||
delete_problem(pid, div);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function update_problem(pid, name, category, description, hint, flag, disabled, value) {
|
||||
$.post("/api/problem/update", {
|
||||
pid: pid,
|
||||
name: name,
|
||||
category: category,
|
||||
description: description,
|
||||
hint: hint,
|
||||
flag: flag,
|
||||
disabled: disabled,
|
||||
value: value
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("status_" + pid, "success", data.message, function() {});
|
||||
} else {
|
||||
display_message("status_" + pid, "danger", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function delete_problem(pid, div) {
|
||||
$.post("/api/problem/delete", {
|
||||
pid: pid
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("delete_status", "success", data.message, function() {
|
||||
div.slideUp("normal", function() {
|
||||
$(this).remove();
|
||||
$("#delete-modal").modal("hide");
|
||||
} );
|
||||
});
|
||||
} else {
|
||||
display_message("delete_status", "warning", data.message, function() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
render_problems();
|
||||
});
|
|
@ -1,91 +1,140 @@
|
|||
var app = angular.module("easyctf", [ "ngRoute" ]);
|
||||
app.config(function($routeProvider, $locationProvider) {
|
||||
$routeProvider.when("/", {
|
||||
templateUrl: "pages/home.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/about", {
|
||||
templateUrl: "pages/about.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/register", {
|
||||
templateUrl: "pages/register.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/login", {
|
||||
templateUrl: "pages/login.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/chat", {
|
||||
templateUrl: "pages/chat.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/updates", {
|
||||
templateUrl: "pages/updates.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/problems", {
|
||||
templateUrl: "pages/problems.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/programming", {
|
||||
templateUrl: "pages/programming.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/shell", {
|
||||
templateUrl: "pages/shell.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/rules", {
|
||||
templateUrl: "pages/rules.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/scoreboard", {
|
||||
templateUrl: "pages/scoreboard.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/admin/problems", {
|
||||
templateUrl: "pages/admin/problems.html",
|
||||
controller: "mainController"
|
||||
});
|
||||
$locationProvider.html5Mode(true);
|
||||
$routeProvider.when("/", {
|
||||
templateUrl: "pages/home.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/about", {
|
||||
templateUrl: "pages/about.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/scoreboard", {
|
||||
templateUrl: "pages/scoreboard.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/learn", {
|
||||
templateUrl: "pages/learn.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/chat", {
|
||||
templateUrl: "pages/chat.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/register", {
|
||||
templateUrl: "pages/register.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/login", {
|
||||
templateUrl: "pages/login.html",
|
||||
controller: "mainController"
|
||||
})
|
||||
.when("/profile", {
|
||||
templateUrl: "pages/profile.html",
|
||||
controller: "profileController"
|
||||
})
|
||||
.when("/logout", {
|
||||
templateUrl: "pages/blank.html",
|
||||
controller: "logoutController"
|
||||
})
|
||||
.when("/admin/problems", {
|
||||
templateUrl: "pages/admin/problems.html",
|
||||
controller: "adminProblemsController"
|
||||
})
|
||||
.otherwise({
|
||||
templateUrl: "pages/404.html",
|
||||
controller: "mainController"
|
||||
});
|
||||
$locationProvider.html5Mode(true);
|
||||
});
|
||||
app.controller("mainController", function($scope) {
|
||||
|
||||
app.controller("mainController", ["$scope", "$http", function($scope, $http) {
|
||||
$scope.config = { navbar: { } };
|
||||
$.post("/api/user/status", function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.config.navbar.logged_in = result["logged_in"];
|
||||
$scope.config.navbar.username = result["username"];
|
||||
$scope.config.navbar.admin = result["admin"];
|
||||
} else {
|
||||
$scope.config.navbar.logged_in = false;
|
||||
}
|
||||
$scope.$apply();
|
||||
}).fail(function() {
|
||||
$scope.config.navbar.logged_in = false;
|
||||
$scope.$apply();
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("logoutController", function() {
|
||||
$.post("/api/user/logout", function(result) {
|
||||
location.href = "/";
|
||||
});
|
||||
});
|
||||
|
||||
app.controller("profileController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
$.post("/api/user/info", function(result) {
|
||||
if (result["success"] == 1) {
|
||||
$scope.user = result["user"];
|
||||
}
|
||||
$scope.$apply();
|
||||
$(".timeago").timeago();
|
||||
});
|
||||
}]);
|
||||
|
||||
app.controller("adminProblemsController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
|
||||
$controller("mainController", { $scope: $scope });
|
||||
}]);
|
||||
|
||||
function display_message(containerId, alertType, message, callback) {
|
||||
$("#" + containerId).html('<div class="alert alert-' + alertType + '">' + message + '</div>');
|
||||
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
||||
window.setTimeout(function () {
|
||||
$("#" + containerId).slideUp("fast", "swing", callback);
|
||||
}, message.length * 75);
|
||||
});
|
||||
}
|
||||
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
|
||||
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
||||
window.setTimeout(function () {
|
||||
$("#" + containerId).slideUp("fast", "swing", callback);
|
||||
}, message.length * 75);
|
||||
});
|
||||
};
|
||||
|
||||
function load_navbar() {
|
||||
$.post("/api/user/status", {
|
||||
},
|
||||
function(data) {
|
||||
if (data.logged_in) {
|
||||
$("#logout").show();
|
||||
$("#account_link").show();
|
||||
} else {
|
||||
$("#login_link").show();
|
||||
$("#register_link").show();
|
||||
}
|
||||
if (data.admin) {
|
||||
$("#admin_dropdown").show();
|
||||
}
|
||||
});
|
||||
}
|
||||
$.fn.serializeObject = function() {
|
||||
var a, o;
|
||||
o = {};
|
||||
a = this.serializeArray();
|
||||
$.each(a, function() {
|
||||
if (o[this.name]) {
|
||||
if (!o[this.name].push) {
|
||||
o[this.name] = [o[this.name]];
|
||||
}
|
||||
return o[this.name].push(this.value || "");
|
||||
} else {
|
||||
return o[this.name] = this.value || "";
|
||||
}
|
||||
});
|
||||
return o;
|
||||
};
|
||||
|
||||
$("#logout").click(function(e) {
|
||||
e.preventDefault();
|
||||
$.post("/api/user/logout", {
|
||||
}, function (data) {
|
||||
window.location = "/";
|
||||
});
|
||||
})
|
||||
// register page
|
||||
|
||||
$(document).ready( load_navbar() );
|
||||
var register_form = function() {
|
||||
var input = "#register_form input";
|
||||
var data = $("#register_form").serializeObject();
|
||||
$.post("/api/user/register", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.href = "/profile";
|
||||
} else {
|
||||
display_message("register_msg", "danger", result["message"])
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// login page
|
||||
|
||||
var login_form = function() {
|
||||
var input = "#login_form input";
|
||||
var data = $("#login_form").serializeObject();
|
||||
$.post("/api/user/login", data, function(result) {
|
||||
if (result["success"] == 1) {
|
||||
location.href = "/profile";
|
||||
} else {
|
||||
display_message("login_msg", "danger", result["message"])
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,21 +0,0 @@
|
|||
$("#login-form").on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
login($("#email").val(), $("#password").val());
|
||||
});
|
||||
|
||||
function login(email, password) {
|
||||
$("#login").attr("disabled", "disabled");
|
||||
$.post("/api/user/login", {
|
||||
email: email,
|
||||
password: password
|
||||
}, function(data) {
|
||||
if (data.success == 1) {
|
||||
display_message("status", "success", "Success!", function() {
|
||||
$("#login").removeAttr("disabled");
|
||||
window.location = "#/account";
|
||||
});
|
||||
} else {
|
||||
display_message("status", "danger", data.message, function() {$("#login").removeAttr("disabled");});
|
||||
}
|
||||
});
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
$("#registration-form").on("submit", function(e) {
|
||||
e.preventDefault();
|
||||
register($("#name").val(), $("#username").val(), $("#password").val(), $("#password_confirm").val(), $("#email").val(), $("#g-recaptcha-response").val());
|
||||
});
|
||||
|
||||
function register(name, username, password, password_confirm, email, captcha_response) {
|
||||
$("#register").attr("disabled", "disabled");
|
||||
$.post("/api/user/register", {
|
||||
name: name,
|
||||
username: username,
|
||||
password: password,
|
||||
password_confirm: password_confirm,
|
||||
email: email,
|
||||
captcha_response: captcha_response
|
||||
}, function(data) {
|
||||
$("#status").text(data.message);
|
||||
if (data.success == 1) {
|
||||
display_message("status", "success", "Success!", function() {
|
||||
$("#register").removeAttr("disabled");
|
||||
window.location = "#/login";
|
||||
});
|
||||
} else {
|
||||
display_message("status", "danger", data.message, function() {$("#register").removeAttr("disabled")});
|
||||
grecaptcha.reset();
|
||||
}
|
||||
});
|
||||
}
|
5
web/pages/404.html
Normal file
5
web/pages/404.html
Normal file
|
@ -0,0 +1,5 @@
|
|||
<div class="page-header">
|
||||
<h1>404: Page Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p>Go away. Stop snooping around, you little creep.</p>
|
|
@ -1,77 +1,5 @@
|
|||
<center class="fade_in ng-scope">
|
||||
<h1>Problems</h1>
|
||||
<div id="status"></div>
|
||||
<input type="button" class="btn btn-primary btn-lg" data-toggle="modal" data-target="#add-modal" value="Add Problem">
|
||||
<div id="problems"></div>
|
||||
<div class="modal fade" id="add-modal" tabindex="-2" role="dialog" aria-labelledby="add-modal-label" data-backdrop="false">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="add-modal-label">Add Problem</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="add-form" method="POST" action="/api/problem/add" enctype="multipart/form-data">
|
||||
<div class="panel-heading">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="name" id="name" autocomplete="on" placeholder="Name" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="category" id="category" autocomplete="on" placeholder="Category" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<textarea type="text" name="description" id="description" autocomplete="on" placeholder="Description" class="form-control"></textarea>
|
||||
<br><br>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="flag" id="flag" autocomplete="off" placeholder="EasyCTF{insert_correct_flag_here}" class="form-control">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<input type="text" name="problem-hint" id="problem-hint" autocomplete="off" placeholder="Hint" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="row">
|
||||
<input type="number" name="value" id="value" autocomplete="off" placeholder="Value" class="form-control-number center-block">
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<h4>These are important files!</h4>
|
||||
<hr>
|
||||
<div class="row">
|
||||
<input type="file" name="files[]" id="files" multiple="true">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
<input form="add-form" id="add-problem" type="submit" class="btn btn-primary" value="Add Problem">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal fade" id="delete-modal" tabindex="-1" role="dialog" aria-labelledby="delete-modal-label" data-backdrop="false">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<h4 class="modal-title" id="delete-modal-label">Delete Problem</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
Are you sure you want to delete this problem? You cannot undo this.
|
||||
<div id="delete_status"></div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">No</button>
|
||||
<button type="button" id="delete" class="btn btn-primary">Yes</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h1>Problems</h1>
|
||||
|
||||
|
||||
<script src="js/admin/problems.js"></script>
|
||||
</center>
|
||||
|
||||
<script type="text/javascript" src="/js/admin/problems.js"></script>
|
0
web/pages/blank.html
Normal file
0
web/pages/blank.html
Normal file
|
@ -1,14 +1,53 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">Log-in</h1>
|
||||
<div class="input-group">
|
||||
<form id="login-form">
|
||||
<input type="text" class="form-control" placeholder="Email" id="email">
|
||||
<input type="password" id="password" name="password" placeholder="Password" class="form-control">
|
||||
<input id="login" type="submit" class="btn btn-lg btn-success" value="Login">
|
||||
</form>
|
||||
<div class="container">
|
||||
<p> </p>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Login</h2>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="login_form(); return false;" id="login_form">
|
||||
<fieldset>
|
||||
<div id="login_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="username"><small>Username</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="submit" class="btn btn-success btn-lg" value="Login" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<small>
|
||||
<a href="/forgot">Forgot Password?</a>
|
||||
</small>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="status"></div>
|
||||
<a href="#register" class="item">Register</a> |
|
||||
<a href="#forgot_password" class="item">Forgot Password</a>
|
||||
</div>
|
||||
<script src="js/login.js"></script>
|
||||
</div>
|
55
web/pages/profile.html
Normal file
55
web/pages/profile.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
<div ng-show="user['user_found']==true">
|
||||
<div class="row">
|
||||
<div class="col-sm-3 col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-body">
|
||||
<img src="//www.gravatar.com/avatar/?size=512" id="avatar" style="max-width:100%;" />
|
||||
<small style="display:block;text-align:right;" ng-show="user['me']==true"><a href="http://en.gravatar.com/emails/" target="_blank">Edit Picture</a></small>
|
||||
<h2 style="margin:0px;font-weight:bold;font-size:2em;">{{ user.name }}</h2>
|
||||
<small style="display:block;font-size:1.5em;">@{{ user.username }}</small>
|
||||
<hr>
|
||||
<div>
|
||||
<i class="fa fa-fw fa-user"></i>
|
||||
{{ user.type }}
|
||||
<div class="label label-info" ng-show="user['admin']==true">ADMIN</div>
|
||||
</div>
|
||||
<div ng-show="user['show_email']==true">
|
||||
<i class="fa fa-fw fa-envelope"></i>
|
||||
<a style="color:#666;" href="mailto:{{ user.email }}">
|
||||
<span id="email">{{ user.email }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<i class="fa fa-fw fa-clock-o"></i>
|
||||
Joined <time class="timeago" datetime="{{ user.registertime }}"></time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-9 col-xs-12">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#profile" aria-controls="profile" role="tab" data-toggle="tab">Profile</a></li>
|
||||
<li role="presentation"><a href="#activity" aria-controls="activity" role="tab" data-toggle="tab">Activity</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="profile">
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane" id="activity">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-show="user['user_found']==false">
|
||||
<div class="page-header">
|
||||
<h1>User Not Found</h1>
|
||||
</div>
|
||||
|
||||
<p>The user you were looking for doesn't exist. Check to make sure you've spelled the name right.</p>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(document).ready(function() {
|
||||
$("ul[role=tablist]").tab();
|
||||
});
|
||||
</script>
|
|
@ -1,64 +1,103 @@
|
|||
<div class="fade_in text-center">
|
||||
<h1 class="heading1">Register</h1>
|
||||
<script src="https://www.google.com/recaptcha/api.js" async defer></script>
|
||||
<script src="js/register.js"></script>
|
||||
<div class="input-group">
|
||||
<form id="registration-form">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-6">
|
||||
<br>
|
||||
<label>Name</label>
|
||||
<br>
|
||||
<input type="text" name="name" id="name" autocomplete="off" autofocus placeholder="Name" class="form-control">
|
||||
<div class="container">
|
||||
<p> </p>
|
||||
<div class="row">
|
||||
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h2 class="panel-title">Register</h2>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<br>
|
||||
<label>Email</label>
|
||||
<br>
|
||||
<input type="email" name="email" id="email" autocomplete="off" placeholder="Email" class="form-control">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" onsubmit="register_form(); return false;" id="register_form">
|
||||
<fieldset>
|
||||
<div id="register_msg"></div>
|
||||
</fieldset>
|
||||
<fieldset class="container-fluid">
|
||||
<p>Register your individual account here. Make sure that members of your team also register accounts. You'll be able to create teams and add members after you register!</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="name"><small>Your Name</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="name" id="name" placeholder="Your Name" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="email"><small>Email</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="email" required name="email" id="email" placeholder="michael@example.com" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="username"><small>Username</small></label>
|
||||
<div class="col-sm-12">
|
||||
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<input class="form-control" type="password" required name="password_confirm" id="password_confirm" placeholder="Confirm Password" autocomplete="off" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12" for="type"><small>Who are you?</small></label>
|
||||
<div class="col-sm-12">
|
||||
<select name="type" id="type" class="selectpicker" data-width="100%">
|
||||
<option value="1">US Middle/High School Student</option>
|
||||
<option value="2">Middle/High School Teacher</option>
|
||||
<option value="3">Non-US/Non-Student/Observer</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<label class="col-sm-12"><small>Receive email notifications about future CTFs?</small></label>
|
||||
<div class="col-sm-12">
|
||||
<div class="btn-group" data-toggle="buttons">
|
||||
<label class="btn btn-default active" id="notify_btn">
|
||||
<input type="checkbox" autocomplete="off" checked name="notify" id="notify"><span id="notify_box" class="fa fa-fw fa-check-square"></span> Get notified about future CTFs
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 form-group">
|
||||
<center>
|
||||
<input type="submit" class="btn btn-primary btn-lg" value="Register" />
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<br>
|
||||
<label>Username</label>
|
||||
<br>
|
||||
<input type="text" name="username" id="username" autocomplete="off" placeholder="Username" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<br>
|
||||
<label>Password</label>
|
||||
<br>
|
||||
<input type="password" name="password" id="password" autocomplete="off" placeholder="Password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="col-md-12">
|
||||
<br>
|
||||
<label>Confirm Password</label>
|
||||
<br>
|
||||
<input type="password" name="password_confirm" id="password_confirm" autocomplete="off" placeholder="Confirm Password" class="form-control">
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="col-md-12">
|
||||
<br>
|
||||
<label>Captcha</label>
|
||||
<br>
|
||||
<div class="g-recaptcha" data-sitekey="6Lc4xhMTAAAAAIaiF3yEWGbHRaGgMg4FHor61p1G"></div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<label>I have read and I agree to <a href="/rules" target="_blank">EasyCTF Rules</a>.</label>
|
||||
<br>
|
||||
<br>
|
||||
<input class="style2" type="checkbox" class="form-control" value="didRead">
|
||||
<br>
|
||||
<input id="register" class="style3" type="submit" class="btn btn-lg btn-success" value="Register">
|
||||
</div>
|
||||
</form>
|
||||
<div id="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(".selectpicker").selectpicker();
|
||||
var checkedClass = "fa fa-check-square";
|
||||
var uncheckedClass = "fa fa-square";
|
||||
$("#notify_btn").click(function() {
|
||||
if($("#notify").is(":checked")) {
|
||||
$("#notify_box").removeClass(checkedClass);
|
||||
$("#notify_box").addClass(uncheckedClass);
|
||||
} else {
|
||||
$("#notify_box").removeClass(uncheckedClass);
|
||||
$("#notify_box").addClass(checkedClass);
|
||||
}
|
||||
});
|
||||
</script>
|
Loading…
Reference in a new issue