- updated readme and env.example

- fix some language validator inconsistencies
- added admin notifications
- added possiblity for users to delete their account
- added back index page
- works with mod_admin_rest version [afc42d7](afc42d70f0)
This commit is contained in:
Alexander Schäferdiek 2016-07-10 20:45:42 +02:00
parent f0643ce999
commit 9f085ca304
15 changed files with 289 additions and 56 deletions

View file

@ -63,7 +63,20 @@ as dependencies.
* Change directory to project home
* `git pull`
* `composer update`
* look for changes
* `php bin/phpmig migrate`
## Translations ##
This app uses Symfony Translator. It's bootstraped in `Util\TranslationHelper` and locales are placed under `data/locale/`. Adjust to your needs or help translating.
## Changelog ##
- 0.1.1:
- updated readme and `env.example`
- fix some language validator inconsistencies
- added admin notifications
- added possiblity for users to delete their account
- added back index page
- works with mod_admin_rest version [afc42d7](https://github.com/snowblindroan/mod_admin_rest/commit/afc42d70f0aceb2351a1bc786d61e3f4dbdfb948)
- 0.1:
- initial release
- works with mod_admin_rest version [afc42d7](https://github.com/snowblindroan/mod_admin_rest/commit/afc42d70f0aceb2351a1bc786d61e3f4dbdfb948)

View file

@ -16,15 +16,18 @@ $container[InternalApplicationError::class] = function ($c) {
};
// pages
$container[IndexAction::class] = function ($c) {
return new IndexAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
$container[HomeAction::class] = function ($c) {
return new HomeAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
};
$container[SignUpAction::class] = function ($c) {
return new SignUpAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
return new SignUpAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'), $c->get('router'));
};
$container[VerificationAction::class] = function ($c) {
return new VerificationAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
};
$container[DeleteAction::class] = function ($c) {
return new DeleteAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
};
// Routes
// error
@ -34,6 +37,7 @@ $app->get('/404', NotFoundAction::class)->setName('404');
$app->get('/500', InternalApplicationError::class)->setName('500');
// pages
$app->get('/', IndexAction::class)->setName('/');
$app->get('/', HomeAction::class)->setName('/');
$app->map(['GET', 'POST'], '/signup', SignUpAction::class)->setName('signup');
$app->get('/verification/{verificationCode}', VerificationAction::class)->setName('verification');
$app->map(['GET', 'POST'], '/delete', DeleteAction::class)->setName('delete');

View file

@ -1,6 +1,8 @@
# site settings
site_title=""
site_navbar_index_displayname="Sign Up"
site_navbar_home_displayname="Home"
site_navbar_signup_displayname="Sign up"
site_navbar_delete_displayname="Delete Account"
site_navbar_backlink_enabled="false" # enables a link in the navbar to go back to e.g. main server site
site_navbar_backlink_uri=""
site_navbar_backlink_displayname=""
@ -10,16 +12,21 @@ site_xmpp_server_displayname="jabber.server.org"
verification_cleanup_time="7 day"
# mod_admin_rest Settings
xmpp_curl_uri="/admin_rest" # uri to admin_rest
xmpp_curl_uri="/admin_rest" # full uri to admin_rest
xmpp_curl_auth_admin_username="" # configured in prosody lua file
xmpp_curl_auth_admin_password="" # configured in prosody lua file
# Mail Settings
# main
mail_host=""
mail_port="587"
mail_secure="tls"
mail_auth="true"
mail_auth=true
mail_username=""
mail_password=""
mail_from="webmaster@jabber.server.org"
mail_from_name="jabber.server.org"
# notification
mail_notify="true" # sends an email to mail_notify_to if a new user successfully verified their account
mail_notify_to=${mail_from} # defaults to sender mail, e.g. webmaster, maybe change this e.g. to "xx@xx"

View file

@ -1,3 +1,9 @@
# Home
home.title: Home
home.text: |
Hi,
welcome to the free jabber service %server%. Sign up now by clicking the "Sign up" button in the navigation bar.
# Sign up
sign.up.title: Sign Up
sign.up.flash.success: Signed up successfully. Check your inbox.
@ -13,7 +19,7 @@ sign.up.form.password: Password:
sign.up.form.password.placeholder: Password:
# Verification
verification.mail.subject: %server% jabber account verification
verification.mail.subject: %server%: jabber account verification
verification.mail.body: |
Hello %username%,
you've signed up for a jabber account on %server%.
@ -22,14 +28,29 @@ verification.code.invalid: Verification code %verificationCode% is not valid.
verification.flash.already_in_use_username: %username% is already in use.
verification.flash.success: Verification successful. You can now sign in to your newly created jabber account %username%@hoth.one.
verification.flash.unknown_error: Could not process sign up of %username%. Please contact administrator.
verification.mail.success.subject: %server% jabber account information
verification.mail.success.subject: %server%: jabber account information
verification.mail.success.body: |
Hello %username%,
you've verified your email address successfully and your jabber account on %server% has been created.
Your password is "%password%". Keep this mail safe!
Your password is "%password%".
If you wish to delete your account, use %deleteCode% on the website.
Keep this mail safe!
verification.mail.success.notify.subject: %server%: user verified their account
verification.mail.success.notify.body: A user (%email%) verified their account %username%@%server% successfully.
# Delete
delete.title: Delete Account
delete.form.username: Username:
delete.form.username.placeholder: username
delete.form.delete_code: Deletion Code:
delete.form.delete_code.placeholder: received via mail on sign up, 32 in length
delete.form.button: Delete
delete.flash.combination_not_found: Could not find a combination match for username and delete code.
delete.flash.success: Successfully deleted your account %username%@%server%.
delete.flash.unknown_error: Could not process deletion of %username%. Please contact administrator.
# Cleanup
cleanup.mail.subject: %server% jabber account verification expired
cleanup.mail.subject: %server%: jabber account verification expired
cleanup.mail.body: |
Hello %username%,
you've recently signed up for a jabber account on %server% but you did not verify your account within 7 days.
@ -42,6 +63,8 @@ log.verification.invalid: Tried to use code %verificationCode% but it is invalid
log.verification.sucess: %username% verified.
log.verification.unknown_error: Unknown error in XMPP Rest API.
log.verification.cleanup: %username% did not verify. Deleted.
log.delete.success: %username% deleted their account.
log.delete.unknown_error: Unknown error in XMPP Rest API.
# Error
error.401.title: 401

View file

@ -0,0 +1,35 @@
<?php
use Phpmig\Migration\Migration;
class UsersRegisteredTable extends Migration
{
public $tableName = 'users_registered'; // Table name
public $db;
/**
* Do the migration
*/
public function up()
{
$this->db->create($this->tableName, function($table) {
$table->string('username')->unique()->primary();
$table->string('delete_code', 64);
});
}
/**
* Undo the migration
*/
public function down()
{
$this->db->dropIfExists($this->tableName);
}
/**
* Init the migration
*/
public function init()
{
$this->db = $this->container['schema'];
}
}

View file

@ -0,0 +1,85 @@
<?php
use Curl\Curl;
use Psr\Log\LoggerInterface;
use Slim\Flash\Messages;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Views\Twig;
use Symfony\Component\Translation\Translator;
final class DeleteAction
{
private $view;
private $translator;
private $logger;
private $flash;
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
{
$this->view = $view;
$this->translator = $translator;
$this->logger = $logger;
$this->flash = $flash;
}
public function __invoke(Request $request, Response $response, $args)
{
$body = $request->getParsedBody();
if ($request->isPost()) {
// Form validation
$validator = new Validator();
$validator->filter_rules([
'username' => 'trim|sanitize_string',
'delete_code' => 'trim|sanitize_string',
]);
$validator->validation_rules([
'username' => 'required|alpha_numeric|max_len,64|min_len,3',
'delete_code' => 'required|exact_len,64',
]);
if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash);
return $response->withRedirect('/delete');
}
$username = $body['username'];
$deleteCode = $body['delete_code'];
// check if combination matches
$usersRegistered = UserRegistered::with([])->where('username', $username)->where('delete_code', $deleteCode)->get();
if (empty($usersRegistered) || $usersRegistered->count() == 0) {
$this->flash->addMessage('error', $this->translator->trans('delete.flash.combination_not_found'));
return $response->withRedirect('/delete');
} else {
$userRegistered = $usersRegistered->pop();
$curl = new Curl();
$curl->setBasicAuthentication(getenv('xmpp_curl_auth_admin_username'), getenv('xmpp_curl_auth_admin_password'));
$curl->delete(getenv('xmpp_curl_uri') . '/user/' . $username);
$curl->close();
if ($curl->http_status_code == 200) {
$userRegistered->delete();
$this->flash->addMessage('success', $this->translator->trans('delete.flash.success', ['%username%' => $username, '%server%' => getenv('site_xmpp_server_displayname')]));
$this->logger->info($this->translator->trans('log.delete.success', ['%username%' => $username]));
return $response->withRedirect('/');
} else {
$this->flash->addMessage('error', $this->translator->trans('delete.flash.unknown_error', ['%username%' => $username]));
$this->logger->warning($this->translator->trans('log.delete.flash.unknown_error'), ['code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('/delete');
}
}
}
// render GET
$this->view->render($response, 'delete.twig', [
'title' => $this->translator->trans('delete.title'),
]);
return $response;
}
}

View file

@ -7,7 +7,7 @@ use Slim\Http\Request;
use Slim\Http\Response;
use Symfony\Component\Translation\Translator;
final class IndexAction
final class HomeAction
{
private $view;
private $translator;
@ -24,6 +24,9 @@ final class IndexAction
public function __invoke(Request $request, Response $response, $args)
{
return $response->withRedirect('/signup');
return $this->view->render($response, 'home.twig', [
'title' => $this->translator->trans('home.title'),
'content' => $this->translator->trans('home.text', ['%server%' => getenv('site_xmpp_server_displayname')])
]);
}
}

View file

@ -2,6 +2,7 @@
use Curl\Curl;
use Slim\Flash\Messages;
use Slim\Interfaces\RouterInterface;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Slim\Http\Request;
@ -14,13 +15,15 @@ final class SignUpAction
private $translator;
private $logger;
private $flash;
private $router;
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator)
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator, RouterInterface $router)
{
$this->view = $view;
$this->translator = $translator;
$this->logger = $logger;
$this->flash = $flash;
$this->router = $router;
}
public function __invoke(Request $request, Response $response, $args)
@ -102,7 +105,11 @@ final class SignUpAction
$mailer->addAddress($userAwaiting->email);
$verificationLink = $request->getUri()->getScheme() . '://' . $request->getUri()->getHost() . (!empty($p = $request->getUri()->getPort()) ? ':' .$p : '') .'/verification/' . $userAwaiting->verification_code;
$verificationLink = $request->getUri()->getScheme();
$verificationLink .= '://';
$verificationLink .= $request->getUri()->getHost();
$verificationLink .= (!empty($p = $request->getUri()->getPort()) ? ':' .$p : '');
$verificationLink .= $this->router->pathFor('verification', ['verificationCode' => $userAwaiting->verification_code]);
$mailer->Subject = $this->translator->trans('verification.mail.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
$mailer->Body = $this->translator->trans('verification.mail.body', ['%username%' => $userAwaiting->username, '%verificationLink%' => $verificationLink, '%server%' => getenv('site_xmpp_server_displayname')]);

View file

@ -52,32 +52,53 @@ final class VerificationAction
$this->flash->addMessage('success', $this->translator->trans('verification.flash.success', ['%username%' => $userAwaiting->username]));
$this->logger->info($this->translator->trans('log.verification.sucess', ['%username%' => $userAwaiting->username]));
if (getenv('mail_notify') == true) {
$mailer = new PHPMailer();
$mailer->CharSet = 'UTF-8';
$mailer->ContentType = 'text/plain';
$mailer->isSMTP();
$mailer->SMTPSecure = getenv('mail_secure');
$mailer->SMTPAuth = getenv('mail_auth');
$mailer->Host = getenv('mail_host');
$mailer->Port = getenv('mail_port');
$mailer->Username = getenv('mail_username');
$mailer->Password = getenv('mail_password');
$mailer->From = getenv('mail_from');
$mailer->FromName = getenv('mail_from_name');
$mailer->addAddress(getenv('mail_notify_to'));
$mailer->Subject = $this->translator->trans('verification.mail.success.notify.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
$mailer->Body = $this->translator->trans('verification.mail.success.notify.body', ['%username%' => $userAwaiting->username, '%server%' => getenv('site_xmpp_server_displayname'), '%email%' => $userAwaiting->email]);
$mailer->send();
}
$userRegistered = new UserRegistered();
$userRegistered->username = $userAwaiting->username;
$userRegistered->delete_code = hash('sha256', (time() . $userAwaiting->username . rand()));
$userRegistered->save();
$mailer = new PHPMailer();
$mailer->CharSet = 'UTF-8';
$mailer->ContentType = 'text/plain';
$mailer->isSMTP();
$mailer->SMTPSecure = getenv('mail_secure');
$mailer->SMTPAuth = getenv('mail_auth');
$mailer->Host = getenv('mail_host');
$mailer->Port = getenv('mail_port');
$mailer->Username = getenv('mail_username');
$mailer->Password = getenv('mail_password');
$mailer->From = getenv('mail_from');
$mailer->FromName = getenv('mail_from_name');
$mailer->addAddress($userAwaiting->email);
$mailer->Subject = $this->translator->trans('verification.mail.success.subject', ['%server%' => getenv('site_xmpp_server_displayname')]);
$mailer->Body = $this->translator->trans('verification.mail.success.body', ['%username%' => $userAwaiting->username, '%server%' => getenv('site_xmpp_server_displayname'), '%password%' => $userAwaiting->password]);
$mailer->Body = $this->translator->trans('verification.mail.success.body', ['%username%' => $userAwaiting->username, '%server%' => getenv('site_xmpp_server_displayname'), '%password%' => $userAwaiting->password, '%deleteCode%' => $userRegistered->delete_code]);
$mailer->send();
$userAwaiting->delete();
return $response->withRedirect('/signup');
return $response->withRedirect('/');
} else {
$this->flash->addMessage('error', $this->translator->trans('verification.flash.unknown_error', ['%username%' => $userAwaiting->username]));
$this->logger->warning($this->translator->trans('verification.flash.unknown_error'), ['code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('/signup');
return $response->withRedirect('/');
}
}
}

View file

@ -0,0 +1,9 @@
<?php
use Illuminate\Database\Eloquent\Model;
class UserRegistered extends Model
{
public $table = 'users_registered';
public $primaryKey = 'username';
public $timestamps = false;
}

View file

@ -93,86 +93,86 @@ class Validator extends GUMP
switch ($e['rule']) {
case 'mismatch' :
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_required':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_json_string':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_email':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_max_len':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_min_len':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_exact_len':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_alpha':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_alpha_numeric':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_alpha_dash':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_numeric':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_integer':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_boolean':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_float':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_url':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_url_exists':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_ip':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_cc':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_valid_name':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_contains':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => implode(', ', $param)]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => implode(', ', $param)]);
break;
case 'validate_street_address':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_date':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_min_numeric':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_max_numeric':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_equals':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
case 'validate_set_min_len':
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
break;
default:
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param' => $param]);
$resp[$field] = $this->translator->trans($e['rule'], ['%field%' => $field, '%param%' => $param]);
}
}

View file

@ -23,9 +23,11 @@
<!-- Navigation -->
<nav id="nav-left">
<div id="nav-list">
<a href="/">{{ getenv('site_navbar_index_displayname') }}</a>
<a href="{{ base_url() }}{{ path_for('/') }}">{{ getenv('site_navbar_home_displayname') }}</a>
<a href="{{ base_url() }}{{ path_for('signup') }}">{{ getenv('site_navbar_signup_displayname') }}</a>
<a href="{{ base_url() }}{{ path_for('delete') }}">{{ getenv('site_navbar_delete_displayname') }}</a>
{% if getenv('site_navbar_backlink_enabled') %}
{% if getenv('site_navbar_backlink_enabled') == 'true' %}
<br />
<br />
<a href="{{ getenv('site_navbar_backlink_uri') }}">{{ getenv('site_navbar_backlink_displayname') }}</a>
@ -97,9 +99,9 @@
<script src="{{ base_url() }}/js/main.js"></script>
<script>
$(".alert-danger" ).delay(20000).fadeOut(300);
$(".alert-success" ).delay(5000).fadeOut(300);
$(".alert-info" ).delay(5000).fadeOut(300);
$(".alert-danger" ).delay(30000).fadeOut(300);
$(".alert-success" ).delay(10000).fadeOut(300);
$(".alert-info" ).delay(10000).fadeOut(300);
</script>
</div>

15
src/View/delete.twig Normal file
View file

@ -0,0 +1,15 @@
{% extends 'base.twig' %}
{% block content %}
<form class="" role="form" name="delete" id="delete" method="post">
<h1>{{ title }}</h1>
<label for="username">{% trans %}delete.form.username{% endtrans %}</label><br/>
<input type="text" id="username" name="username" class="" placeholder="{% trans %}delete.form.username.placeholder{% endtrans %}" value="" autofocus required> @{{ getenv('site_xmpp_server_displayname') }}
<br/><br/>
<label for="deletion_code">{% trans %}delete.form.delete_code{% endtrans %}</label><br/>
<input type="text" id="delete_code" name="delete_code" class="" placeholder="{% trans %}delete.form.delete_code.placeholder{% endtrans %}" value="" autofocus required>
<br/><br/>
<input class="" type="submit" name="delete" value="{% trans %}delete.form.button{% endtrans %}"/>
</form>
{% endblock %}

8
src/View/home.twig Normal file
View file

@ -0,0 +1,8 @@
{% extends 'base.twig' %}
{% block content %}
<h1 class="header">{{ title }}</h1>
{{ content|nl2br }}
{% endblock %}

View file

@ -3,6 +3,7 @@
{% block content %}
<form class="" role="form" name="register" id="register" method="post">
<h1>{{ title }}</h1>
<label for="username">{% trans %}sign.up.form.username{% endtrans %}</label><br/>
<input type="text" id="username" name="username" class="" placeholder="{% trans %}sign.up.form.username.placeholder{% endtrans %}" value="" autofocus required> @{{ getenv('site_xmpp_server_displayname') }}
<br/><br/>