registration: improve sign up flow

This adds separate paths for contributors and maintainers, the former
which skips the billing setup, and the latter which better explains the
payment requirements.
This commit is contained in:
Drew DeVault 2021-06-10 14:25:31 -04:00
parent fec992dd72
commit 72292457cb
10 changed files with 324 additions and 174 deletions

View File

@ -91,13 +91,14 @@ def index():
def register():
if current_user:
return redirect("/")
if cfg("meta.sr.ht::billing", "enabled") != "yes":
return redirect(url_for("auth.register_step2_GET"))
return render_template("register.html", site_key=site_key_id)
@auth.route("/register/<invite_hash>")
def register_invite(invite_hash):
if current_user:
return redirect("/")
if is_external_auth():
return render_template("register.html")
@ -110,13 +111,62 @@ def register_invite(invite_hash):
return render_template("register.html", site_key=site_key_id,
invite_hash=invite_hash)
@csrf_bypass # for registration via sourcehut.org
@auth.route("/register", methods=["POST"])
def register_POST():
valid = Validation(request)
is_open = allow_registration()
valid = Validation(request)
payment = valid.require("payment")
invite_hash = valid.optional("invite_hash")
if not valid.ok:
abort(400)
payment = payment == "yes"
if not is_open:
if not invite_hash:
abort(401)
else:
invite = (Invite.query
.filter(Invite.invite_hash == invite_hash)
.filter(Invite.recipient_id == None)
).one_or_none()
if not invite:
abort(401)
if invite_hash:
session["invite_hash"] = invite_hash
session["payment"] = payment
return redirect(url_for("auth.register_step2_GET"))
@auth.route("/register/step2")
def register_step2_GET():
invite_hash = session.get("invite_hash")
payment = session.get("payment", "no")
if current_user:
return redirect("/")
if invite_hash:
invite = (Invite.query
.filter(Invite.invite_hash == invite_hash)
.filter(Invite.recipient_id == None)
).one_or_none()
if not invite:
abort(404)
return render_template("register-step2.html",
site_key=site_key_id, invite_hash=invite_hash, payment=payment)
@csrf_bypass # for registration via sourcehut.org
@auth.route("/register/step2", methods=["POST"])
def register_step2_POST():
if current_user:
abort(400)
is_open = allow_registration()
session.pop("invite_hash", None)
payment = session.get("payment", False)
valid = Validation(request)
username = valid.require("username", friendly_name="Username")
email = valid.require("email", friendly_name="Email address")
password = valid.require("password", friendly_name="Password")
@ -141,10 +191,9 @@ def register_POST():
valid.expect(False, "Invalid email address", field="email")
if not valid.ok:
return render_template("register.html",
return render_template("register-step2.html",
is_open=(is_open or invite_hash is not None),
site_key=site_key_id,
**valid.kwargs), 400
site_key=site_key_id, **valid.kwargs), 400
if is_abuse(valid):
return redirect("/registered")
@ -167,17 +216,15 @@ def register_POST():
validate_password(valid, password)
if not valid.ok:
return render_template("register.html",
site_key=site_key_id,
return render_template("register-step2.html",
is_open=(is_open or invite_hash is not None),
**valid.kwargs), 400
site_key=site_key_id, **valid.kwargs), 400
allow_plus_in_email = valid.optional("allow-plus-in-email")
if "+" in email and allow_plus_in_email != "yes":
return render_template("register.html",
site_key=site_key_id,
return render_template("register-step2.html",
is_open=(is_open or invite_hash is not None),
**valid.kwargs), 400
site_key=site_key_id, **valid.kwargs), 400
user = User(username)
user.email = email
@ -248,11 +295,15 @@ def confirm_account(token):
audit_log("account confirmed", user=user)
db.session.commit()
login_user(user, set_cookie=True)
if cfg("meta.sr.ht::billing", "enabled") == "yes":
return redirect(url_for("billing.billing_initial_GET"))
metrics.meta_confirmations.inc()
print(f"Confirmed account: {user.username} ({user.email})")
return redirect(onboarding_redirect)
payment = session.pop("payment", False)
if payment and cfg("meta.sr.ht::billing", "enabled") == "yes":
return redirect(url_for("billing.billing_initial_GET"))
else:
return redirect(onboarding_redirect)
@auth.route("/login")
def login_GET():

View File

@ -14,43 +14,29 @@
<div class="col-md-12">
<p>
On {{cfg("sr.ht", "site-name")}}, all plans have access to the same
features. You should pick the plan which best matches your financial needs
and best represents the level of investment you have in {{cfg("sr.ht",
"site-name")}}. If you require financial aid to use {{cfg("sr.ht",
"site-name")}}, please <a href="mailto:{{cfg("sr.ht",
"owner-email")}}">send an email</a> explaining your circumstances and
we'll do our best to accommodate your needs.
features and in the same quantity. You should pick the plan which best
matches your financial needs and best represents the level of investment
you have in {{cfg("sr.ht", "site-name")}}. If you require financial aid
to use {{cfg("sr.ht", "site-name")}}, please
<a href="mailto:{{cfg("sr.ht", "owner-email")}}">send us an email</a>
explaining your circumstances and we'll do our best to accommodate your
needs.
</p>
<div class="alert alert-info">
<strong>Notice</strong>: sr.ht is currently in <strong>alpha</strong>,
and the quality of the service may reflect that. As such, payment is
currently optional, and only encouraged for users who want to support the
ongoing development of the site. For a summary of the guarantees and
limitations that the alpha entails, see <a
href="https://sourcehut.org/alpha-details/"
>this reference</a>. You may
<div class="alert alert-danger">
<strong>Notice</strong>: {{cfg("sr.ht", "site-name")}} is currently
considered at an alpha stage of development, and the quality of the
service may reflect that. However, the service is reliable, stable,
secure, and mostly complete at this stage of development. To learn
exactly what the alpha entails,
<a
href="{{cfg("meta.sr.ht::settings", "onboarding-redirect")}}"
>click here to continue without payment</a>.
</div>
<div style="margin-bottom: 2rem">
<div class="progress">
<div class="progress-bar"
role="progressbar"
style="width: {{paid_pct}}%;"
aria-valuenow="{{paid_pct}}"
aria-valuemin="0" aria-valuemax="100"
>{{paid_pct}}% paid</div>
<div
class="goal"
style="left: 13.37%"
title="Presented without comment"
>13.37%</div>
<div class="progress-bar total">of {{total_users}} registered users</div>
</div>
<small class="text-muted pull-right">
Current number of paid accounts on {{cfg("sr.ht", "site-name")}}
</small>
href="https://sourcehut.org/alpha-details/"
rel="noopener"
target="_blank"
>consult this document</a>.
During the alpha, payment is encouraged, but optional, for most features.
<a
href="{{url_for("profile.profile_GET")}}"
>Continue without payment {{icon('caret-right')}}</a>.
</div>
</div>
</div>
@ -104,7 +90,7 @@
<div class="row">
<div class="col-md-12">
<div class="alert alert-warning">
<strong>Notice</strong>: continuing to the next page will execute
<strong>Notice</strong>: Continuing to the next page will execute
non-free JavaScript from our payment processor,
<a
href="https://stripe.com/"

View File

@ -1,4 +0,0 @@
<p>
{{cfg("sr.ht", "site-name")}} is a community and a network of websites
supporting hackers and their projects.
</p>

View File

@ -4,7 +4,7 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="col-md-8 offset-md-2">
<h3>
Reset password
</h3>

View File

@ -4,17 +4,17 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="col-md-8 offset-md-2">
<h3>
Log in to {{cfg("sr.ht", "site-name")}}
<small>
or <a href="/register">register</a>
or <a href="/register">register</a>
</small>
</h3>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="col-md-6 offset-md-3">
<form method="POST" action="/login">
{{csrf_token()}}
<div class="form-group">

View File

@ -0,0 +1,156 @@
{% extends "layout.html" %}
{% block title %}
<title>Register for {{cfg("sr.ht", "site-name")}}</title>
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-10 offset-md-1">
<h3>
Register for {{cfg("sr.ht", "site-name")}}
<small>
or <a href="/login">log in</a>
</small>
</h3>
</div>
</div>
{% if is_external_auth() %}
<p>Registration is disabled because {{cfg("sr.ht", "site-name")}} authentication
is managed by a different service. Please contact the system administrator
for further information.</p>
{% elif allow_registration() or invite_hash %}
{% if cfg("meta.sr.ht::billing", "enabled") == "yes" %}
<div class="row">
<div class="col-md-8 offset-md-2">
<p>
{% if payment %}
You are registering as a <strong>maintainer</strong>. After you complete
your registration, you will be taken to the billing page, where you'll
be provided information on payment options, financial aid, and so on.
<a href="{{url_for("auth.register")}}">Register as a contributor instead {{icon('caret-right')}}</a>
{% else %}
You are registering as a <strong>contributor</strong>, which is free but
will limit your access to some features. After you complete registration,
you can convert to a maintainer account by setting up billing on your
profile at any time.
<a href="{{url_for("auth.register")}}">Register as a maintainer instead {{icon('caret-right')}}</a>
{% endif %}
</p>
</div>
</div>
{% endif %}
<div class="row">
<div class="col-md-6 offset-md-3">
<form method="POST" action="{{url_for("auth.register_step2_POST")}}">
{{csrf_token()}}
{% if invite_hash %}
<input type="hidden" name="invite_hash" value="{{invite_hash}}" />
<div class="alert alert-info">
You have received a special invitation to join {{cfg("sr.ht",
"site-name")}}. Sign up here!
</div>
{% endif %}
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
name="username"
id="username"
value="{{ username }}"
class="form-control {{valid.cls("username")}}"
required
autocomplete="username"
{% if not username %} autofocus{% endif %} />
{{valid.summary("username")}}
</div>
<div class="form-group">
<label for="email">Email address</label>
<input
type="email"
name="email"
id="email"
value="{{ email }}"
class="form-control {{valid.cls("email")}}"
required
{% if username and not email %} autofocus{% endif %} />
{{valid.summary("email")}}
{% if email and "+" in email %}
<input type="hidden" name="allow-plus-in-email" value="yes" />
<div class="alert alert-danger">
<strong>Warning</strong>: in order to use {{cfg("sr.ht",
"site-name")}} effectively, you must be able to both send <em>and</em>
receive emails from this email address. To continue, submit the form
again.
</div>
{% endif %}
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
name="password"
id="password"
value="{{ password }}"
class="form-control {{valid.cls("password")}}"
required
autocomplete="new-password"
{% if username and email and not password %} autofocus{% endif %} />
{{valid.summary("password")}}
</div>
{% if site_key %}
<div class="form-group">
<details>
<summary>PGP public key (optional)</summary>
<textarea
class="form-control {{valid.cls("pgp-key")}}"
id="pgp-key"
name="pgp-key"
style="font-family: monospace"
rows="5"
placeholder="gpg --armor --export-options export-minimal --export fingerprint…"
>{{pgp_key or ""}}</textarea>
<small class="form-text text-muted">
Emails sent from {{cfg("sr.ht", "site-name")}} are
signed with our PGP key:<br />
<a href="/privacy/pubkey">{{site_key}}</a>
<br />
If you add your PGP key here, we will also encrypt emails sent to
you. You may change this in your settings later, but if you enable
it now you must be able to decrypt the confirmation email to
complete registration.
</small>
{{valid.summary("pgp-key")}}
</details>
</div>
{% endif %}
<button class="btn btn-primary pull-right" type="submit">
Register {{icon("caret-right")}}
</button>
<p class="clearfix"></p>
</form>
</div>
</div>
<div class="row">
<div class="col-md-8 offset-md-2">
<div class="alert alert-warning">
<strong>Privacy notice</strong>:
{{cfg("sr.ht", "site-name")}} collects the minimum amount of your personal
information which is necessary to faciliate the features of our services.
We do not collect or process any of your personal information for the
purposes of marketing or analytics. We do not send unsolicited marketing
emails. Your information is only shared with third-parties if it is
necessary to facilitate our services, and you will be warned before this
occurs and given an opportunity to prevent the transmission of your
information.
<a
href="{{cfg("sr.ht", "privacy-policy", default="https://man.sr.ht/privacy.md")}}"
rel="noopener"
target="_blank"
>Privacy policy {{icon('external-link-alt')}}</a>
</div>
</div>
</div>
{% else %}
<p>Registration is currently closed.</p>
{% endif %}
{% endblock %}

View File

@ -4,7 +4,7 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="col-md-10 offset-md-1">
<h3>
Register for {{cfg("sr.ht", "site-name")}}
<small>
@ -18,116 +18,77 @@
is managed by a different service. Please contact the system administrator
for further information.</p>
{% elif allow_registration() or invite_hash %}
<div class="row">
<div class="col-md-12">
{% include "blurb.html" %}
</div>
</div>
<div class="row">
<div class="col-md-6 offset-md-3">
<form method="POST" action="/register">
{% if invite_hash %}
<input type="hidden" name="invite_hash" value="{{invite_hash}}" />
<div class="alert alert-info">
You have received a special invitation to join {{cfg("sr.ht",
"site-name")}}. Sign up here!
</div>
{% endif %}
<div class="form-group">
<label for="username">Username</label>
<input
type="text"
name="username"
id="username"
value="{{ username }}"
class="form-control {{valid.cls("username")}}"
required
autocomplete="username"
{% if not username %} autofocus{% endif %} />
{{valid.summary("username")}}
</div>
<div class="form-group">
<label for="email">Email address</label>
<input
type="email"
name="email"
id="email"
value="{{ email }}"
class="form-control {{valid.cls("email")}}"
required
{% if username and not email %} autofocus{% endif %} />
{{valid.summary("email")}}
{% if email and "+" in email %}
<input type="hidden" name="allow-plus-in-email" value="yes" />
<div class="alert alert-danger">
<strong>Warning</strong>: in order to use {{cfg("sr.ht",
"site-name")}} effectively, you must be able to both send <em>and</em>
receive emails from this email address. To continue, submit the form
again.
</div>
{% endif %}
</div>
<div class="form-group">
<label for="password">Password</label>
<input
type="password"
name="password"
id="password"
value="{{ password }}"
class="form-control {{valid.cls("password")}}"
required
autocomplete="new-password"
{% if username and email and not password %} autofocus{% endif %} />
{{valid.summary("password")}}
</div>
{% if site_key %}
<div class="form-group">
<details>
<summary><label for="pgp-key">PGP public key (optional)</label></summary>
<textarea
class="form-control {{valid.cls("pgp-key")}}"
id="pgp-key"
name="pgp-key"
style="font-family: monospace"
rows="5"
placeholder="gpg --armor --export-options export-minimal --export fingerprint…"
>{{pgp_key or ""}}</textarea>
<small class="form-text text-muted">
Emails sent from {{cfg("sr.ht", "site-name")}} are
signed with our PGP key:<br />
<a href="/privacy/pubkey">{{site_key}}</a>
<br />
If you add your PGP key here, we will also encrypt emails sent to
you. You may change this in your settings later, but if you enable
it now you must be able to decrypt the confirmation email to
complete registration.
</small>
{{valid.summary("pgp-key")}}
</details>
</div>
{% endif %}
<button class="btn btn-primary pull-right" type="submit">
Register {{icon("caret-right")}}
<form
class="row"
action="{{url_for("auth.register_POST")}}"
method="POST"
style="margin-bottom: 0" {# Look. I know. #}
>
{{csrf_token()}}
{% if invite_hash %}
<input type="hidden" name="invite_hash" value="{{invite_hash}}" />
{% endif %}
<div class="col-md-5 offset-md-1 event-list">
<div class="event">
<h3>Register as a contributor</h3>
<p>
<strong>Signing up to contribute to a project here?</strong>
<br />
You may sign up to participate in projects hosted on
{{cfg("sr.ht", "site-name")}} for free. If you decide to host your own
projects here in the future, you can convert to a paid account later.
</p>
<button
type="submit"
name="payment"
value="no"
class="btn btn-primary btn-block"
>
Sign up for free {{icon("caret-right")}}
</button>
<p class="clearfix"></p>
</form>
</div>
</div>
<div class="col-md-8 offset-md-2">
<div class="alert alert-warning">
<strong>Privacy notice</strong>:
{{cfg("sr.ht", "site-name")}} collects the minimum amount of your personal
information which is necessary to faciliate the features of our services.
We do not collect or process any of your personal information for the
purposes of marketing or analytics. For details, please review our
<a
href="{{cfg("sr.ht", "privacy-policy", default="https://man.sr.ht/privacy.md")}}"
rel="noopener"
target="_blank"
>privacy policy</a>.
<div class="col-md-5">
<div class="event">
<h3>Register as a maintainer</h3>
<p>
<strong>Want to host your own projects here?</strong>
<br />
Projects hosted on {{cfg("sr.ht", "site-name")}} are expected to pay for
their account. Financial aid is available for those in need. You can
cancel at any time without losing access to your data.
<a href="https://sourcehut.org/pricing" rel="noopener" target="_blank">
Pricing details {{icon('external-link-alt')}}
</a>
</p>
<button
type="submit"
name="payment"
value="yes"
class="btn btn-primary btn-block"
>
Sign up with payment {{icon("caret-right")}}
</button>
</div>
</div>
</form>
<div class="row">
<div class="col-md-10 offset-md-1">
<div class="alert alert-info">
Contributors can also skip registration entirely. You may submit or
comment on tickets, participate in discussions, and send patches to
projects on {{cfg("sr.ht", "site-name")}}, without signing up for an
account. You can find links to participate via email throughout the
logged-out version of many services.
</div>
</div>
</div>
{% else %}
<p>Registration is currently closed.</p>
<div class="row">
<div class="col-md-10 offset-md-1">
<p>Registration is currently closed.</p>
</div>
</div>
{% endif %}
{% endblock %}

View File

@ -4,13 +4,13 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="col-md-8 offset-md-2">
<h3>
Check your email
Registration successful
</h3>
<p>
You will receive an email shortly with a link to complete registration.
Contact
You will receive an email shortly with a link to complete your account
registration. Contact support at
<a href="mailto:{{cfg("sr.ht", "owner-email")}}">
{{ "{} <{}>".format(cfg("sr.ht", "owner-name"), cfg("sr.ht", "owner-email")) }}</a> if you need help.
</p>

View File

@ -4,14 +4,14 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="col-md-12">
<h3>
Reset password
</h3>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="col-md-6 offset-md-6">
<form method="POST">
{{csrf_token()}}
<div class="form-group">

View File

@ -4,14 +4,14 @@
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-8">
<div class="col-md-8 offset-md-2">
<h3>
TOTP Challenge
</h3>
</div>
</div>
<div class="row">
<div class="col-md-8">
<div class="col-md-8 offset-md-2">
<p>
{% if challenge_type == "reset" %}
This account has two-factor authentication enabled. You must complete a