Implement password reset
This commit is contained in:
parent
8de7c95718
commit
ddfe46e34c
7 changed files with 226 additions and 28 deletions
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ def api_wrapper(f):
|
||||||
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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -166,4 +167,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
|
||||||
|
|
|
@ -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:
|
||||||
|
return { "success": 0, "message": "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:
|
||||||
|
return { "success": 0, "message": "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:
|
||||||
|
return { "success": 0, "message": "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:
|
||||||
|
return { "success": 0, "message": 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()
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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) {
|
||||||
|
@ -208,6 +226,41 @@ var register_form = function() {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// password reset
|
||||||
|
var request_reset_form = function() {
|
||||||
|
var data = $("#request_reset_form").serializeObject();
|
||||||
|
data["csrf_token"] = $.cookie("csrf_token");
|
||||||
|
$.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"]);
|
||||||
|
}
|
||||||
|
}).fail(function(jqXHR, status, error) {
|
||||||
|
var result = JSON.parse(jqXHR["responseText"]);
|
||||||
|
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
$.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"]);
|
||||||
|
}
|
||||||
|
}).fail(function(jqXHR, status, error) {
|
||||||
|
var result = JSON.parse(jqXHR["responseText"]);
|
||||||
|
display_message("reset_msg", "danger", "Error " + jqXHR["status"] + ": " + result["message"]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// login page
|
// login page
|
||||||
|
|
||||||
var login_form = function() {
|
var login_form = function() {
|
||||||
|
@ -250,4 +303,4 @@ var add_member = function() {
|
||||||
location.reload(true);
|
location.reload(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
92
web/pages/forgot.html
Normal file
92
web/pages/forgot.html
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
<div class="container">
|
||||||
|
<p> </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>
|
Loading…
Reference in a new issue