~sircmpwn/meta.sr.ht

72292457cbae4dfd4675832482af32cb2b375069 — Drew DeVault 5 days ago fec992d master 0.54.0
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.
M metasrht/blueprints/auth.py => metasrht/blueprints/auth.py +66 -15
@@ 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():
    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():

M metasrht/templates/billing-initial.html => metasrht/templates/billing-initial.html +21 -35
@@ 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
    <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="https://sourcehut.org/alpha-details/"
      >this reference</a>. You may
        rel="noopener"
        target="_blank"
      >consult this document</a>.
      During the alpha, payment is encouraged, but optional, for most features.
      <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="{{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/"

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

M metasrht/templates/forgot.html => metasrht/templates/forgot.html +1 -1
@@ 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>

M metasrht/templates/login.html => metasrht/templates/login.html +3 -3
@@ 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">

A metasrht/templates/register-step2.html => metasrht/templates/register-step2.html +156 -0
@@ 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 %}

M metasrht/templates/register.html => metasrht/templates/register.html +66 -105
@@ 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" %}
<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>
    </div>
  </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")}}
  <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>
      <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>.
</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 %}

M metasrht/templates/registered.html => metasrht/templates/registered.html +4 -4
@@ 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>

M metasrht/templates/reset.html => metasrht/templates/reset.html +2 -2
@@ 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">

M metasrht/templates/totp-challenge.html => metasrht/templates/totp-challenge.html +2 -2
@@ 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