Merge pull request #1 from failedxyz/redesign

Redesign
This commit is contained in:
Michael Zhang 2016-01-06 18:48:04 -06:00
commit 177d4bf4ca
26 changed files with 889 additions and 609 deletions

View file

@ -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
View file

@ -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'

View file

@ -4,3 +4,4 @@ Flask-SQLAlchemy
SQLAlchemy
gunicorn
requests
voluptuous

31
server/api.html Normal file
View 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>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial">&nbsp;<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>&nbsp;<br>
<font color="#ffffff" face="helvetica, arial"><big><strong>Package Contents</strong></big></font></td></tr>
<tr><td bgcolor="#aa55cc"><tt>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</tt></td><td>&nbsp;</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>

View file

@ -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 }

View file

@ -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

View file

@ -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
View 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))

View file

@ -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"]

View file

@ -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

View file

@ -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
View 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

Binary file not shown.

Binary file not shown.

View file

@ -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>&nbsp;&nbsp;About</a>
</li>
<li>
<a href="/rules">
<span class="fa fa-book"></span>&nbsp;&nbsp;Rules</a>
</li>
<li>
<a href="/updates">
<span class="fa fa-bullhorn"></span>&nbsp;&nbsp;Updates</a>
</li>
<li>
<a href="/chat">
<span class="fa fa-comments"></span>&nbsp;&nbsp;Chat</a>
</li>
<li>
<a href="/scoreboard">
<span class="fa fa-trophy"></span>&nbsp;&nbsp;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>&nbsp;&nbsp;Problems <span class="caret"></span></a>
<ul class="dropdown-menu" id="menubox">
<li>
<a href="/problems">
<span class="fa fa-pencil"></span>&nbsp;&nbsp;Problems</a>
</li>
<li>
<a href="/programming">
<span class="fa fa-code"></span>&nbsp;&nbsp;Programming</a>
</li>
<li>
<a href="/shell">
<span class="fa fa-terminal"></span>&nbsp;&nbsp;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>&nbsp;&nbsp;Login</a>
</li>
<li id="register_link" style="display:none">
<a href="/register">
<span class="fa fa-pencil"></span>&nbsp;&nbsp;Register</a>
</li>
<li id="account_link" style="display:none">
<a href="/account">
<span class="fa fa-pencil"></span>&nbsp;&nbsp;Account</a>
</li>
<li id="logout" style="display:none">
<a href="/api/user/logout">
<span class="fa fa-pencil"></span>&nbsp;&nbsp;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>&nbsp;&nbsp;Admin<span class="caret"></span></a>
<ul class="dropdown-menu" id="menubox">
<li>
<a href="/admin/problems">
<span class="fa fa-pencil"></span>&nbsp;&nbsp;Problems</a>
</li>
<li>
<a href="/admin/teams">
<span class="fa fa-code"></span>&nbsp;&nbsp;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>

View file

@ -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();
});

View 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();
});

View file

@ -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"])
}
});
};

View file

@ -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");});
}
});
}

View file

@ -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
View 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>

View file

@ -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">&times;</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">&times;</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
View file

View 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>&nbsp;</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
View 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>

View file

@ -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>&nbsp;</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>&nbsp;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>