This commit is contained in:
Michael Zhang 2016-01-16 20:24:21 -06:00
commit 0a47adb419
11 changed files with 306 additions and 46 deletions

View file

@ -14,7 +14,7 @@ server {
# } # }
# Put all the pages here so Angular doesn't fail. # Put all the pages here so Angular doesn't fail.
location ~^/(about|chat|help|learn|login|profile|register|scoreboard|settings|team)$ { location ~^/(about|chat|help|learn|login|profile|register|scoreboard|settings|team|forgot)$ {
default_type text/html; default_type text/html;
try_files /index.html /index.html; try_files /index.html /index.html;
} }
@ -22,7 +22,7 @@ server {
default_type text/html; default_type text/html;
try_files /index.html /index.html; try_files /index.html /index.html;
} }
location ~^/(profile|team)/(.*)$ { location ~^/(profile|team|forgot)/(.*)$ {
default_type text/html; default_type text/html;
try_files /index.html /index.html; try_files /index.html /index.html;
} }
@ -34,4 +34,4 @@ server {
proxy_pass http://localhost:8000; proxy_pass http://localhost:8000;
proxy_redirect off; proxy_redirect off;
} }
} }

View file

@ -40,11 +40,11 @@ def api_wrapper(f):
response = make_response(result) response = make_response(result)
# Setting CSRF token # Setting CSRF token
if "token" not in session: if "csrf_token" not in session:
token = utils.generate_string() token = utils.generate_string()
response.set_cookie("csrf_token", token) response.set_cookie("csrf_token", token)
session["csrf_token"] = token session["csrf_token"] = token
return response return response
return wrapper return wrapper

View file

@ -18,6 +18,7 @@ class Users(db.Model):
utype = db.Column(db.Integer) utype = db.Column(db.Integer)
tid = db.Column(db.Integer) tid = db.Column(db.Integer)
registertime = db.Column(db.Integer) registertime = db.Column(db.Integer)
reset_token = db.Column(db.String(64))
def __init__(self, name, username, email, password, utype=1): def __init__(self, name, username, email, password, utype=1):
self.name = name self.name = name
@ -185,4 +186,4 @@ class TeamInvitations(db.Model):
def __init__(self, rtype, frid, toid): def __init__(self, rtype, frid, toid):
self.rtype = rtype self.rtype = rtype
self.frid = frid self.frid = frid
self.toid = toid self.toid = toid

View file

@ -7,7 +7,7 @@ from flask import current_app as app
from werkzeug import secure_filename from werkzeug import secure_filename
from models import db, Files, Problems, Solves, Teams from models import db, Files, Problems, Solves, Teams
from decorators import admins_only, api_wrapper, login_required from decorators import admins_only, api_wrapper, login_required, WebException
blueprint = Blueprint("problem", __name__) blueprint = Blueprint("problem", __name__)
@ -24,7 +24,7 @@ def problem_add():
name_exists = Problems.query.filter_by(name=name).first() name_exists = Problems.query.filter_by(name=name).first()
if name_exists: if name_exists:
return { "success": 0, "message": "Problem name already taken." } raise WebException("Problem name already taken.")
problem = Problems(name, category, description, hint, flag, value) problem = Problems(name, category, description, hint, flag, value)
db.session.add(problem) db.session.add(problem)
db.session.commit() db.session.commit()
@ -57,7 +57,7 @@ def problem_delete():
Problems.query.filter_by(pid=pid).delete() Problems.query.filter_by(pid=pid).delete()
db.session.commit() db.session.commit()
return { "success": 1, "message": "Success!" } return { "success": 1, "message": "Success!" }
return { "success": 0, "message": "Problem does not exist!" } raise WebException("Problem does not exist!")
@blueprint.route("/update", methods=["POST"]) @blueprint.route("/update", methods=["POST"])
@admins_only @admins_only
@ -86,7 +86,7 @@ def problem_update():
db.session.commit() db.session.commit()
return { "success": 1, "message": "Success!" } return { "success": 1, "message": "Success!" }
return { "success": 0, "message": "Problem does not exist!" } raise WebException("Problem does not exist!")
@blueprint.route("/submit", methods=["POST"]) @blueprint.route("/submit", methods=["POST"])
@api_wrapper @api_wrapper
@ -113,10 +113,10 @@ def problem_submit():
else: else:
logger.log("submissions.log", logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.name)) logger.log("submissions.log", logger.WARNING, "%s has incorrectly submitted %s to %s" % (team.name, flag, problem.name))
return { "success": 0, "message": "Incorrect." } raise WebException("Incorrect.")
else: else:
return { "success": 0, "message": "Problem does not exist!" } raise WebException("Problem does not exist!")
@blueprint.route("/data", methods=["POST"]) @blueprint.route("/data", methods=["POST"])
#@api_wrapper # Disable atm due to json serialization issues: will fix #@api_wrapper # Disable atm due to json serialization issues: will fix

View file

@ -1,4 +1,4 @@
from flask import Blueprint, request from flask import Blueprint, request, session
from flask import current_app as app from flask import current_app as app
from voluptuous import Schema, Length, Required from voluptuous import Schema, Length, Required
@ -21,7 +21,7 @@ blueprint = Blueprint("team", __name__)
def team_create(): def team_create():
params = utils.flat_multi(request.form) params = utils.flat_multi(request.form)
_user = user.get_user().first() _user = user.get_user().first()
if _user.tid is not None or _user.tid >= 0 or get_team(owner=_user.uid).first() is not None: if (_user.tid is not None and _user.tid >= 0) or get_team(owner=_user.uid).first() is not None:
raise WebException("You're already in a team!") raise WebException("You're already in a team!")
verify_to_schema(TeamSchema, params) verify_to_schema(TeamSchema, params)
@ -29,14 +29,36 @@ def team_create():
school = params.get("school") school = params.get("school")
team = Teams(teamname, school, _user.uid, _user.utype != 1) team = Teams(teamname, school, _user.uid, _user.utype != 1)
tid = team.tid
with app.app_context(): with app.app_context():
db.session.add(team) db.session.add(team)
db.session.commit() db.session.commit()
Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid }) Users.query.filter_by(uid=_user.uid).update({ "tid": team.tid })
db.session.commit() db.session.commit()
session["tid"] = tid
return { "success": 1, "message": "Success!" } return { "success": 1, "message": "Success!" }
@blueprint.route("/delete", methods=["POST"])
@api_wrapper
@login_required
def team_delete():
username = session["username"]
tid = session["tid"]
team = Teams.query.filter_by(tid=tid).first()
usr = Users.query.filter_by(username=username).first()
owner = team.owner
if usr.uid == owner or usr.admin:
usr.tid = -1
with app.app_context():
db.session.add(usr)
db.session.delete(team)
db.session.commit()
session.pop("tid")
return { "success": 1, "message": "Success!" }
else:
raise WebException("Not authorized.")
@blueprint.route("/invite", methods=["POST"]) @blueprint.route("/invite", methods=["POST"])
@api_wrapper @api_wrapper
@login_required @login_required
@ -203,4 +225,4 @@ def get_team(tid=None, teamname=None, teamname_lower=None, owner=None):
match.update({ "tid": _user.tid }) match.update({ "tid": _user.tid })
with app.app_context(): with app.app_context():
result = Teams.query.filter_by(**match) result = Teams.query.filter_by(**match)
return result return result

View file

@ -19,6 +19,55 @@ import utils
blueprint = Blueprint("user", __name__) blueprint = Blueprint("user", __name__)
@blueprint.route("/forgot", methods=["POST"])
@blueprint.route("/forgot/<token>", methods=["GET", "POST"])
@api_wrapper
def user_forgot_password(token=None):
params = utils.flat_multi(request.form)
if token is not None:
user = get_user(reset_token=token).first()
if user is None:
raise WebException("Invalid reset token.")
# We are viewing the actual reset form
if request.method == "GET":
return { "success": 1, "message": ""}
# Submission of actual reset form
if request.method == "POST":
password = params.get("password")
confirm_password = params.get("confirm_password")
if password != confirm_password:
raise WebException("Passwords do not match.")
else:
user.password = utils.hash_password(password)
user.reset_token = None
current_session = db.session.object_session(user)
current_session.add(user)
current_session.commit()
return { "success": 1, "message": "Success!" }
else:
email = params.get("email")
user = get_user(email=email).first()
if user is None:
raise WebException("User with that email does not exist.")
token = utils.generate_string(length=64)
user.reset_token = token
current_session = db.session.object_session(user)
current_session.add(user)
current_session.commit()
reset_link = "%s/forgot/%s" % ("127.0.0.1:8000", token)
subject = "EasyCTF password reset"
body = """%s,\n\nA request to reset your EasyCT password has been made. If you did not request this password reset, you may safely ignore this email and delete it.\n\nYou may reset your password by clicking this link or pasting it to your browser.\n\n%s\n\nThis link can only be used once, and will lead you to a page where you can reset your password.\n\nGood luck!\n\n- The EasyCTF Team""" % (user.username, reset_link)
response = utils.send_email(email, subject, body).json()
if "Queued" in response["message"]:
return { "success": 1, "message": "Email sent to %s" % email }
else:
raise WebException(response["message"])
@blueprint.route("/register", methods=["POST"]) @blueprint.route("/register", methods=["POST"])
@api_wrapper @api_wrapper
def user_register(): def user_register():
@ -150,21 +199,23 @@ UserSchema = Schema({
"notify": str "notify": str
}, extra=True) }, extra=True)
def get_user(username=None, username_lower=None, email=None, uid=None): def get_user(username=None, username_lower=None, email=None, uid=None, reset_token=None):
match = {} match = {}
if username != None: if username != None:
match.update({ "username": username }) match.update({ "username": username })
elif username_lower != None: elif username_lower != None:
match.update({ "username_lower": username_lower }) match.update({ "username_lower": username_lower })
elif uid != None: elif uid != None:
match.update({ "uid": uid }) match.update({ "uid": uid })
elif email != None: elif email != None:
match.update({ "email": email }) match.update({ "email": email })
elif is_logged_in(): elif is_logged_in():
match.update({ "username": session["username"] }) match.update({ "username": session["username"] })
with app.app_context(): elif reset_token != None:
result = Users.query.filter_by(**match) match.update({ "reset_token": reset_token })
return result with app.app_context():
result = Users.query.filter_by(**match)
return result
def login_user(username, password): def login_user(username, password):
user = get_user(username_lower=username.lower()).first() user = get_user(username_lower=username.lower()).first()
@ -186,6 +237,8 @@ def login_user(username, password):
session["sid"] = token.sid session["sid"] = token.sid
session["username"] = token.username session["username"] = token.username
session["admin"] = user.admin == True session["admin"] = user.admin == True
if user.tid is not None and user.tid >= 0:
session["tid"] = user.tid
return True return True

View file

@ -19,6 +19,7 @@ UPLOAD_FOLDER = os.path.normpath("../web/files")
CTF_BEGIN = 0 # To be used later CTF_BEGIN = 0 # To be used later
CTF_END = 0 # To be used later CTF_END = 0 # To be used later
MG_HOST = "" MG_HOST = ""
MG_API_KEY = "" MG_API_KEY = ""
ADMIN_EMAIL = "" ADMIN_EMAIL = ""

View file

@ -48,6 +48,14 @@ app.config(function($routeProvider, $locationProvider) {
templateUrl: "pages/settings.html", templateUrl: "pages/settings.html",
controller: "mainController" controller: "mainController"
}) })
.when("/forgot", {
templateUrl: "pages/forgot.html",
controller: "resetController"
})
.when("/forgot/:token", {
templateUrl: "pages/forgot.html",
controller: "resetController"
})
.when("/team", { .when("/team", {
templateUrl: "pages/team.html", templateUrl: "pages/team.html",
controller: "teamController" controller: "teamController"
@ -120,13 +128,23 @@ app.controller("teamController", ["$controller", "$scope", "$http", "$routeParam
} else { } else {
$controller("loginController", { $scope: $scope }); $controller("loginController", { $scope: $scope });
} }
$.get("/api/team/info", data, function(result) { }]);
if (result["success"] == 1) {
$scope.team = result["team"]; app.controller("resetController", ["$controller", "$scope", "$http", "$routeParams", function($controller, $scope, $http, $routeParams) {
} var data = { };
$scope.$apply(); $scope.token = false;
$(".timeago").timeago(); data["csrf_token"] = $.cookie("csrf_token");
}); if ("token" in $routeParams) {
$scope.token = true;
token = $routeParams["token"];
$.get("/api/user/forgot/" + token, data, function(data) {
$scope.body = data["message"];
$scope.success = data["success"]
$scope.$apply();
});
} else {
$controller("mainController", { $scope: $scope });
}
}]); }]);
app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) { app.controller("adminController", ["$controller", "$scope", "$http", function($controller, $scope, $http) {
@ -196,32 +214,90 @@ $.fn.serializeObject = function() {
var register_form = function() { var register_form = function() {
var input = "#register_form input"; var input = "#register_form input";
var data = $("#register_form").serializeObject(); var data = $("#register_form").serializeObject();
var button = $("#register_form").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/user/register", data, function(result) { api_call("POST", "/api/user/register", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.href = "/profile"; location.href = "/profile";
} else { } else {
display_message("register_msg", "danger", result["message"]); display_message("register_msg", "danger", result["message"], function() {
button.removeAttr("disabled");
});
} }
}).fail(function(jqXHR, status, error) { }).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]); var result = JSON.parse(jqXHR["responseText"]);
display_message("register_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); display_message("register_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
}); });
}; };
// password reset
var request_reset_form = function() {
var data = $("#request_reset_form").serializeObject();
var button = $("#request_reset_form").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/user/forgot", data, function(result) {
if (result["success"] == 1) {
display_message("reset_msg", "success", result["message"]);
} else {
display_message("reset_msg", "danger", result["message"], function() {
button.removeAttr("disabled");
});
}
}).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
});
}
var reset_form = function() {
var data = $("#reset_form").serializeObject();
data["csrf_token"] = $.cookie("csrf_token");
var url = window.location.href;
var token = url.substr(url.lastIndexOf("/")+1);
var button = $("#reset_form").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/user/forgot/" + token, data, function(result) {
if (result["success"] == 1) {
display_message("reset_msg", "success", result["message"], function() {
location.href = "/login";
});
} else {
display_message("reset_msg", "danger", result["message"], function() {
button.removeAttr("disabled");
});
}
}).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
});
}
// login page // login page
var login_form = function() { var login_form = function() {
var input = "#login_form input"; var input = "#login_form input";
var data = $("#login_form").serializeObject(); var data = $("#login_form").serializeObject();
var button = $("#login_form").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/user/login", data, function(result) { api_call("POST", "/api/user/login", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.href = "/profile"; location.href = "/profile";
} else { } else {
display_message("login_msg", "danger", result["message"]); display_message("login_msg", "danger", result["message"], function() {
button.removeAttr("disabled");
});
} }
}).fail(function(jqXHR, status, error) { }).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]); var result = JSON.parse(jqXHR["responseText"]);
display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); display_message("login_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
}); });
}; };
@ -230,25 +306,40 @@ var login_form = function() {
var create_team = function() { var create_team = function() {
var input = "#create_team input"; var input = "#create_team input";
var data = $("#create_team").serializeObject(); var data = $("#create_team").serializeObject();
var button = $("#create_team").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/team/create", data, function(result) { api_call("POST", "/api/team/create", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.reload(true); location.reload(true);
} else { } else {
display_message("create_team_msg", "danger", result["message"]); display_message("create_team_msg", "danger", result["message"], function() {
button.removeAttr("disabled");
});
} }
}).fail(function(jqXHR, status, error) { }).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]); var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]); display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
}); });
}; };
var add_member = function() { var add_member = function() {
var input = "#add_member input"; var input = "#add_member input";
var data = $("#add_member").serializeObject(); var data = $("#add_member").serializeObject();
var button = $("#add_member").find(":submit");
button.prop("disabled", true);
api_call("POST", "/api/team/invite", data, function(result) { api_call("POST", "/api/team/invite", data, function(result) {
if (result["success"] == 1) { if (result["success"] == 1) {
location.reload(true); location.reload(true);
} else {
button.removeAtr("disabled");
} }
}).fail(function(jqXHR, status, error) {
var result = JSON.parse(jqXHR["responseText"]);
display_message("create_team_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"], function() {
button.removeAttr("disabled");
});
}); });
}; };

92
web/pages/forgot.html Normal file
View file

@ -0,0 +1,92 @@
<div class="container">
<p>&nbsp;</p>
<div ng-switch on="token">
<div ng-switch-when="true">
{{ body }}
<div ng-switch on="success">
<div ng-switch-when="1">
<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">Reset Password</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="reset_form(); return false;" id="reset_form">
<fieldset>
<div id="reset_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<p>Reset your password here</p>
<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="New password" autocomplete="off" />
</div>
</div>
<div class="col-sm-12 form-group">
<label class="col-sm-12" for="confirm_password"><small>Confirm Password</small></label>
<div class="col-sm-12">
<input class="form-control" type="password" required name="confirm_password" id="confirm_password" placeholder="Confirm new password" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Reset password" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div ng-switch-default>
<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">Reset Password</h2>
</div>
<div class="panel-body">
<form class="form-horizontal" onsubmit="request_reset_form(); return false;" id="request_reset_form">
<fieldset>
<div id="reset_msg"></div>
</fieldset>
<fieldset class="container-fluid">
<div class="row">
<p>Enter the email you used to sign up with, and we'll send you an an email with a link to reset your password.</p>
<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="Email" autocomplete="off" />
</div>
</div>
</div>
<div class="row">
<div class="col-sm-12 form-group">
<center>
<input type="hidden" id="_csrf" value="{{ csrf_token }}" />
<input type="submit" class="btn btn-success btn-lg" value="Send email" />
</center>
</div>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -51,4 +51,4 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -171,4 +171,4 @@
} }
}); });
$("[data-toggle=tooltip]").tooltip(); $("[data-toggle=tooltip]").tooltip();
</script> </script>