]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
New Signup Flow
authorDylan William Hardison <dylan@hardison.net>
Sun, 28 Jun 2020 03:00:22 +0000 (23:00 -0400)
committerGitHub <noreply@github.com>
Sun, 28 Jun 2020 03:00:22 +0000 (23:00 -0400)
This introduces a much simplified signup flow, over email.

This code is live on https://harmony.bugzilla.ninja

## Before

1. Visit createaccount.cgi
2. Type email
3. Get email, click link
4. Create account
5. Re-enter login and password.

## After

1. Type email into any page
2. Get email, click link
3. Create account + login at the same time

16 files changed:
Bugzilla/App.pm
Bugzilla/App/Plugin/Glue.pm
Bugzilla/App/Users.pm [new file with mode: 0644]
Bugzilla/Config/General.pm
createaccount.cgi [deleted file]
js/global.js
js/signup.js [new file with mode: 0644]
skins/standard/signup.css [new file with mode: 0644]
template/en/default/account/auth/signup-small.html.tmpl [new file with mode: 0644]
template/en/default/admin/params/general.html.tmpl
template/en/default/global/header.html.tmpl
template/en/default/global/user-error.html.tmpl
template/en/default/users/signup_email.html.tmpl [new file with mode: 0644]
template/en/default/users/signup_email.txt+email.tmpl [new file with mode: 0644]
template/en/default/users/signup_email_finish.html.tmpl [new file with mode: 0644]
template/en/default/users/signup_email_verify.html.tmpl [new file with mode: 0644]

index 274ea5021b90202f55b7eb845156e9e0af404e5c..613e6517b74c94baa9d5b9406bb6efad8c2c2986 100644 (file)
@@ -25,6 +25,7 @@ use Bugzilla::App::API;
 use Bugzilla::App::BouncedEmails;
 use Bugzilla::App::CGI;
 use Bugzilla::App::Main;
+use Bugzilla::App::Users;
 use Bugzilla::App::OAuth2::Clients;
 use Bugzilla::App::SES;
 use Bugzilla::App::Static;
@@ -189,6 +190,7 @@ sub setup_routes {
   Bugzilla::App::BouncedEmails->setup_routes($r);
   Bugzilla::App::CGI->setup_routes($r);
   Bugzilla::App::Main->setup_routes($r);
+  Bugzilla::App::Users->setup_routes($r);
   Bugzilla::App::OAuth2::Clients->setup_routes($r);
   Bugzilla::App::SES->setup_routes($r);
 
index 849aa21c1cadd2266082d61195f5132f13c113ae..a9961398d28fec00dff53a0cb96c388ee005231a 100644 (file)
@@ -85,6 +85,16 @@ sub register {
 
       my $name = sprintf '%s.%s.tmpl', $options->{template}, $options->{format};
       my $template = Bugzilla->template;
+      if ($options->{variant}) {
+        my $name_variant = sprintf '%s.%s+%s.tmpl', $options->{template}, $options->{format}, $options->{variant};
+        WARN("loading $name_variant");
+        my $rendered = $template->process($name_variant, \%params, $output);
+        return if $rendered;
+
+        my $error = $template->error;
+        die $error unless $error->type eq 'file' && $error->info =~ /not found/;
+      }
+      WARN("loading $name");
       $template->process($name, \%params, $output) or die $template->error;
     }
   );
diff --git a/Bugzilla/App/Users.pm b/Bugzilla/App/Users.pm
new file mode 100644 (file)
index 0000000..f7bad9f
--- /dev/null
@@ -0,0 +1,212 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+# This Source Code Form is "Incompatible With Secondary Licenses", as
+# defined by the Mozilla Public License, v. 2.0.
+
+package Bugzilla::App::Users;
+use Mojo::Base 'Mojolicious::Controller';
+
+use Bugzilla::Constants;
+use Bugzilla::Error;
+use Bugzilla::Logging;
+use Bugzilla::Mailer qw(MessageToMTA);
+use Date::Format qw(ctime);
+use Scalar::Util qw(blessed);
+use Bugzilla::User;
+use List::Util qw(any);
+use Try::Tiny;
+
+sub setup_routes {
+  my ($class, $r) = @_;
+
+  $r->post('/signup/email')->to('Users#signup_email')->name('signup_email');
+  $r->get('/signup/email/:token/verify')->to('Users#signup_email_verify')
+    ->name('signup_email_verify');
+  $r->post('/signup/email/:token/finish')->to('Users#signup_email_finish')
+    ->name('signup_email_finish');
+}
+
+sub signup_email {
+  my ($c) = @_;
+  my $v = $c->validation;
+
+  try {
+    Bugzilla::User->new->check_account_creation_enabled;
+    my $email_regexp = Bugzilla->params->{createemailregexp};
+    $v->required('email')->like(qr/$email_regexp/);
+    $v->csrf_protect;
+
+    ThrowUserError('account_creation_restricted') unless $v->is_valid;
+
+    my $email = $v->param('email');
+    Bugzilla::User->check_login_name_for_creation($email);
+    Bugzilla::Hook::process("user_verify_login", {login => $email});
+
+    $c->issue_new_user_account_token($email);
+    $c->render(handler => 'bugzilla');
+  }
+  catch {
+    $c->bugzilla->error_page($_);
+  };
+}
+
+sub signup_email_verify {
+  my ($c) = @_;
+  my $token = $c->stash->{token};
+  my (undef, $issuedate, $email) = Bugzilla::Token::GetTokenData($token);
+
+  if ($email) {
+    $c->stash->{signup_token} = $token;
+    $c->stash->{email}        = $email;
+    $c->stash->{expires}      = $issuedate;
+  }
+  else {
+    $c->stash->{missing_token} = 1;
+  }
+
+  $c->render(handler => 'bugzilla');
+}
+
+sub signup_email_finish {
+  my ($c) = @_;
+  my $v = $c->validation;
+  try {
+    $v->optional('create')->equal_to('create');
+    $v->optional('cancel')->equal_to('cancel');
+    $v->csrf_protect;
+    $v->required('signup_token')->size(22);
+
+    my $token = $v->param('signup_token');
+    if ($v->is_valid) {
+      my (undef, undef, $email) = Bugzilla::Token::GetTokenData($token);
+
+      $v->error('signup_token', ['invalid_token']) unless $email;
+
+      if ($v->is_valid && $v->param('create') eq 'create') {
+        $v->optional('realname')->size(1, 255);
+        $v->required('etiquette');
+        $v->required('password')->size(8, 100);
+        $v->required('password_confirm')->size(8, 100);
+        if ($v->is_valid && $v->param('password') ne $v->param('password_confirm')) {
+          $v->error('password_confirm', ['password_mismatch']);
+          $v->error('password', ['password_mismatch']);
+        }
+        if ($v->is_valid) {
+          my $new_user = Bugzilla::User->create({
+            login_name    => $email,
+            realname      => $v->param('realname'),
+            cryptpassword => $v->param('password'),
+          });
+          $c->persist_login($new_user, 'signup');
+          $c->redirect_to('/home');
+        }
+      }
+      elsif ($v->is_valid && $v->param('cancel') eq 'cancel') {
+        my (undef, undef, $email) = Bugzilla::Token::GetTokenData($token);
+        my $vars = {};
+        $vars->{'message'} = 'account_creation_canceled';
+        $vars->{'account'} = $email;
+        Bugzilla::Token::Cancel($token, $vars->{'message'});
+      }
+    }
+    ThrowUserError('validation', { v => $v });
+  }
+  catch {
+    $c->bugzilla->error_page($_);
+  };
+}
+
+# This is adapted from issue_new_user_account_token from Bugzilla/Token.pm
+# Creates and sends a token to create a new user account.
+# It assumes that the login has the correct format and is not already in use.
+sub issue_new_user_account_token {
+  my ($c, $email) = @_;
+  my $dbh = Bugzilla->dbh;
+
+  # Is there already a pending request for this login name? If yes, do not throw
+  # an error because the user may have lost their email with the token inside.
+  # But to prevent using this way to mailbomb an email address, make sure
+  # the last request is at least 10 minutes old before sending a new email.
+
+  my $pending_requests = $dbh->selectrow_array(
+    'SELECT COUNT(*)
+           FROM tokens
+          WHERE tokentype = ?
+                AND ' . $dbh->sql_istrcmp('eventdata', '?') . '
+                AND issuedate > '
+      . $dbh->sql_date_math('NOW()', '-', 10, 'MINUTE'), undef, ('signup', $email)
+  );
+
+  ThrowUserError('too_soon_for_new_token', {'type' => 'signup'})
+    if $pending_requests;
+
+  my ($token, $token_ts)
+    = Bugzilla::Token::_create_token(undef, 'signup', $email);
+
+  $c->stash->{email}   = $email . Bugzilla->params->{'emailsuffix'};
+  $c->stash->{expires} = ctime($token_ts + MAX_TOKEN_AGE * 86400);
+  $c->stash->{verify_url}
+    = $c->url_for('signup_email_verify', token => $token)->to_abs;
+
+  my $message = $c->render_to_string(
+    handler => 'bugzilla',
+    format  => 'txt',
+    variant => 'email'
+  );
+  WARN("Email: is\n$message");
+  MessageToMTA($message->to_string);
+}
+
+# This is adapted from persist_login in Bugzilla/Auth/Persist/Cookie.pm
+sub persist_login {
+  my ($c, $user, $auth_method) = @_;
+  my $dbh = Bugzilla->dbh;
+
+  $dbh->bz_start_transaction();
+
+  my $login_cookie
+    = Bugzilla::Token::GenerateUniqueToken('logincookies', 'cookie');
+
+  my $ip_addr = $c->forwarded_for;
+
+  $dbh->do(
+    'INSERT INTO logincookies (cookie, userid, ipaddr, lastused)
+    VALUES (?, ?, ?, NOW())', undef, $login_cookie, $user->id, $ip_addr
+  );
+
+  # Issuing a new cookie is a good time to clean up the old
+  # cookies.
+  $dbh->do("DELETE FROM logincookies WHERE lastused < "
+      . $dbh->sql_date_math('LOCALTIMESTAMP(0)', '-', MAX_LOGINCOOKIE_AGE, 'DAY'));
+
+  $dbh->bz_commit_transaction();
+
+  my %cookie_attr = (httponly => 1, path => '/', expires => time + 604800);
+
+  if (Bugzilla->localconfig->urlbase =~ /^https/) {
+    $cookie_attr{secure} = 1;
+  }
+
+  $c->cookie('Bugzilla_login',       $user->id,     \%cookie_attr);
+  $c->cookie('Bugzilla_logincookie', $login_cookie, \%cookie_attr);
+
+  my $securemail_groups
+    = Bugzilla->can('securemail_groups')
+    ? Bugzilla->securemail_groups
+    : ['admin'];
+
+  if (any { $user->in_group($_) } @$securemail_groups) {
+    $auth_method //= 'unknown';
+
+    Bugzilla->audit(
+      sprintf "successful login of %s from %s using \"%s\", authenticated by %s",
+      $user->login, $ip_addr, $c->req->headers->user_agent // '', $auth_method);
+  }
+
+  return $login_cookie;
+}
+
+
+1;
index 84cc1bb144a2a42357b5644a17e1d7b589ed46f0..5d8ab094633f95a138764322bc0474c159539ae4 100644 (file)
@@ -41,6 +41,13 @@ use constant get_param_list => (
 
   {name => 'announcehtml', type => 'l', default => ''},
 
+  {
+    name => 'etiquettehtml',
+    type => 'l',
+    default =>
+      'I have read <a href="/page.cgi?id=etiquette.html">Bugzilla Etiquette</a> and agree to abide by it.'
+  },
+
   {
     name    => 'upgrade_notification',
     type    => 's',
diff --git a/createaccount.cgi b/createaccount.cgi
deleted file mode 100755 (executable)
index f0fd407..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env perl
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-#
-# This Source Code Form is "Incompatible With Secondary Licenses", as
-# defined by the Mozilla Public License, v. 2.0.
-
-use 5.10.1;
-use strict;
-use warnings;
-
-use lib qw(. lib local/lib/perl5);
-
-use Bugzilla;
-use Bugzilla::Constants;
-use Bugzilla::Error;
-use Bugzilla::Token;
-
-# Just in case someone already has an account, let them get the correct footer
-# on an error message. The user is logged out just after the account is
-# actually created.
-my $user     = Bugzilla->login(LOGIN_OPTIONAL);
-my $cgi      = Bugzilla->cgi;
-my $template = Bugzilla->template;
-my $vars     = {doc_section => 'myaccount.html'};
-
-print $cgi->header();
-
-$user->check_account_creation_enabled;
-my $login = $cgi->param('login');
-
-if (defined($login)) {
-
-  # Check the hash token to make sure this user actually submitted
-  # the create account form.
-  my $token = $cgi->param('token');
-  check_hash_token($token, ['create_account']);
-
-  $user->check_and_send_account_creation_confirmation($login);
-  $vars->{'login'} = $login;
-
-  $template->process("account/created.html.tmpl", $vars)
-    || ThrowTemplateError($template->error());
-  exit;
-}
-
-# Show the standard "would you like to create an account?" form.
-$template->process("account/create.html.tmpl", $vars)
-  || ThrowTemplateError($template->error());
index 51fd3b50d4f0fb29992aad1d839ffbb2f411d52e..dbb0e4ae5adae07e8d7aa9e1000c5d2c079379f8 100644 (file)
@@ -26,6 +26,12 @@ $(function () {
   $('.hide_mini_login_form').on("click", function (event) {
     return hide_mini_login_form($(this).data('qs-suffix'));
   });
+  $('.show_mini_signup_form').on("click", function (event) {
+    return show_mini_signup_form($(this).data('qs-suffix'));
+  });
+  $('.hide_mini_signup_form').on("click", function (event) {
+    return hide_mini_signup_form($(this).data('qs-suffix'));
+  });
   $('.show_forgot_form').on("click", function (event) {
     return show_forgot_form($(this).data('qs-suffix'));
   });
@@ -86,17 +92,29 @@ function manage_old_lists() {
 
 function show_mini_login_form( suffix ) {
     hide_forgot_form(suffix);
+    hide_mini_signup_form(suffix);
     $('#mini_login' + suffix).removeClass('bz_default_hidden').find('input[required]:first').focus();
-    $('#new_account_container' + suffix).addClass('bz_default_hidden');
     return false;
 }
 
 function hide_mini_login_form( suffix ) {
     $('#mini_login' + suffix).addClass('bz_default_hidden');
-    $('#new_account_container' + suffix).removeClass('bz_default_hidden');
     return false;
 }
 
+function show_mini_signup_form( suffix ) {
+    hide_forgot_form(suffix);
+    hide_mini_login_form(suffix);
+    $('#mini_signup' + suffix).removeClass('bz_default_hidden').find('input[required]:first').focus();
+    return false;
+}
+
+function hide_mini_signup_form( suffix ) {
+    $('#mini_signup' + suffix).addClass('bz_default_hidden');
+    return false;
+}
+
+
 function show_forgot_form( suffix ) {
     hide_mini_login_form(suffix);
     $('#forgot_form' + suffix).removeClass('bz_default_hidden').find('input[required]:first').focus();
diff --git a/js/signup.js b/js/signup.js
new file mode 100644 (file)
index 0000000..9c91b55
--- /dev/null
@@ -0,0 +1,39 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This Source Code Form is "Incompatible With Secondary Licenses", as
+ * defined by the Mozilla Public License, v. 2.0. */
+
+document.addEventListener("DOMContentLoaded", () => {
+  const password = document.getElementById("password");
+  const password_confirm = document.getElementById("password_confirm");
+  const on_change = (event) => {
+    if (password.value == password_confirm.value) {
+      console.log(password.value);
+      console.log(password_confirm.value);
+      password.setCustomValidity("");
+      password_confirm.setCustomValidity("");
+    } else {
+      password.setCustomValidity("This password doesn't match");
+      password_confirm.setCustomValidity("This password doesn't match");
+    }
+  };
+  if (password && password_confirm) {
+    password.addEventListener("change", on_change);
+    password_confirm.addEventListener("change", on_change);
+  }
+
+  const cancel = document.getElementById("signup_cancel");
+  if (cancel) {
+    cancel.addEventListener("click", (event) => {
+      const not_required = ['etiquette', 'password', 'password_confirm'];
+      for (const id of not_required) {
+        const field = document.getElementById(id);
+        if (field) {
+          field.required = false;
+        }
+      }
+    });
+  }
+});
diff --git a/skins/standard/signup.css b/skins/standard/signup.css
new file mode 100644 (file)
index 0000000..31a724d
--- /dev/null
@@ -0,0 +1,51 @@
+.signup, .fresh-signup {
+    max-width: 1023px;
+    min-width: 600px;
+    margin-left: auto;
+    margin-right: auto;
+    display: grid;
+    grid-template-columns: 1fr 4fr;
+    grid-gap: 16px;
+}
+
+@media screen and (max-width: 790px) {
+    .signup, .fresh-signup {
+        max-width: 600px;
+    }
+}
+
+.signup label {
+    grid-column: 1 / 2;
+}
+
+.signup input {
+    grid-column: 2 / 3;
+}
+
+
+.signup .notes {
+    text-align: left;
+    grid-column: 2 / 3;
+}
+
+.signup .buttons {
+    text-align: right;
+    grid-column: 2 / 3;
+}
+
+.fresh-signup {
+    grid-template-columns: 4fr 1fr;
+}
+
+.fresh-signup .notes {
+    text-align: left;
+    grid-column: 1 / 2;
+}
+
+.fresh-signup input {
+    grid-column: 1 / 2;
+}
+
+.fresh-signup button {
+    grid-column: 2 / 2;
+}
diff --git a/template/en/default/account/auth/signup-small.html.tmpl b/template/en/default/account/auth/signup-small.html.tmpl
new file mode 100644 (file)
index 0000000..7af502a
--- /dev/null
@@ -0,0 +1,56 @@
+[%# The contents of this file are subject to the Mozilla Public
+  # License Version 1.1 (the "License"); you may not use this file
+  # except in compliance with the License. You may obtain a copy of
+  # the License at http://www.mozilla.org/MPL/
+  #
+  # Software distributed under the License is distributed on an "AS
+  # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+  # implied. See the License for the specific language governing
+  # rights and limitations under the License.
+  #
+  # The Original Code is the Bugzilla Bug Tracking System.
+  #
+  # The Initial Developer of the Original Code is Netscape Communications
+  # Corporation. Portions created by Netscape are
+  # Copyright (C) 1998 Netscape Communications Corporation. All
+  # Rights Reserved.
+  #
+  # Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
+  #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+[%# Use the current script name. If an empty name is returned,
+  # then we are accessing the home page. %]
+
+<li id="mini_signup_container[% qs_suffix %]">
+  <a id="signup_link[% qs_suffix %]" href="#"
+     class='show_mini_signup_form' data-qs-suffix="[% qs_suffix FILTER html %]">Sign Up</a>
+
+  <div id="mini_signup[% qs_suffix FILTER html %]" class="mini-popup mini_signup bz_default_hidden">
+  [% Hook.process('additional_methods') %]
+
+  <form action="[% c.url_for('signup_email') FILTER html %]" method="post"
+        data-qs-suffix="[% qs_suffix FILTER html %]">
+
+    <input id="signup_email[% qs_suffix FILTER html %]"
+           class="bz_signup"
+           name="email"
+           title="Email"
+           placeholder="Email"
+           aria-label="Email"
+           type="email"
+           required
+    >
+    <input type="hidden" name="csrf_token"
+           value="[% c.csrf_token %]">
+    <input type="submit" value="Sign up"
+           class="check_mini_signup_fields"
+            id="signup_[% qs_suffix %]">
+    <a href="#" id="hide_mini_signup[% qs_suffix FILTER html %]" aria-label="Close"
+       class="close-button hide_mini_signup_form" data-qs-suffix="[% qs_suffix FILTER html %]">
+      <span class="icon" aria-hidden="true"></span>
+    </a>
+  </form>
+  </div>
+</li>
index 248d818a94e54c65f1fdcf4812da1fe0a6920c46..40c778c3b049156ce08444e644ae59ed1d684683 100644 (file)
@@ -56,6 +56,9 @@
     _ " <code>class=\"warning\"</code> to make the text red. Anything defined"
     _ " in <code>skins/standard/global.css</code> will work.",
 
+  etiquettehtml =>
+    "When users create accounts, this message will display next to mandatory check box."
+
   upgrade_notification =>
     "$terms.Bugzilla can inform you when a new release is available."
     _ " The notification will appear on the $terms.Bugzilla homepage,"
index 881880b45c63dfd3e7b4efa93bd898b82c394f6e..cbef59f2a5943aee69cce0a82703afe7f97e6088 100644 (file)
       </div>
     [% ELSE %]
       <ul id="header-login" class="links">
-        [% IF Param('createemailregexp') && user.authorizer.user_can_create_account %]
-          <li id="moz_new_account_container_top"><a href="[% basepath FILTER none %]createaccount.cgi">New Account</a></li>
-        [% END %]
+          [% PROCESS "account/auth/signup-small.html.tmpl" qs_suffix = "_top" %]
         [% IF user.authorizer.can_login %]
           [% PROCESS "account/auth/login-small.html.tmpl" qs_suffix = "_top" %]
         [% END %]
index 8e776504e96c6f7e25f2763a00c042b3d1caed4d..6aef1fb439310d1120df5b9e28051e233e12a84a 100644 (file)
     [% title = "Unknown Tab" %]
     <code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
 
+  [% ELSIF error == "validation" %]
+    <p>Some fields failed validation.</p>
+    <ul>
+        [% FOREACH f IN v.failed %]
+        <li>[% f FILTER html %]: [% v.error(f).join(", ") FILTER html %]</li>
+        [% END %]
+    </ul>
+
   [% ELSIF error == "value_inactive" %]
     [% title = "Value is Not Active" %]
     [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
diff --git a/template/en/default/users/signup_email.html.tmpl b/template/en/default/users/signup_email.html.tmpl
new file mode 100644 (file)
index 0000000..9308f85
--- /dev/null
@@ -0,0 +1,14 @@
+[% title = BLOCK %]New user account for '[% email FILTER html %]'[% END %]
+[% PROCESS "global/header.html.tmpl"
+   title = title
+   style_urls      = ['skins/standard/admin.css']
+   javascript_urls = ['js/account.js']
+%]
+
+<p>
+    We've sent a confirmation message to [% email FILTER html %]. When you recieve
+    it, you'll be able to finish creating your account.
+</p>
+
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/users/signup_email.txt+email.tmpl b/template/en/default/users/signup_email.txt+email.tmpl
new file mode 100644 (file)
index 0000000..a69d361
--- /dev/null
@@ -0,0 +1,30 @@
+[%# INTERFACE:
+  # verify_url: random string used to authenticate the transaction.
+  # expiration_ts: expiration date of the token.
+  # email: email address of the new account.
+  #%]
+
+[% PROCESS global/variables.none.tmpl %]
+
+From: [% Param('mailfrom') %]
+To: [% email %]
+Subject: [% terms.Bugzilla %]: complete account signup
+X-Bugzilla-Type: admin
+
+[%+ terms.Bugzilla %] has received a request to create a user account
+using your email address ([% email %]).
+
+To continue creating an account using this email address, visit the
+following link by [% expiration_ts FILTER time("%B %e, %Y at %H:%M %Z") %]:
+
+[%+ verify_url %]
+
+[% IF Param('createemailregexp') == '.*' && Param('emailsuffix') == '' %]
+PRIVACY NOTICE: [% terms.Bugzilla %] is an open [% terms.bug %] tracking system. Activity on most
+[%+ terms.bugs %], including email addresses, will be visible to the public. We recommend
+using a secondary account or free web email service (such as Gmail, Yahoo,
+Hotmail, or similar) to avoid receiving spam at your primary email address.
+[% END %]
+
+If you do not wish to create an account, or if this request was made in
+error you can do ignore it.
diff --git a/template/en/default/users/signup_email_finish.html.tmpl b/template/en/default/users/signup_email_finish.html.tmpl
new file mode 100644 (file)
index 0000000..ec94a37
--- /dev/null
@@ -0,0 +1,7 @@
+[% PROCESS "global/header.html.tmpl"
+   title           = "Backend validation problems"
+   style_urls      = ['skins/standard/signup.css']
+%]
+[% v = c.stash.v %]
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/template/en/default/users/signup_email_verify.html.tmpl b/template/en/default/users/signup_email_verify.html.tmpl
new file mode 100644 (file)
index 0000000..90debd8
--- /dev/null
@@ -0,0 +1,51 @@
+[% title = BLOCK %]Create a new user account for '[% email FILTER html %]'[% END %]
+[% PROCESS "global/header.html.tmpl"
+   title           = title
+   style_urls      = ['skins/standard/signup.css']
+   javascript_urls      = ['js/signup.js']
+%]
+
+[% IF signup_token %]
+  <form class="signup" method="post" action="[% c.url_for('signup_email_finish') %]">
+    <p class="notes">
+      This account will not be created if this form is not completed by
+      <u>[% expires FILTER time("%B %e, %Y at %H:%M %Z") %]</u>.
+    </p>
+    <input type="hidden" name="csrf_token" value="[% c.csrf_token FILTER html %]">
+    <input type="hidden" name="signup_token" value="[% signup_token FILTER html %]">
+
+    <label for="email">Email Address:</label>
+    <input type="text" id="email" readonly value="[% email FILTER html %]">
+
+    <label for="realname">Display Name</label>
+    <input type="text" id="realname" name="realname" value="" placeholder="Long Name :shortname (pronouns)">
+
+    <label for="password">Type your password</label>
+    <input type="password" id="password" name="password" value="" required>
+
+    <label for="passwd2">Confirm your password</label>
+    <input type="password" id="password_confirm" name="password_confirm" value="" required>
+
+    <div class="buttons">
+      <input type="checkbox" id="etiquette" name='etiquette' value="agreed" required>
+      <label for="etiquette"> [% Param('etiquettehtml') FILTER html_light %]</label>
+    </div>
+
+    <div class="buttons">
+      <button class="secondary" type="submit" id="signup_cancel" name="cancel" value="cancel">Cancel</button>
+      <button type="submit" id="signup_create" name="create" value="create">Create</button>
+    </div>
+  </form>
+[% ELSE %]
+  <form class="fresh-signup" method="post" action="[% c.url_for('signup_email') %]">
+    <p class="notes">
+      It seems we can't verify your email address because the signup token is expired.
+      Fill in the email below and we'll try this again.
+    </p>
+    <input type="hidden" name="csrf_token" value="[% c.csrf_token %]">
+    <input type="text" placeholder="Email Address" id="email">
+    <button type="submit" id="confirm" name="submit" value="create">Sign up</button>
+  </form>
+[% END %]
+
+[% PROCESS global/footer.html.tmpl %]