Use 4 spaces as tab and reformat

This commit is contained in:
Varakh 2021-05-21 01:21:43 +02:00
parent b541d33671
commit 70e48783b8
28 changed files with 382 additions and 328 deletions

6
.editorconfig Normal file
View file

@ -0,0 +1,6 @@
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
indent_size = 4

View file

@ -1,6 +1,7 @@
# README #
A simple webinterface for users. This app uses [`admin_rest`](https://github.com/snowblindroan/mod_admin_rest) module of prosody. So [prosody.im](http://prosody.im) and this module are hard dependencies. The interface allows users
A simple webinterface for users. This app uses [`admin_rest`](https://github.com/snowblindroan/mod_admin_rest) module of
prosody. So [prosody.im](http://prosody.im) and this module are hard dependencies. The interface allows users
* to have two step verification (as an alternative to the integrated `register_web` module),
* to delete of their accounts and
@ -38,7 +39,8 @@ as dependencies.
## Deployment ##
* Set up a cron job using `php projectRootDir/bin/UsersAwaitingVerificationCleanUpCronJob.php` to clean up users who signed up but did not verify their account periodically.
* Set up a cron job using `php projectRootDir/bin/UsersAwaitingVerificationCleanUpCronJob.php` to clean up users who
signed up but did not verify their account periodically.
* Point your document root to `public/`.
* Example nginx conf:
@ -75,13 +77,17 @@ You should be able to set a very strict Content-Security-Policy.
* look into Changelog for major changes
## Developers ##
* start server with `php -S localhost:8080 -t public public/index.php`
* point browser to [localhost:8080](http://localhost:8080) to have a preview
## Translations ##
This app uses Symfony Translator. It's bootstraped in `Util\BootstrapHelper` and locales are placed under `data/locale/`. Adjust to your needs or help translating.
This app uses Symfony Translator. It's bootstraped in `Util\BootstrapHelper` and locales are placed under `data/locale/`
. Adjust to your needs or help translating.
## Changelog ##
- 0.3.0.1
- Remove cookie consent as session cookies should be allowed because they provide core functionality
- Adjust `legal.example.md` and add `PHPSESSID`
@ -120,7 +126,9 @@ This app uses Symfony Translator. It's bootstraped in `Util\BootstrapHelper` and
- added admin notifications
- added possibility 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)
- 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)
- works with mod_admin_rest
version [afc42d7](https://github.com/snowblindroan/mod_admin_rest/commit/afc42d70f0aceb2351a1bc786d61e3f4dbdfb948)

View file

@ -9,7 +9,7 @@ class Config
// no need to change anything here
'db_settings' => [
'driver' => 'sqlite',
'database' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'db.sqlite',
'database' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'db.sqlite',
'charset' => 'utf8',
'collation' => 'utf8_unicode_ci',
'prefix' => '',
@ -21,13 +21,13 @@ class Config
],
'twig_settings' => [
'twig_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'View',
'twig_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'View',
'twig_cache_dir' => false,
//'twig_cache_dir' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'src'. DIRECTORY_SEPARATOR .'cache',
],
'logger_settings' => [
'path' => __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'log'. DIRECTORY_SEPARATOR .'application.log',
'path' => __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'log' . DIRECTORY_SEPARATOR . 'application.log',
],
];
}

View file

@ -13,19 +13,28 @@ Contact data
## Cookies
This website uses small files called cookies to help the operator customise your experience. Cookies are small text files that are stored by the browser on your device. They allow websites to store things like user preferences. Functionality might be affected if you disable cookies for this website.
This website uses small files called cookies to help the operator customise your experience. Cookies are small text
files that are stored by the browser on your device. They allow websites to store things like user preferences.
Functionality might be affected if you disable cookies for this website.
These cookies are essential to the proper functioning of our website and enable you to use its features, such as accessing secure areas of the site. Without these cookies, you will not be able to perform core site functions such as logging in.
These cookies are essential to the proper functioning of our website and enable you to use its features, such as
accessing secure areas of the site. Without these cookies, you will not be able to perform core site functions such as
logging in.
| Cookiename | Provider | Purpose |
|:------------------:|:-----------:|:---------|
| `PHPSESSID` | Provider | Creates a unique session for your device, allowing a platform for login. No personal or device information is collected or stored. If you login, you will be given access to parts of the site for registered members. If you close your browser or end your browser session, this cookie will be deleted automatically. |
## Registration
Registration: An email is stored (maximal: 7 days, typical: deleted after verification process is completed) and credentials (minimal: stored as long as the account exists, typical: check user JID against well-known spammer patterns) are stored.
Registration: An email is stored (maximal: 7 days, typical: deleted after verification process is completed) and
credentials (minimal: stored as long as the account exists, typical: check user JID against well-known spammer patterns)
are stored.
## Log
Access logs are not stored except for fixing bugs in case of an error or an attack on this service. Logs will be removed once the [log rotates](https://en.wikipedia.org/wiki/Log_rotation) within 7 days.
Access logs are not stored except for fixing bugs in case of an error or an attack on this service. Logs will be removed
once the [log rotates](https://en.wikipedia.org/wiki/Log_rotation) within 7 days.
# Service

View file

@ -1,6 +1,6 @@
<?php
use \Phpmig\Adapter;
use Phpmig\Adapter;
$container = new ArrayObject();
$container['env'] = BootstrapHelper::bootEnvironment();
@ -8,8 +8,8 @@ $container['db'] = BootstrapHelper::bootDatabase();
$container['phpmig.adapter'] = new Phpmig\Adapter\PDO\Sql($container['db']->getConnection()->getPdo(), 'migrations');
$container['phpmig.migrations_template_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'phpmig_template.php';
$container['phpmig.migrations_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..'. DIRECTORY_SEPARATOR .'data'. DIRECTORY_SEPARATOR .'migrations';
$container['phpmig.migrations_template_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'phpmig_template.php';
$container['phpmig.migrations_path'] = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'data' . DIRECTORY_SEPARATOR . 'migrations';
$container['schema'] = $container['db']->schema();

View file

@ -1,4 +1,5 @@
<?php
use Phpmig\Migration\Migration;
class UsersAwaitingVerificationTable extends Migration
@ -11,7 +12,7 @@ class UsersAwaitingVerificationTable extends Migration
*/
public function up()
{
$this->db->create($this->tableName, function($table) {
$this->db->create($this->tableName, function ($table) {
$table->increments('id');
$table->string('username');
$table->string('email')->unique();

View file

@ -1,4 +1,5 @@
<?php
use Phpmig\Migration\Migration;
class UsersRegisteredTable extends Migration
@ -11,7 +12,7 @@ class UsersRegisteredTable extends Migration
*/
public function up()
{
$this->db->create($this->tableName, function($table) {
$this->db->create($this->tableName, function ($table) {
$table->string('username')->unique()->primary();
$table->string('delete_code', 64);
});

View file

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

View file

@ -1,10 +1,10 @@
<?php
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 ForbiddenAction

View file

@ -1,10 +1,10 @@
<?php
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 HomeAction

View file

@ -1,10 +1,10 @@
<?php
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 InternalApplicationError

View file

@ -1,11 +1,11 @@
<?php
use JeremyKendall\Slim\Auth\Authenticator;
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 LoginAction

View file

@ -1,11 +1,11 @@
<?php
use JeremyKendall\Slim\Auth\Authenticator;
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 LogoutAction

View file

@ -1,10 +1,10 @@
<?php
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 NotAuthorizedAction

View file

@ -1,10 +1,10 @@
<?php
use Slim\Flash\Messages;
use Slim\Views\Twig;
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 NotFoundAction

View file

@ -1,12 +1,12 @@
<?php
use Curl\Curl;
use Slim\Flash\Messages;
use Slim\Interfaces\RouterInterface;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Slim\Flash\Messages;
use Slim\Http\Request;
use Slim\Http\Response;
use Slim\Interfaces\RouterInterface;
use Slim\Views\Twig;
use Symfony\Component\Translation\Translator;
final class SignUpAction
@ -109,7 +109,7 @@ final class SignUpAction
$verificationLink = $request->getUri()->getScheme();
$verificationLink .= '://';
$verificationLink .= $request->getUri()->getHost();
$verificationLink .= (!empty($p = $request->getUri()->getPort()) ? ':' .$p : '');
$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')]);

View file

@ -1,4 +1,5 @@
<?php
use Illuminate\Database\Eloquent\Model;
class UserAwaitingVerification extends Model

View file

@ -1,4 +1,5 @@
<?php
use Illuminate\Database\Eloquent\Model;
class UserRegistered extends Model

View file

@ -13,59 +13,110 @@ class ValidationHelper extends GUMP
$this->translator = BootstrapHelper::bootTranslator();
}
/** Validates if $field content is equal to $param
* @param $field
* @param $input
* @param $param
* @return bool
/**
* Perform data validation against the provided ruleset
*
* Arrays as FIELDS are added here as a custom feature
*
* @access public
* @param mixed $input
* @param array $ruleset
* @return mixed
* @throws \Exception
*/
protected function validate_equals($field, $input, $param)
public function validate(array $input, array $ruleset)
{
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
$this->errors = [];
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
return $err;
foreach ($ruleset as $field => $rules) {
#if(!array_key_exists($field, $input))
#{
# continue;
#}
$rules = explode('|', $rules);
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
foreach ($rules as $rule) {
$method = NULL;
$param = NULL;
if (strstr($rule, ',') !== false) // has params
{
$rule = explode(',', $rule);
$method = 'validate_' . $rule[0];
$param = $rule[1];
$rule = $rule[0];
} else {
$method = 'validate_' . $rule;
}
if ($input[$field] != $param || $input[$field] !== $param) {
return $err;
// array required
if ($rule === "required" && !isset($input[$field])) {
$result = $this->$method($field, $input, $param);
$this->errors[] = $result;
return;
}
return true;
if (is_callable([$this, $method])) {
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
} else {
if (isset(self::$validation_methods[$rule])) {
if (isset($input[$field])) {
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
}
} else {
throw new \Exception("Validator method '$method' does not exist.");
}
}
}
}
}
return (count($this->errors) > 0) ? $this->errors : true;
}
public function filter_upper($value, $param = NULL)
{
return strtoupper($value);
}
public function filter_lower($value, $param = NULL)
{
return strtolower($value);
}
/**
* Validates if array has min size, defaults to size = 1
* @param $field
* @param $input
* @param null $param
* @return array|bool
* Converts all error array into a single string
* @return void
*/
protected function validate_set_min_len($field, $input, $param = NULL)
public function addErrorsToFlashMessage($flash)
{
$errors = $this->get_errors_array(true);
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!is_array($input[$field])) {
return $err;
if (!empty($errors)) {
foreach ($errors as $error) {
$flash->addMessage('error', $error);
}
}
// default value
if (empty($param)) $param = 1;
if (count($input[$field]) < $param) return $err;
return true;
}
/**
@ -179,109 +230,58 @@ class ValidationHelper extends GUMP
return $resp;
}
/**
* Perform data validation against the provided ruleset
*
* Arrays as FIELDS are added here as a custom feature
*
* @access public
* @param mixed $input
* @param array $ruleset
* @return mixed
* @throws \Exception
/** Validates if $field content is equal to $param
* @param $field
* @param $input
* @param $param
* @return bool
*/
public function validate(array $input, array $ruleset)
protected function validate_equals($field, $input, $param)
{
$this->errors = [];
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
foreach ($ruleset as $field => $rules) {
#if(!array_key_exists($field, $input))
#{
# continue;
#}
$rules = explode('|', $rules);
if (in_array("required", $rules) || (isset($input[$field]) && (is_array($input[$field]) || trim($input[$field]) != ''))) {
foreach ($rules as $rule) {
$method = NULL;
$param = NULL;
if (strstr($rule, ',') !== false) // has params
{
$rule = explode(',', $rule);
$method = 'validate_' . $rule[0];
$param = $rule[1];
$rule = $rule[0];
} else {
$method = 'validate_' . $rule;
if (!isset($input[$field]) || empty($input[$field]) || empty($param) || !isset($param)) {
return $err;
}
// array required
if ($rule === "required" && !isset($input[$field])) {
$result = $this->$method($field, $input, $param);
$this->errors[] = $result;
return;
if ($input[$field] != $param || $input[$field] !== $param) {
return $err;
}
if (is_callable([$this, $method])) {
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
} else {
if (isset(self::$validation_methods[$rule])) {
if (isset($input[$field])) {
$result = call_user_func(self::$validation_methods[$rule], $field, $input, $param);
$result = $this->$method($field, $input, $param);
if (is_array($result)) // Validation Failed
{
$this->errors[] = $result;
return $this->errors;
}
}
} else {
throw new \Exception("Validator method '$method' does not exist.");
}
}
}
}
}
return (count($this->errors) > 0) ? $this->errors : true;
}
public function filter_upper($value, $param = NULL)
{
return strtoupper($value);
}
public function filter_lower($value, $param = NULL)
{
return strtolower($value);
return true;
}
/**
* Converts all error array into a single string
* @return void
* Validates if array has min size, defaults to size = 1
* @param $field
* @param $input
* @param null $param
* @return array|bool
*/
public function addErrorsToFlashMessage($flash)
protected function validate_set_min_len($field, $input, $param = NULL)
{
$errors = $this->get_errors_array(true);
if (!empty($errors)) {
foreach ($errors as $error) {
$flash->addMessage('error', $error);
}
$err = [
'field' => $field,
'value' => $input[$field],
'rule' => __FUNCTION__,
'param' => $param,
];
if (!is_array($input[$field])) {
return $err;
}
// default value
if (empty($param)) $param = 1;
if (count($input[$field]) < $param) return $err;
return true;
}
}

View file

@ -8,7 +8,9 @@
<div class="form-group row">
<label class="col-2 col-form-label" for="username">{% trans %}delete.form.username{% endtrans %}</label>
<div class="col-7">
<input type="text" id="username" name="username" class="form-control" placeholder="{% trans %}delete.form.username.placeholder{% endtrans %}" value="" autofocus required>
<input type="text" id="username" name="username" class="form-control"
placeholder="{% trans %}delete.form.username.placeholder{% endtrans %}" value="" autofocus
required>
</div>
<div class="col-3">
@{{ getenv('site_xmpp_server_displayname') }}
@ -16,14 +18,19 @@
</div>
<div class="form-group row">
<label class="col-2 col-form-label" for="delete_code">{% trans %}delete.form.delete_code{% endtrans %}</label>
<label class="col-2 col-form-label"
for="delete_code">{% trans %}delete.form.delete_code{% endtrans %}</label>
<div class="col-10">
<input aria-describedby="help" type="text" id="delete_code" name="delete_code" class="form-control" placeholder="{% trans %}delete.form.delete_code.placeholder{% endtrans %}" value="" autofocus required>
<small id="help" class="form-text text-muted">{% trans %}delete.form.delete_code.help{% endtrans %}</small>
<input aria-describedby="help" type="text" id="delete_code" name="delete_code" class="form-control"
placeholder="{% trans %}delete.form.delete_code.placeholder{% endtrans %}" value="" autofocus
required>
<small id="help"
class="form-text text-muted">{% trans %}delete.form.delete_code.help{% endtrans %}</small>
</div>
</div>
<br/>
<input class="btn btn-primary" type="submit" name="delete_button" value="{% trans %}delete.form.button{% endtrans %}"/>
<input class="btn btn-primary" type="submit" name="delete_button"
value="{% trans %}delete.form.button{% endtrans %}"/>
</div>
</form>
{% endblock %}

View file

@ -8,7 +8,9 @@
<div class="form-group row">
<label class="col-2 col-form-label" for="username">{% trans %}login.form.username{% endtrans %}</label>
<div class="col-7">
<input class="form-control" type="text" id="username" name="username" placeholder="{% trans %}login.form.username.placeholder{% endtrans %}" value="" autofocus required>
<input class="form-control" type="text" id="username" name="username"
placeholder="{% trans %}login.form.username.placeholder{% endtrans %}" value="" autofocus
required>
</div>
<div class="col-3">
@{{ getenv('site_xmpp_server_displayname') }}
@ -18,11 +20,13 @@
<div class="form-group row">
<label class="col-2 col-form-label" for="username">{% trans %}login.form.password{% endtrans %}</label>
<div class="col-10">
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>
<input type="password" id="password" name="password" class="form-control"
placeholder="{% trans %}login.form.password.placeholder{% endtrans %}" required>
</div>
</div>
<br/>
<input class="btn btn-primary" type="submit" name="login" value="{% trans %}login.form.button{% endtrans %}"/>
<input class="btn btn-primary" type="submit" name="login"
value="{% trans %}login.form.button{% endtrans %}"/>
</div>
</form>
{% endblock %}

View file

@ -6,19 +6,25 @@
<h1>{{ title }}</h1>
<div class="form-group row">
<label class="col-2 col-form-label" for="password">{% trans %}password.form.password{% endtrans %}</label>
<label class="col-2 col-form-label"
for="password">{% trans %}password.form.password{% endtrans %}</label>
<div class="col-10">
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}password.form.password.placeholder{% endtrans %}" required>
<input type="password" id="password" name="password" class="form-control"
placeholder="{% trans %}password.form.password.placeholder{% endtrans %}" required>
</div>
</div>
<div class="form-group row">
<label class="col-2 col-form-label" for="password_confirmation">{% trans %}password.form.password_confirmation{% endtrans %}</label>
<label class="col-2 col-form-label"
for="password_confirmation">{% trans %}password.form.password_confirmation{% endtrans %}</label>
<div class="col-10">
<input type="password" id="password_confirmation" name="password_confirmation" class="form-control" placeholder="{% trans %}password.form.password_confirmation.placeholder{% endtrans %}" required>
<input type="password" id="password_confirmation" name="password_confirmation" class="form-control"
placeholder="{% trans %}password.form.password_confirmation.placeholder{% endtrans %}"
required>
</div>
</div>
<br/>
<input class="btn btn-primary" type="submit" name="password_change" value="{% trans %}password.form.button{% endtrans %}"/>
<input class="btn btn-primary" type="submit" name="password_change"
value="{% trans %}password.form.button{% endtrans %}"/>
</div>
</form>
{% endblock %}

View file

@ -6,10 +6,13 @@
<h1>{{ title }}</h1>
<div class="form-group row">
<label class="col-2 col-form-label" for="username">{% trans %}sign.up.form.username{% endtrans %}</label>
<label class="col-2 col-form-label"
for="username">{% trans %}sign.up.form.username{% endtrans %}</label>
<div class="col-7">
<input type="text" id="username" name="username" class="form-control" placeholder="{% trans %}sign.up.form.username.placeholder{% endtrans %}" value="" autofocus required>
<input type="text" id="username" name="username" class="form-control"
placeholder="{% trans %}sign.up.form.username.placeholder{% endtrans %}" value="" autofocus
required>
</div>
<div class="col-3">
@{{ getenv('site_xmpp_server_displayname') }}
@ -20,25 +23,32 @@
<label class="col-2 col-form-label" for="email">{% trans %}sign.up.form.email{% endtrans %}</label>
<div class="col-10">
<input aria-describedby="help" type="text" id="email" name="email" class="form-control" placeholder="{% trans %}sign.up.form.email.placeholder{% endtrans %}" value="" autofocus required>
<small id="help" class="form-text text-muted">{% trans %}sign.up.form.email.help{% endtrans %}</small>
<input aria-describedby="help" type="text" id="email" name="email" class="form-control"
placeholder="{% trans %}sign.up.form.email.placeholder{% endtrans %}" value="" autofocus
required>
<small id="help"
class="form-text text-muted">{% trans %}sign.up.form.email.help{% endtrans %}</small>
</div>
</div>
<div class="form-group row">
<label class="col-2 col-form-label" for="password">{% trans %}sign.up.form.password{% endtrans %}</label>
<label class="col-2 col-form-label"
for="password">{% trans %}sign.up.form.password{% endtrans %}</label>
<div class="col-10">
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}sign.up.form.password.placeholder{% endtrans %}" required>
<input type="password" id="password" name="password" class="form-control"
placeholder="{% trans %}sign.up.form.password.placeholder{% endtrans %}" required>
</div>
</div>
<div class="form-check">
<input type="checkbox" id="legal" name="legal" class="form-check-input" />
<label class="form-check-label" for="legal"><a href="#" data-target="#legalModal" data-toggle="modal">{% trans %}legalmodal.open{% endtrans %}</a></label>
<input type="checkbox" id="legal" name="legal" class="form-check-input"/>
<label class="form-check-label" for="legal"><a href="#" data-target="#legalModal"
data-toggle="modal">{% trans %}legalmodal.open{% endtrans %}</a></label>
</div>
<br />
<input class="btn btn-primary" type="submit" name="signup_button" value="{% trans %}sign.up.form.button{% endtrans %}"/>
<br/>
<input class="btn btn-primary" type="submit" name="signup_button"
value="{% trans %}sign.up.form.button{% endtrans %}"/>
</div>
</form>