Implement post-donation password setting

Still to come: password reset
master
Drew DeVault 9 years ago
parent 84d34e291a
commit 2fb788316b
  1. 4
      README.md
  2. 89
      fosspay/blueprints/html.py
  3. 2
      fosspay/objects.py
  4. 7
      scripts/index.js
  5. 22
      templates/index.html
  6. 43
      templates/login.html
  7. 8
      templates/summary.html

@ -95,6 +95,10 @@ run this command to try the site in development mode:
[Click here](http://localhost:5000) to visit your donation site and further
instructions will be provided there.
### Static Assets
Run `make` to compile static assets.
### Production Deployment
To deploy this to production, copy the systemd unit from `contrib/` to your

@ -1,13 +1,17 @@
from flask import Blueprint, render_template, abort, request, redirect, session, url_for, send_file, Response
from flask.ext.login import current_user, login_user, logout_user
from datetime import datetime, timedelta
from fosspay.objects import *
from fosspay.database import db
from fosspay.common import *
from fosspay.config import _cfg, load_config
import os
import locale
import bcrypt
import hashlib
import stripe
import binascii
encoding = locale.getdefaultlocale()[1]
html = Blueprint('html', __name__, template_folder='../../templates')
@ -19,7 +23,14 @@ def index():
return render_template("setup.html")
projects = sorted(Project.query.all(), key=lambda p: p.name)
avatar = "//www.gravatar.com/avatar/" + hashlib.md5(_cfg("your-email").encode("utf-8")).hexdigest()
return render_template("index.html", projects=projects, avatar=avatar)
selected_project = request.args.get("project")
if selected_project:
try:
selected_project = int(selected_project)
except:
selected_project = None
return render_template("index.html", projects=projects,
avatar=avatar, selected_project=selected_project)
@html.route("/setup", methods=["POST"])
def setup():
@ -81,3 +92,79 @@ def login():
def logout():
logout_user()
return redirect("/")
@html.route("/donate", methods=["POST"])
@json_output
def donate():
email = request.form.get("email")
stripe_token = request.form.get("stripe_token")
amount = request.form.get("amount")
type = request.form.get("type")
comment = request.form.get("comment")
project_id = request.form.get("project")
if not email or not stripe_token or not amount or not type:
return { "success": False, "reason": "Invalid request" }, 400
try:
if project_id is None or project_id == "null":
project = None
else:
project_id = int(project_id)
project = Project.query.filter(Project.id == project_id).first()
except:
return { "success": False, "reason": "Invalid request" }, 400
new_account = False
user = User.query.filter(User.email == email).first()
if not user:
new_account = True
user = User(email, binascii.b2a_hex(os.urandom(20)).decode("utf-8"))
user.passwordReset = binascii.b2a_hex(os.urandom(20)).decode("utf-8")
user.passwordResetExpiry = datetime.now() + timedelta(days=1)
db.add(user)
customer = stripe.Customer.create(email=user.email, card=stripe_token)
user.stripe_customer = customer.id
db.commit()
if new_account:
return { "success": True, "new_account": new_account, "password_reset": user.password_reset }
else:
return { "success": True, "new_account": new_account }
@html.route("/password-reset", methods=['GET', 'POST'], defaults={'token': None})
@html.route("/password-reset/<token>", methods=['GET', 'POST'])
def reset_password(token):
if not token and request.method == "POST":
token = request.form.get("token")
if not token:
redirect("/")
else:
redirect("/")
user = User.query.filter(User.password_reset == token).first()
if not user:
redirect("/")
if request.method == 'GET':
if user.password_reset_expires == None or user.password_reset_expires < datetime.now():
return render_template("reset.html", expired=True)
if user.password_reset != token:
redirect("/")
return render_template("reset.html", token=token)
else:
if user.password_reset_expires == None or user.password_reset_expires < datetime.now():
abort(401)
if user.password_reset != token:
abort(401)
password = request.form.get('password')
if not password:
return render_template("reset.html", token=token, errors="You need to type a new password.")
user.set_password(password)
user.password_reset = None
user.password_reset_expires = None
db.commit()
login_user(user)
return redirect("/panel")
@html.route("/panel")
@loginrequired
def panel():
return render_template("panel.html")

@ -36,7 +36,7 @@ class User(Base):
self.set_password(password)
def __repr__(self):
return "<User {}>".format(self.username)
return "<User {}>".format(self.email)
# Flask.Login stuff
# We don't use most of these features

@ -82,7 +82,9 @@
data.append("email", token.email);
data.append("amount", donation.amount);
data.append("type", donation.type);
data.append("comment", donation.comment);
if (donation.comment !== null) {
data.append("comment", donation.comment);
}
if (donation.project !== null) {
data.append("project", donation.project);
}
@ -92,8 +94,9 @@
document.getElementById("donation-stuff").classList.add("hidden");
document.getElementById("thanks").classList.remove("hidden");
var res = JSON.parse(this.responseText);
if (res.newAccount) {
if (res.new_account) {
document.getElementById("new-donor-password").classList.remove("hidden");
document.getElementById("reset-token").value = res.password_reset;
}
};
xhr.send(data);

@ -17,14 +17,7 @@ window.default_type = "{{ _cfg("default-type") }}";
<h1>Donate to {{ _cfg("your-name") }}</h1>
<div class="row">
<div class="col-md-8 col-md-offset-2">
<p>
Donations accumulate until there's enough to fund one week of
full time development. The project you specify influences which
projects receive the most time. Each donated-to project will
receive attention, even if there's just one donation for it.
Monthly donations will help me keep doing this for a long time,
but one-offs are also great.
</p>
{% include "summary.html" %}
</div>
</div>
</div>
@ -39,11 +32,12 @@ window.default_type = "{{ _cfg("default-type") }}";
</noscript>
<div class="container text-center hidden" id="thanks">
{% include "post-donation-message.html" %}
<div id="new-donor-password" class="hidden">
<form id="new-donor-password" class="hidden" action="/password-reset" method="POST">
<p>Set a password now if you want to manage your donations later:</p>
<input type="password" placeholder="Password" />
<input type="password" placeholder="Password" name="password" />
<input type="hidden" name="token" id="reset-token" />
<button class="btn btn-primary btn-sm">Submit</button>
</div>
</form>
</div>
<div class="container text-center" id="donation-stuff">
<h3>How much?</h3>
@ -115,9 +109,11 @@ window.default_type = "{{ _cfg("default-type") }}";
<div class="col-md-4 col-md-offset-4">
<div class="form-group">
<select id="project" class="form-control">
<option value="null">None in particular</option>
<option value="null"
{{ "selected" if selected_project == None else "" }}>None in particular</option>
{% for project in projects %}
<option value="{{ project.id }}">{{ project.name }}</option>
<option value="{{ project.id }}"
{{ "selected" if selected_project == project.id else "" }}>{{ project.name }}</option>
{% endfor %}
</select>
</div>

@ -1,20 +1,31 @@
{% extends "layout.html" %}
{% block container %}
<h1>Log In</h1>
{% if errors %}
<div class="alert alert-danger">
<p>
Username or password incorrect.
</p>
</div>
{% endif %}
<form action="/login" method="POST">
<div class="form-group">
<input class="form-control" type="text" name="email" placeholder="you@email.com" />
{% block body %}
<div class="well">
<div class="container">
<h1>Donate to {{ _cfg("your-name") }}</h1>
</div>
<div class="form-group">
<input class="form-control" type="password" name="password" placeholder="Password" />
</div>
<div class="container">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<h1>Log In</h1>
{% if errors %}
<div class="alert alert-danger">
<p>
Username or password incorrect.
</p>
</div>
{% endif %}
<form action="/login" method="POST">
<div class="form-group">
<input class="form-control" type="text" name="email" placeholder="you@email.com" />
</div>
<div class="form-group">
<input class="form-control" type="password" name="password" placeholder="Password" />
</div>
<input type="submit" value="Log in" class="btn btn-primary" />
</form>
</div>
</div>
<input type="submit" value="Log in" class="btn btn-primary" />
</form>
</div>
{% endblock %}

@ -0,0 +1,8 @@
<p>
Donations accumulate until there's enough to fund one week of
full time development. The project you specify influences which
projects receive the most time. Each donated-to project will
receive attention, even if there's just one donation for it.
Monthly donations will help me keep doing this for a long time,
but one-offs are also great.
</p>
Loading…
Cancel
Save