login/register system with tokens
This commit is contained in:
parent
500462ec00
commit
252ed8ab9b
17 changed files with 547 additions and 353 deletions
|
@ -13,7 +13,7 @@ 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;
|
default_type text/html;
|
||||||
try_files /index.html /index.html;
|
try_files /index.html /index.html;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,3 +4,4 @@ Flask-SQLAlchemy
|
||||||
SQLAlchemy
|
SQLAlchemy
|
||||||
gunicorn
|
gunicorn
|
||||||
requests
|
requests
|
||||||
|
voluptuous
|
|
@ -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,73 +1,99 @@
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask.ext.sqlalchemy import SQLAlchemy
|
||||||
|
import datetime
|
||||||
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)
|
utype = 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
|
||||||
|
|
||||||
class Teams(db.Model):
|
class Teams(db.Model):
|
||||||
tid = db.Column(db.Integer, primary_key=True)
|
tid = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(64), unique=True)
|
name = db.Column(db.String(64), unique=True)
|
||||||
join_code = db.Column(db.String(128), unique=True)
|
join_code = db.Column(db.String(128), unique=True)
|
||||||
school = db.Column(db.Text)
|
school = db.Column(db.Text)
|
||||||
size = db.Column(db.Integer)
|
size = db.Column(db.Integer)
|
||||||
score = db.Column(db.Integer)
|
score = db.Column(db.Integer)
|
||||||
observer = db.Column(db.Boolean)
|
observer = db.Column(db.Boolean)
|
||||||
owner = db.Column(db.Integer)
|
owner = db.Column(db.Integer)
|
||||||
|
|
||||||
def __init__(self, name, school):
|
def __init__(self, name, school):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.school = school
|
self.school = school
|
||||||
|
|
||||||
class Problems(db.Model):
|
class Problems(db.Model):
|
||||||
pid = db.Column(db.Integer, primary_key=True)
|
pid = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(128))
|
name = db.Column(db.String(128))
|
||||||
category = db.Column(db.String(128))
|
category = db.Column(db.String(128))
|
||||||
description = db.Column(db.Text)
|
description = db.Column(db.Text)
|
||||||
hint = db.Column(db.Text)
|
hint = db.Column(db.Text)
|
||||||
flag = db.Column(db.Text)
|
flag = db.Column(db.Text)
|
||||||
disabled = db.Column(db.Boolean, default=False)
|
disabled = db.Column(db.Boolean, default=False)
|
||||||
value = db.Column(db.Integer)
|
value = db.Column(db.Integer)
|
||||||
solves = db.Column(db.Integer, default=0)
|
solves = db.Column(db.Integer, default=0)
|
||||||
|
|
||||||
def __init__(self, name, category, description, hint, flag, value):
|
def __init__(self, name, category, description, hint, flag, value):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.category = category
|
self.category = category
|
||||||
self.description = description
|
self.description = description
|
||||||
self.hint = hint
|
self.hint = hint
|
||||||
self.flag = flag
|
self.flag = flag
|
||||||
self.value = value
|
self.value = value
|
||||||
|
|
||||||
class Files(db.Model):
|
class Files(db.Model):
|
||||||
fid = db.Column(db.Integer, primary_key=True)
|
fid = db.Column(db.Integer, primary_key=True)
|
||||||
pid = db.Column(db.Integer)
|
pid = db.Column(db.Integer)
|
||||||
location = db.Column(db.Text)
|
location = db.Column(db.Text)
|
||||||
|
|
||||||
def __init__(self, pid, location):
|
def __init__(self, pid, location):
|
||||||
self.pid = pid
|
self.pid = pid
|
||||||
self.location = location
|
self.location = location
|
||||||
|
|
||||||
class Solves(db.Model):
|
class Solves(db.Model):
|
||||||
sid = db.Column(db.Integer, primary_key=True)
|
sid = db.Column(db.Integer, primary_key=True)
|
||||||
pid = db.Column(db.Integer)
|
pid = db.Column(db.Integer)
|
||||||
tid = db.Column(db.Integer)
|
tid = db.Column(db.Integer)
|
||||||
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
date = db.Column(db.Integer, default=utils.get_time_since_epoch())
|
||||||
|
|
||||||
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=datetime.datetime.utcnow(), active=True, ua=None, ip=None):
|
||||||
|
self.sid = utils.generate_string()
|
||||||
|
self.uid = uid
|
||||||
|
self.username = username
|
||||||
|
self.issued = datetime.datetime.utcnow()
|
||||||
|
self.expiry = expiry
|
||||||
|
self.active = active
|
||||||
|
self.ua = ua
|
||||||
|
self.ip = ip
|
24
server/api/schemas.py
Normal file
24
server/api/schemas.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import api
|
||||||
|
import re
|
||||||
|
|
||||||
|
from voluptuous import Required, Length, Schema, Invalid, MultipleInvalid
|
||||||
|
from decorators import WebException
|
||||||
|
|
||||||
|
def check(*callback_tuples):
|
||||||
|
def v(value):
|
||||||
|
for callbacks, msg in callback_tuples:
|
||||||
|
for callback in callbacks:
|
||||||
|
try:
|
||||||
|
result = callback(value)
|
||||||
|
if not result and type(result) == bool:
|
||||||
|
raise Invalid(msg)
|
||||||
|
except Exception:
|
||||||
|
raise WebException(msg)
|
||||||
|
return value
|
||||||
|
return v
|
||||||
|
|
||||||
|
def verify_to_schema(schema, data):
|
||||||
|
try:
|
||||||
|
schema(data)
|
||||||
|
except MultipleInvalid as error:
|
||||||
|
raise WebException(str(error))
|
|
@ -1,96 +1,170 @@
|
||||||
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 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()
|
if password != password_confirm:
|
||||||
email_exists = Users.query.add_columns("name", "uid").filter_by(email=email.lower()).first()
|
raise WebException("Passwords do not match.")
|
||||||
|
verify_to_schema(UserSchema, params)
|
||||||
|
|
||||||
if password != password_confirm:
|
user = Users(name, username, email, password, utype)
|
||||||
return { "success": 0, "message": "Passwords do not match." }
|
with app.app_context():
|
||||||
if len(password) > 128:
|
db.session.add(user)
|
||||||
return { "success": 0, "message": "Password is too long." }
|
db.session.commit()
|
||||||
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." }
|
|
||||||
|
|
||||||
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=["GET", "POST"])
|
||||||
@api_wrapper
|
@api_wrapper
|
||||||
def user_logout():
|
def user_logout():
|
||||||
session.clear()
|
sid = session["sid"]
|
||||||
|
username = session["username"]
|
||||||
|
LoginTokens.query.filter_by(sid=sid, username=username).delete()
|
||||||
|
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()
|
|
||||||
if user is None:
|
|
||||||
return { "success": 0, "message": "Invalid credentials." }
|
|
||||||
|
|
||||||
if utils.check_password(user.password, password):
|
username = params.get("username")
|
||||||
session["username"] = user.username
|
password = params.get("password")
|
||||||
if user.admin:
|
|
||||||
session["admin"] = True
|
result = login_user(username, password)
|
||||||
session["logged_in"] = True
|
if result != True:
|
||||||
return { "success": 1, "message": "Success!" }
|
raise WebException("Please check if your username/password are correct.")
|
||||||
else:
|
|
||||||
return { "success": 0, "message": "Invalid credentials." }
|
return { "success": 1, "message": "Success!" }
|
||||||
|
|
||||||
@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 = {
|
||||||
"admin": is_admin(),
|
"success": 1,
|
||||||
"username": session["username"] if is_logged_in() else "",
|
"logged_in": logged_in,
|
||||||
}
|
"admin": is_admin(),
|
||||||
return status
|
"username": session["username"] if logged_in else "",
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
|
||||||
|
##################
|
||||||
|
# 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):
|
||||||
|
with app.app_context():
|
||||||
|
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() })
|
||||||
|
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
|
||||||
|
token = LoginTokens(user.uid, user.username, ua=useragent, ip=ip)
|
||||||
|
with app.app_context():
|
||||||
|
db.session.add(token)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
session["sid"] = token.sid
|
||||||
|
session["username"] = token.username
|
||||||
|
session["admin"] = user.utype == 0
|
||||||
|
|
||||||
|
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:
|
||||||
return False
|
return False
|
||||||
captcha_response = form["captcha_response"]
|
captcha_response = form["captcha_response"]
|
||||||
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
|
data = {"secret": "6Lc4xhMTAAAAACFaG2NyuKoMdZQtSa_1LI76BCEu", "response": captcha_response}
|
||||||
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
|
response = requests.post("https://www.google.com/recaptcha/api/siteverify", data=data)
|
||||||
return response.json()["success"]
|
return response.json()["success"]
|
|
@ -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
|
13
web/css/easyctf.css
Normal file
13
web/css/easyctf.css
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
@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";
|
||||||
|
}
|
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.
108
web/index.html
108
web/index.html
|
@ -8,15 +8,16 @@
|
||||||
<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" />
|
||||||
</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,90 +25,45 @@
|
||||||
</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">
|
||||||
<a href="/programming">
|
<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-code"></span> Programming</a>
|
<ul class="dropdown-menu">
|
||||||
</li>
|
<li><a href="/profile">Profile</a></li>
|
||||||
<li>
|
<li><a href="/team">Team</a></li>
|
||||||
<a href="/shell">
|
<li><a href="/help">Help</a></li>
|
||||||
<span class="fa fa-terminal"></span> Shell</a>
|
<li role="separator" class="divider"></li>
|
||||||
</li>
|
<li><a href="/settings">Settings</a></li>
|
||||||
</ul>
|
<li><a href="/logout">Logout</a></li>
|
||||||
</li>
|
|
||||||
<li id="login_link" style="display:none">
|
|
||||||
<a href="/login">
|
|
||||||
<span class="fa fa-sign-in"></span> Login</a>
|
|
||||||
</li>
|
|
||||||
<li id="register_link" style="display:none">
|
|
||||||
<a href="/register">
|
|
||||||
<span class="fa fa-pencil"></span> Register</a>
|
|
||||||
</li>
|
|
||||||
<li id="account_link" style="display:none">
|
|
||||||
<a href="/account">
|
|
||||||
<span class="fa fa-pencil"></span> Account</a>
|
|
||||||
</li>
|
|
||||||
<li id="logout" style="display:none">
|
|
||||||
<a href="/api/user/logout">
|
|
||||||
<span class="fa fa-pencil"></span> Logout</a>
|
|
||||||
</li>
|
|
||||||
<li id="admin_dropdown" style="display:none">
|
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"><span class="glyphicon glyphicon-pencil"></span> Admin<span class="caret"></span></a>
|
|
||||||
<ul class="dropdown-menu" id="menubox">
|
|
||||||
<li>
|
|
||||||
<a href="/admin/problems">
|
|
||||||
<span class="fa fa-pencil"></span> Problems</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<a href="/admin/teams">
|
|
||||||
<span class="fa fa-code"></span> Teams</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<br>
|
|
||||||
<br>
|
|
||||||
<div id="mainContent" class="ui 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://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://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.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/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="js/easyctf.js"></script>
|
<script src="js/easyctf.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -1,91 +1,110 @@
|
||||||
var app = angular.module("easyctf", [ "ngRoute" ]);
|
var app = angular.module("easyctf", [ "ngRoute" ]);
|
||||||
app.config(function($routeProvider, $locationProvider) {
|
app.config(function($routeProvider, $locationProvider) {
|
||||||
$routeProvider.when("/", {
|
$routeProvider.when("/", {
|
||||||
templateUrl: "pages/home.html",
|
templateUrl: "pages/home.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/about", {
|
.when("/about", {
|
||||||
templateUrl: "pages/about.html",
|
templateUrl: "pages/about.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/register", {
|
.when("/scoreboard", {
|
||||||
templateUrl: "pages/register.html",
|
templateUrl: "pages/scoreboard.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/login", {
|
.when("/learn", {
|
||||||
templateUrl: "pages/login.html",
|
templateUrl: "pages/learn.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/chat", {
|
.when("/register", {
|
||||||
templateUrl: "pages/chat.html",
|
templateUrl: "pages/register.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/updates", {
|
.when("/login", {
|
||||||
templateUrl: "pages/updates.html",
|
templateUrl: "pages/login.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/problems", {
|
.when("/profile", {
|
||||||
templateUrl: "pages/problems.html",
|
templateUrl: "pages/profile.html",
|
||||||
controller: "mainController"
|
controller: "mainController"
|
||||||
})
|
})
|
||||||
.when("/programming", {
|
.when("/logout", {
|
||||||
templateUrl: "pages/programming.html",
|
templateUrl: "pages/blank.html",
|
||||||
controller: "mainController"
|
controller: "logoutController"
|
||||||
})
|
});
|
||||||
.when("/shell", {
|
$locationProvider.html5Mode(true);
|
||||||
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);
|
|
||||||
});
|
});
|
||||||
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"];
|
||||||
|
} else {
|
||||||
|
$scope.config.navbar.logged_in = false;
|
||||||
|
}
|
||||||
|
}).fail(function() {
|
||||||
|
$scope.config.navbar.logged_in = false;
|
||||||
|
});
|
||||||
|
}]);
|
||||||
|
|
||||||
|
app.controller("logoutController", function() {
|
||||||
|
$.post("/api/user/logout", function(result) {
|
||||||
|
location.href = "/";
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
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) {
|
||||||
} else {
|
o[this.name] = [o[this.name]];
|
||||||
$("#login_link").show();
|
}
|
||||||
$("#register_link").show();
|
return o[this.name].push(this.value || "");
|
||||||
}
|
} else {
|
||||||
if (data.admin) {
|
return o[this.name] = this.value || "";
|
||||||
$("#admin_dropdown").show();
|
}
|
||||||
}
|
});
|
||||||
});
|
return o;
|
||||||
}
|
};
|
||||||
|
|
||||||
$("#logout").click(function(e) {
|
// register page
|
||||||
e.preventDefault();
|
|
||||||
$.post("/api/user/logout", {
|
|
||||||
}, function (data) {
|
|
||||||
window.location = "/";
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
$(document).ready( load_navbar() );
|
var register_form = function() {
|
||||||
|
var input = "#register_form input";
|
||||||
|
var data = $("#register_form").serializeObject();
|
||||||
|
$.post("/api/user/register", data, function(result) {
|
||||||
|
if (result["success"] == 1) {
|
||||||
|
location.href = "/profile";
|
||||||
|
} else {
|
||||||
|
display_message("register_msg", "danger", result["message"])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// login page
|
||||||
|
|
||||||
|
var login_form = function() {
|
||||||
|
var input = "#login_form input";
|
||||||
|
var data = $("#login_form").serializeObject();
|
||||||
|
$.post("/api/user/login", data, function(result) {
|
||||||
|
if (result["success"] == 1) {
|
||||||
|
location.href = "/profile";
|
||||||
|
} else {
|
||||||
|
display_message("login_msg", "danger", result["message"])
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,9 +1,4 @@
|
||||||
$("#login-form").on("submit", function(e) {
|
function login_form(email, password) {
|
||||||
e.preventDefault();
|
|
||||||
login($("#email").val(), $("#password").val());
|
|
||||||
});
|
|
||||||
|
|
||||||
function login(email, password) {
|
|
||||||
$("#login").attr("disabled", "disabled");
|
$("#login").attr("disabled", "disabled");
|
||||||
$.post("/api/user/login", {
|
$.post("/api/user/login", {
|
||||||
email: email,
|
email: email,
|
||||||
|
|
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>
|
||||||
</form>
|
</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>
|
||||||
<div id="status"></div>
|
</div>
|
||||||
<a href="#register" class="item">Register</a> |
|
|
||||||
<a href="#forgot_password" class="item">Forgot Password</a>
|
|
||||||
</div>
|
|
||||||
<script src="js/login.js"></script>
|
|
0
web/pages/profile.html
Normal file
0
web/pages/profile.html
Normal file
|
@ -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>
|
<div class="row">
|
||||||
<script src="js/register.js"></script>
|
<div class="col-md-6 col-md-offset-3 col-sm-10 col-sm-offset-1">
|
||||||
<div class="input-group">
|
<div class="panel panel-default">
|
||||||
<form id="registration-form">
|
<div class="panel-heading">
|
||||||
<div class="row">
|
<h2 class="panel-title">Register</h2>
|
||||||
|
|
||||||
<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>
|
</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 teams. You'll be able to create teams and add members after you register!</p>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12" for="name"><small>Your Name</small></label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<input class="form-control" type="text" required name="name" id="name" placeholder="Your Name" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12" for="email"><small>Email</small></label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<input class="form-control" type="email" required name="email" id="email" placeholder="michael@example.com" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12" for="username"><small>Username</small></label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<input class="form-control" type="text" required name="username" id="username" placeholder="Username" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12" for="password"><small>Password</small></label>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input class="form-control" type="password" required name="password" id="password" placeholder="Password" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6">
|
||||||
|
<input class="form-control" type="password" required name="password_confirm" id="password_confirm" placeholder="Confirm Password" autocomplete="off" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12" for="type"><small>Who are you?</small></label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<select name="type" id="type" class="selectpicker" data-width="100%">
|
||||||
|
<option value="1">US Middle/High School Student</option>
|
||||||
|
<option value="2">Middle/High School Teacher</option>
|
||||||
|
<option value="3">Non-US/Non-Student/Observer</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<label class="col-sm-12"><small>Receive email notifications about future CTFs?</small></label>
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<div class="btn-group" data-toggle="buttons">
|
||||||
|
<label class="btn btn-default active" id="notify_btn">
|
||||||
|
<input type="checkbox" autocomplete="off" checked name="notify" id="notify"><span id="notify_box" class="fa fa-fw fa-check-square"></span> Get notified about future CTFs
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12 form-group">
|
||||||
|
<center>
|
||||||
|
<input type="submit" class="btn btn-primary btn-lg" value="Register" />
|
||||||
|
</center>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
</div>
|
||||||
</form>
|
</div>
|
||||||
<div id="status"></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…
Add table
Reference in a new issue