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.
|
# 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;
|
default_type text/html;
|
||||||
try_files /index.html /index.html;
|
try_files /index.html /index.html;
|
||||||
}
|
}
|
||||||
|
|
2
deploy
2
deploy
|
@ -5,6 +5,8 @@ pkill gunicorn
|
||||||
sudo service nginx stop
|
sudo service nginx stop
|
||||||
tmux kill-session -t ctf 2> /dev/null
|
tmux kill-session -t ctf 2> /dev/null
|
||||||
|
|
||||||
|
sudo cp /vagrant/ctf.nginx /etc/nginx/sites-enabled/ctf
|
||||||
|
|
||||||
echo "Starting the server..."
|
echo "Starting the server..."
|
||||||
cd /home/vagrant/server
|
cd /home/vagrant/server
|
||||||
sudo service nginx start
|
sudo service nginx start
|
||||||
|
|
|
@ -4,3 +4,4 @@ Flask-SQLAlchemy
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
gunicorn
|
gunicorn
|
||||||
requests
|
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 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
|
from models import db, Problems, Files
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
blueprint = Blueprint("admin", __name__)
|
blueprint = Blueprint("admin", __name__)
|
||||||
|
|
||||||
@blueprint.route("/problem/data", methods=["POST"])
|
@blueprint.route("/problems/list", methods=["POST"])
|
||||||
#@api_wrapper # Disable atm due to json serialization issues: will fix
|
@api_wrapper
|
||||||
@admins_only
|
@admins_only
|
||||||
@login_required
|
|
||||||
def problem_data():
|
def problem_data():
|
||||||
problems = Problems.query.add_columns("pid", "name", "category", "description", "hint", "value", "solves", "disabled", "flag").order_by(Problems.value).all()
|
problems = Problems.query.order_by(Problems.value).all()
|
||||||
jason = []
|
problems_return = [ ]
|
||||||
|
|
||||||
for problem in problems:
|
for problem in problems:
|
||||||
problem_files = [ str(_file.location) for _file in Files.query.filter_by(pid=int(problem.pid)).all() ]
|
problems_return.append({
|
||||||
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})
|
"pid": problem.pid,
|
||||||
|
"name": problem.name,
|
||||||
return jsonify(data=jason)
|
"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:
|
except Exception as error:
|
||||||
response = 200
|
response = 200
|
||||||
traceback.print_exc()
|
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 json.dumps(web_result), response, { "Content-Type": "application/json; charset=utf-8" }
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
|
@ -1,24 +1,31 @@
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
import time
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
db = SQLAlchemy()
|
db = SQLAlchemy()
|
||||||
|
|
||||||
class Users(db.Model):
|
class Users(db.Model):
|
||||||
uid = db.Column(db.Integer, primary_key=True)
|
uid = db.Column(db.Integer, unique=True, primary_key=True)
|
||||||
tid = db.Column(db.Integer)
|
tid = db.Column(db.Integer)
|
||||||
name = db.Column(db.String(64))
|
name = db.Column(db.String(64))
|
||||||
username = db.Column(db.String(64), unique=True)
|
username = db.Column(db.String(64), unique=True)
|
||||||
username_lower = db.Column(db.String(64), unique=True)
|
username_lower = db.Column(db.String(64), unique=True)
|
||||||
email = db.Column(db.String(64), unique=True)
|
email = db.Column(db.String(64), unique=True)
|
||||||
password = db.Column(db.String(128))
|
password = db.Column(db.String(128))
|
||||||
admin = db.Column(db.Boolean, default=False)
|
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):
|
def __init__(self, name, username, email, password, utype=1):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.username = username
|
self.username = username
|
||||||
self.username_lower = username.lower()
|
self.username_lower = username.lower()
|
||||||
self.email = email.lower()
|
self.email = email.lower()
|
||||||
self.password = utils.hash_password(password)
|
self.password = utils.hash_password(password)
|
||||||
|
self.utype = utype
|
||||||
|
self.admin = False
|
||||||
|
self.registertime = int(time.time())
|
||||||
|
|
||||||
class Teams(db.Model):
|
class Teams(db.Model):
|
||||||
tid = db.Column(db.Integer, primary_key=True)
|
tid = db.Column(db.Integer, primary_key=True)
|
||||||
|
@ -71,3 +78,27 @@ class Solves(db.Model):
|
||||||
def __init__(self, pid, tid):
|
def __init__(self, pid, tid):
|
||||||
self.pid = pid
|
self.pid = pid
|
||||||
self.tid = tid
|
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,91 +1,203 @@
|
||||||
from flask import Blueprint, session, request, redirect, url_for
|
from flask import Blueprint, session, request, redirect, url_for
|
||||||
from flask import current_app as app
|
from flask import current_app as app
|
||||||
|
from voluptuous import Schema, Length, Required
|
||||||
|
|
||||||
from models import db, Users
|
from models import db, LoginTokens, Users
|
||||||
from decorators import api_wrapper
|
from decorators import api_wrapper, WebException
|
||||||
|
from schemas import verify_to_schema, check
|
||||||
|
|
||||||
|
import datetime
|
||||||
import logger
|
import logger
|
||||||
|
import re
|
||||||
import requests
|
import requests
|
||||||
import utils
|
import utils
|
||||||
|
|
||||||
|
###############
|
||||||
|
# USER ROUTES #
|
||||||
|
###############
|
||||||
|
|
||||||
blueprint = Blueprint("user", __name__)
|
blueprint = Blueprint("user", __name__)
|
||||||
|
|
||||||
@blueprint.route("/register", methods=["POST"])
|
@blueprint.route("/register", methods=["POST"])
|
||||||
@api_wrapper
|
@api_wrapper
|
||||||
def user_register():
|
def user_register():
|
||||||
# if not validate_captcha(request.form):
|
params = utils.flat_multi(request.form)
|
||||||
# return { "success": 0, "message": "Please do the captcha." }
|
|
||||||
|
|
||||||
name = request.form["name"]
|
name = params.get("name")
|
||||||
username = request.form["username"]
|
email = params.get("email")
|
||||||
password = request.form["password"]
|
username = params.get("username")
|
||||||
password_confirm = request.form["password_confirm"]
|
password = params.get("password")
|
||||||
email = request.form["email"]
|
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:
|
if password != password_confirm:
|
||||||
return { "success": 0, "message": "Passwords do not match." }
|
raise WebException("Passwords do not match.")
|
||||||
if len(password) > 128:
|
verify_to_schema(UserSchema, params)
|
||||||
return { "success": 0, "message": "Password is too long." }
|
|
||||||
if len(password) == 0:
|
user = Users(name, username, email, password, utype)
|
||||||
return { "success": 0, "message": "Password is too short." }
|
with app.app_context():
|
||||||
if len(username) > 64:
|
db.session.add(user)
|
||||||
return { "success": 0, "message": "Username is too long." }
|
db.session.commit()
|
||||||
if username_exists:
|
|
||||||
return { "success": 0, "message": "Username is already taken." }
|
|
||||||
if email_exists:
|
|
||||||
return { "success": 0, "message": "Email has already been used." }
|
|
||||||
|
|
||||||
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"])
|
@blueprint.route("/logout", methods=["POST"])
|
||||||
@api_wrapper
|
@api_wrapper
|
||||||
def user_logout():
|
def user_logout():
|
||||||
|
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()
|
session.clear()
|
||||||
|
|
||||||
@blueprint.route("/login", methods=["POST"])
|
@blueprint.route("/login", methods=["POST"])
|
||||||
@api_wrapper
|
@api_wrapper
|
||||||
def user_login():
|
def user_login():
|
||||||
email = request.form["email"]
|
params = utils.flat_multi(request.form)
|
||||||
password = request.form["password"]
|
|
||||||
user = Users.query.filter_by(email=email).first()
|
username = params.get("username")
|
||||||
if user is None:
|
password = params.get("password")
|
||||||
return { "success": 0, "message": "Invalid credentials." }
|
|
||||||
|
result = login_user(username, password)
|
||||||
|
if result != True:
|
||||||
|
raise WebException("Please check if your username/password are correct.")
|
||||||
|
|
||||||
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!" }
|
return { "success": 1, "message": "Success!" }
|
||||||
else:
|
|
||||||
return { "success": 0, "message": "Invalid credentials." }
|
|
||||||
|
|
||||||
@blueprint.route("/status", methods=["POST"])
|
@blueprint.route("/status", methods=["POST"])
|
||||||
@api_wrapper
|
@api_wrapper
|
||||||
def user_status():
|
def user_status():
|
||||||
status = {
|
logged_in = is_logged_in()
|
||||||
"logged_in": is_logged_in(),
|
result = {
|
||||||
|
"success": 1,
|
||||||
|
"logged_in": logged_in,
|
||||||
"admin": is_admin(),
|
"admin": is_admin(),
|
||||||
"username": session["username"] if is_logged_in() else "",
|
"username": session["username"] if logged_in else "",
|
||||||
}
|
}
|
||||||
return status
|
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():
|
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():
|
def is_admin():
|
||||||
return "admin" in session and session["admin"]
|
return is_logged_in() and "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()
|
|
||||||
|
|
||||||
def validate_captcha(form):
|
def validate_captcha(form):
|
||||||
if "captcha_response" not in form:
|
if "captcha_response" not in form:
|
||||||
|
|
|
@ -3,6 +3,7 @@ import json
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import traceback
|
import traceback
|
||||||
|
import unicodedata
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
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):
|
def check_password(hashed_password, try_password):
|
||||||
return check_password_hash(hashed_password, try_password)
|
return check_password_hash(hashed_password, try_password)
|
||||||
|
|
||||||
def generate_string(length):
|
def generate_string(length=32, alpha=string.hexdigits):
|
||||||
return "".join([random.choice(string.letters + string.digits) for x in range(length)])
|
return "".join([random.choice(alpha) for x in range(length)])
|
||||||
|
|
||||||
def unix_time_millis(dt):
|
def unix_time_millis(dt):
|
||||||
epoch = datetime.datetime.utcfromtimestamp(0)
|
epoch = datetime.datetime.utcfromtimestamp(0)
|
||||||
|
@ -22,3 +23,10 @@ def unix_time_millis(dt):
|
||||||
|
|
||||||
def get_time_since_epoch():
|
def get_time_since_epoch():
|
||||||
return unix_time_millis(datetime.datetime.now())
|
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 json
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
from api.decorators import api_wrapper
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
app.config["SQLALCHEMY_DATABASE_URI"] = config.SQLALCHEMY_DATABASE_URI
|
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()
|
api.logger.initialize_logs()
|
||||||
|
|
||||||
@app.route("/api")
|
@app.route("/api")
|
||||||
|
@api_wrapper
|
||||||
def api_main():
|
def api_main():
|
||||||
return json.dumps({ "success": 1, "message": "The API is online." })
|
return { "success": 1, "message": "The API is online." }
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
with app.app_context():
|
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/d3.v3.min.js" charset="utf-8"></script>
|
||||||
<script src="js/c3.min.js"></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 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 rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" integrity="sha384-yNuQMX46Gcak2eQsUzmBYgJ3eBeWYNKhnjyiBqLd1vvtE9kuMtgw6bjwN8J0JauQ" crossorigin="anonymous">
|
||||||
<link type="text/css" rel="stylesheet" href="css/style.css">
|
<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>
|
</head>
|
||||||
|
|
||||||
<body ng-controller="mainController" class="mainbody">
|
<body ng-controller="mainController">
|
||||||
<nav class="navbar navbar-default navbar-fixed-top">
|
<nav class="navbar navbar-default">
|
||||||
<div id="style1" class="container-fluid">
|
<div class="container">
|
||||||
<div class="navbar-header">
|
<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="sr-only">Toggle navigation</span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
|
@ -24,91 +33,49 @@
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">EasyCTF</a>
|
<a class="navbar-brand" href="/">EasyCTF</a>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="navbar" class="navbar-collapse collapse">
|
||||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
<ul class="nav navbar-nav">
|
||||||
<ul class="nav navbar-nav navbar-fixed">
|
<li><a href="/about">About</a></li>
|
||||||
<li>
|
<li><a href="/scoreboard">Scoreboard</a></li>
|
||||||
<a href="/about">
|
<li><a href="/learn">Learn</a></li>
|
||||||
<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>
|
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==false">
|
||||||
<li>
|
<li><a href="/register">Register</a></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>
|
<li><a href="/login">Login</a></li>
|
||||||
<ul class="dropdown-menu" id="menubox">
|
</ul>
|
||||||
<li>
|
<ul class="nav navbar-nav navbar-right" ng-show="config.navbar['logged_in']==true">
|
||||||
<a href="/problems">
|
<div ng-show="config.navbar['competition_started']==true">
|
||||||
<span class="fa fa-pencil"></span> Problems</a>
|
</div>
|
||||||
</li>
|
<li><a href="/chat">Chat</a></li>
|
||||||
<li>
|
<li class="dropdown" ng-show="config.navbar['admin']==true">
|
||||||
<a href="/programming">
|
<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>
|
||||||
<span class="fa fa-code"></span> Programming</a>
|
<ul class="dropdown-menu">
|
||||||
</li>
|
<li><a href="/admin/stats">Statistics</a></li>
|
||||||
<li>
|
<li><a href="/admin/problems">Problem Editor</a></li>
|
||||||
<a href="/shell">
|
<li><a href="/admin/teams">Team Management</a></li>
|
||||||
<span class="fa fa-terminal"></span> Shell</a>
|
<li role="separator" class="divider"></li>
|
||||||
</li>
|
<li><a href="/admin/settings">CTF Settings</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
<li id="login_link" style="display:none">
|
<li class="dropdown">
|
||||||
<a href="/login">
|
<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>
|
||||||
<span class="fa fa-sign-in"></span> Login</a>
|
<ul class="dropdown-menu">
|
||||||
</li>
|
<li><a href="/profile">Profile</a></li>
|
||||||
<li id="register_link" style="display:none">
|
<li><a href="/team">Team</a></li>
|
||||||
<a href="/register">
|
<li><a href="/help">Help</a></li>
|
||||||
<span class="fa fa-pencil"></span> Register</a>
|
<li role="separator" class="divider"></li>
|
||||||
</li>
|
<li><a href="/settings">Settings</a></li>
|
||||||
<li id="account_link" style="display:none">
|
<li><a href="/logout">Logout</a></li>
|
||||||
<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>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<br>
|
|
||||||
<br>
|
<div id="mainContent" class="container">
|
||||||
<div id="mainContent" class="ui container">
|
|
||||||
<div ng-view></div>
|
<div ng-view></div>
|
||||||
</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>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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();
|
||||||
|
});
|
|
@ -8,6 +8,18 @@ app.config(function($routeProvider, $locationProvider) {
|
||||||
templateUrl: "pages/about.html",
|
templateUrl: "pages/about.html",
|
||||||
controller: "mainController"
|
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", {
|
.when("/register", {
|
||||||
templateUrl: "pages/register.html",
|
templateUrl: "pages/register.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
|
@ -16,76 +28,113 @@ app.config(function($routeProvider, $locationProvider) {
|
||||||
templateUrl: "pages/login.html",
|
templateUrl: "pages/login.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/chat", {
|
.when("/profile", {
|
||||||
templateUrl: "pages/chat.html",
|
templateUrl: "pages/profile.html",
|
||||||
controller: "mainController"
|
controller: "profileController"
|
||||||
})
|
})
|
||||||
.when("/updates", {
|
.when("/logout", {
|
||||||
templateUrl: "pages/updates.html",
|
templateUrl: "pages/blank.html",
|
||||||
controller: "mainController"
|
controller: "logoutController"
|
||||||
})
|
|
||||||
.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", {
|
.when("/admin/problems", {
|
||||||
templateUrl: "pages/admin/problems.html",
|
templateUrl: "pages/admin/problems.html",
|
||||||
|
controller: "adminProblemsController"
|
||||||
|
})
|
||||||
|
.otherwise({
|
||||||
|
templateUrl: "pages/404.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
});
|
});
|
||||||
$locationProvider.html5Mode(true);
|
$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) {
|
function display_message(containerId, alertType, message, callback) {
|
||||||
$("#" + containerId).html('<div class="alert alert-' + alertType + '">' + message + '</div>');
|
$("#" + containerId).html("<div class=\"alert alert-" + alertType + "\">" + message + "</div>");
|
||||||
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
$("#" + containerId).hide().slideDown("fast", "swing", function() {
|
||||||
window.setTimeout(function () {
|
window.setTimeout(function () {
|
||||||
$("#" + containerId).slideUp("fast", "swing", callback);
|
$("#" + containerId).slideUp("fast", "swing", callback);
|
||||||
}, message.length * 75);
|
}, message.length * 75);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
function load_navbar() {
|
$.fn.serializeObject = function() {
|
||||||
$.post("/api/user/status", {
|
var a, o;
|
||||||
},
|
o = {};
|
||||||
function(data) {
|
a = this.serializeArray();
|
||||||
if (data.logged_in) {
|
$.each(a, function() {
|
||||||
$("#logout").show();
|
if (o[this.name]) {
|
||||||
$("#account_link").show();
|
if (!o[this.name].push) {
|
||||||
|
o[this.name] = [o[this.name]];
|
||||||
|
}
|
||||||
|
return o[this.name].push(this.value || "");
|
||||||
} else {
|
} else {
|
||||||
$("#login_link").show();
|
return o[this.name] = this.value || "";
|
||||||
$("#register_link").show();
|
|
||||||
}
|
|
||||||
if (data.admin) {
|
|
||||||
$("#admin_dropdown").show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
return o;
|
||||||
|
};
|
||||||
|
|
||||||
$("#logout").click(function(e) {
|
// register page
|
||||||
e.preventDefault();
|
|
||||||
$.post("/api/user/logout", {
|
var register_form = function() {
|
||||||
}, function (data) {
|
var input = "#register_form input";
|
||||||
window.location = "/";
|
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"])
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
};
|
||||||
|
|
||||||
$(document).ready( load_navbar() );
|
// 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>
|
||||||
<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>
|
|
||||||
|
|
||||||
<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">
|
<div class="container">
|
||||||
<h1 class="heading1">Log-in</h1>
|
<p> </p>
|
||||||
<div class="input-group">
|
<div class="row">
|
||||||
<form id="login-form">
|
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||||
<input type="text" class="form-control" placeholder="Email" id="email">
|
<div class="panel panel-default">
|
||||||
<input type="password" id="password" name="password" placeholder="Password" class="form-control">
|
<div class="panel-heading">
|
||||||
<input id="login" type="submit" class="btn btn-lg btn-success" value="Login">
|
<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>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="status"></div>
|
</div>
|
||||||
<a href="#register" class="item">Register</a> |
|
</div>
|
||||||
<a href="#forgot_password" class="item">Forgot Password</a>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<script src="js/login.js"></script>
|
|
||||||
|
|
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">
|
<div class="container">
|
||||||
<h1 class="heading1">Register</h1>
|
<p> </p>
|
||||||
<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="row">
|
||||||
|
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||||
<div class="col-md-6">
|
<div class="panel panel-default">
|
||||||
<br>
|
<div class="panel-heading">
|
||||||
<label>Name</label>
|
<h2 class="panel-title">Register</h2>
|
||||||
<br>
|
|
||||||
<input type="text" name="name" id="name" autocomplete="off" autofocus placeholder="Name" class="form-control">
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
<div class="col-md-6">
|
<form class="form-horizontal" onsubmit="register_form(); return false;" id="register_form">
|
||||||
<br>
|
<fieldset>
|
||||||
<label>Email</label>
|
<div id="register_msg"></div>
|
||||||
<br>
|
</fieldset>
|
||||||
<input type="email" name="email" id="email" autocomplete="off" placeholder="Email" class="form-control">
|
<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 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>
|
||||||
|
|
||||||
<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>
|
||||||
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-sm-12 form-group">
|
||||||
<br>
|
<label class="col-sm-12" for="email"><small>Email</small></label>
|
||||||
<label>Confirm Password</label>
|
<div class="col-sm-12">
|
||||||
<br>
|
<input class="form-control" type="email" required name="email" id="email" placeholder="michael@example.com" autocomplete="off" />
|
||||||
<input type="password" name="password_confirm" id="password_confirm" autocomplete="off" placeholder="Confirm Password" class="form-control">
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
<div class="col-md-12">
|
|
||||||
<br>
|
|
||||||
<label>Captcha</label>
|
|
||||||
<br>
|
|
||||||
<div class="g-recaptcha" data-sitekey="6Lc4xhMTAAAAAIaiF3yEWGbHRaGgMg4FHor61p1G"></div>
|
|
||||||
<br>
|
|
||||||
</div>
|
</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>
|
</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>
|
</form>
|
||||||
<div id="status"></div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</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