simple account management with deletion

This commit is contained in:
Alexander Schäferdiek 2016-07-11 16:57:48 +02:00
parent 1840592c1b
commit 7f602781d3
22 changed files with 521 additions and 24 deletions

View file

@ -1,10 +1,15 @@
# README #
A simple WebUI for [`admin_rest`](https://github.com/snowblindroan/mod_admin_rest) module of prosody allowing 2 step verification of new user accounts (as an alternative to the integrated `register_web` module).
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) is a hard dependency. The interface allows users
* to have two step verification (as an alternative to the integrated `register_web` module),
* to delete of their accounts and
* to change their password.
This app uses
* Slim Version 3
* Slim Auth
* Eloquent ORM
* PHPMigration
* GUMP Validation
@ -70,6 +75,11 @@ as dependencies.
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.2:
- add authentication after sign up
- only logged in users can delete their account (with the help of the token)
- only logged in users can change their account password
- 0.1.1:
- updated readme and `env.example`
- fix some language validator inconsistencies

View file

@ -9,7 +9,7 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . '../vendor/autoload.php';
*/
$env = EnvironmentHelper::getAppEnvironment();
$config = Config::$CONFIG;
$db = DatabaseHelper::bootORM();
$db = DatabaseHelper::getAppDatabase();
$translator = TranslationHelper::getAppTranslator();
$logger = LoggerHelper::getAppLogger();

View file

@ -11,7 +11,9 @@
"davedevelopment/phpmig": "^1.2",
"symfony/translation": "^3.1",
"symfony/twig-bridge": "^3.1",
"vlucas/phpdotenv": "^2.3"
"vlucas/phpdotenv": "^2.3",
"fabiang/xmpp": "^0.6.1",
"jeremykendall/slim-auth": "dev-slim-3.x"
},
"config": {
"bin-dir": "bin/"

89
config/ACL.php Normal file
View file

@ -0,0 +1,89 @@
<?php
class ACL extends \Zend\Permissions\Acl\Acl
{
public static $ROLE_GUEST = 'guest';
public static $ROLE_MEMBER = 'member';
public static $ROLE_ADMIN = 'admin';
public static $ACL_RESOURCES = [
'/401',
'/403',
'/404',
'/500',
'/',
'/delete',
'/password',
'/verification/{verificationCode}',
'/login',
'/logout',
'/signup',
];
public static $ACL_ALLOWS = [
'admin' => [''],
'member' => [
'/',
'/delete',
'/password',
'/logout',
],
'guest' => [
'/login',
'/signup',
'/verification/{verificationCode}',
'/',
'/401',
'/403',
'/404',
'/500',
],
];
public static $ACL_DENIES = [
'admin' => ['/login', '/signup', '/verification/{verificationCode}'],
'member' => ['/login', '/signup', '/verification/{verificationCode}'],
'guest' => []
];
public function __construct()
{
$res = self::$ACL_RESOURCES;
$allows = self::$ACL_ALLOWS;
$denies = self::$ACL_DENIES;
// roles
$this->addRole(self::$ROLE_GUEST);
$this->addRole(self::$ROLE_MEMBER, self::$ROLE_GUEST);
$this->addRole(self::$ROLE_ADMIN);
// resource
foreach ($res as $resource) {
$this->addResource($resource);
}
// allows
foreach ($allows as $role => $paths) {
foreach ($paths as $path) {
if (empty($path) || $path === '') {
$this->allow($role);
} else {
$this->allow($role, $path);
}
}
}
// denies
foreach ($denies as $role => $paths) {
foreach ($paths as $path) {
if (empty($path) || $path === '') {
$this->deny($role);
} else {
$this->deny($role, $path);
}
}
}
}
}

View file

@ -25,9 +25,18 @@ $container[SignUpAction::class] = function ($c) {
$container[VerificationAction::class] = function ($c) {
return new VerificationAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
};
$container[LoginAction::class] = function ($c) {
return new LoginAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'), $c->get('authenticator'));
};
$container[LogoutAction::class] = function ($c) {
return new LogoutAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'), $c->get('authenticator'));
};
$container[DeleteAction::class] = function ($c) {
return new DeleteAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
};
$container[PasswordAction::class] = function ($c) {
return new PasswordAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'), $c->get('authenticator'));
};
// Routes
// error
@ -40,4 +49,7 @@ $app->get('/500', InternalApplicationError::class)->setName('500');
$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'], '/login', LoginAction::class)->setName('login');
$app->get('/logout', LogoutAction::class)->setName('logout');
$app->map(['GET', 'POST'], '/delete', DeleteAction::class)->setName('delete');
$app->map(['GET', 'POST'], '/password', PasswordAction::class)->setName('password');

View file

@ -1,8 +1,11 @@
# site settings
site_title=""
site_title="hoth.one"
site_navbar_home_displayname="Home"
site_navbar_signup_displayname="Sign up"
site_navbar_password_displayname="Change password"
site_navbar_delete_displayname="Delete Account"
site_navbar_login_displayname="Login"
site_navbar_logout_displayname="Logout"
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=""
@ -11,6 +14,11 @@ site_xmpp_server_displayname="jabber.server.org"
# verification_timeout and non-verified users will be deleted
verification_cleanup_time="7 day"
# xmpp Settings
xmpp_host="hoth.one" # hostname to connect
xmpp_port="5222" # port to connect, defaults to 5222
xmpp_connection_type="tcp" # defaults to tcp
# mod_admin_rest Settings
xmpp_curl_uri="/admin_rest" # full uri to admin_rest
xmpp_curl_auth_admin_username="" # configured in prosody lua file

View file

@ -4,7 +4,7 @@ use \Phpmig\Adapter;
$container = new ArrayObject();
$container['env'] = EnvironmentHelper::getAppEnvironment();
$container['db'] = DatabaseHelper::bootORM();
$container['db'] = DatabaseHelper::getAppDatabase();
$container['phpmig.adapter'] = new Phpmig\Adapter\PDO\Sql($container['db']->getConnection()->getPdo(), 'migrations');

View file

@ -49,6 +49,32 @@ delete.flash.combination_not_found: Could not find a combination match for usern
delete.flash.success: Successfully deleted your account %username%@%server%.
delete.flash.unknown_error: Could not process deletion of %username%. Please contact administrator.
# Password
password.title: Change password
password.form.button: Change password
password.form.password: Password:
password.form.password.placeholder: password
password.form.password_confirmation: Password confirmation:
password.form.password_confirmation.placeholder: password confirmation
password.flash.not_the_same: Passwords do not match.
password.flash.success: Successfully changed your password.
password.flash.unknown_error: Could not process password change of %username%. Please contact administrator.
# Login
logged.in.site: Logged in as %username%@%server%
login.title: Login
login.flash.success: Logged in as %username%@%server% successfully.
login.flash.wrong_credentials: Cannot login. Wrong credentials.
login.form.button: Login
login.form.username: Username:
login.form.username.placeholder: username
login.form.password: Password:
login.form.password.placeholder: Password:
# Logout
logout.title: Logout
logout.flash.success: Logged out successfully.
# Cleanup
cleanup.mail.subject: %server%: jabber account verification expired
cleanup.mail.body: |
@ -65,6 +91,10 @@ 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.
log.login: %username% logged in.
log.logout: %username% logged out.
log.password.success: %username% changed their password.
log.password.unknown_error: Unknown error in XMPP Rest API.
# Error
error.401.title: 401

View file

@ -1,5 +1,7 @@
<?php
use Auth\XmppAdapter;
use Auth\XmppValidator;
use Slim\Http\Request;
use Slim\Http\Response;
@ -31,7 +33,7 @@ $container['config'] = function() {
};
// Database
$capsule = DatabaseHelper::bootORM();
$capsule = DatabaseHelper::getAppDatabase();
$container['db'] = function () use ($capsule) {
return $capsule;
};
@ -48,6 +50,19 @@ $container['logger'] = function () {
return $logger;
};
// Auth
$container['authAdapter'] = function ($container) {
$adapter = new XmppAdapter(getenv('xmpp_host'), getenv('xmpp_port'), $container['logger'], getenv('xmpp_connection_type'));
return $adapter;
};
$container['acl'] = function ($c) {
return new ACL();
};
$container->register(new \JeremyKendall\Slim\Auth\ServiceProvider\SlimAuthProvider());
$app->add($app->getContainer()->get('slimAuthRedirectMiddleware'));
// View
$container['flash'] = function () {
return new Slim\Flash\Messages;
@ -67,6 +82,7 @@ $container['view'] = function ($container) use ($translator) {
}));
$view['flash'] = $container['flash'];
$view['config'] = $container['config'];
$view['currentUser'] = ($container['authenticator']->hasIdentity() ? $container['authenticator']->getIdentity() : NULL); // currentUser in Twig
return $view;
};

View file

@ -41,7 +41,7 @@ final class DeleteAction
]);
if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash);
return $response->withRedirect('/delete');
return $response->withRedirect('delete');
}
$username = $body['username'];
@ -52,7 +52,7 @@ final class DeleteAction
if (empty($usersRegistered) || $usersRegistered->count() == 0) {
$this->flash->addMessage('error', $this->translator->trans('delete.flash.combination_not_found'));
return $response->withRedirect('/delete');
return $response->withRedirect('delete');
} else {
$userRegistered = $usersRegistered->pop();
@ -66,11 +66,11 @@ final class DeleteAction
$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('/');
return $response->withRedirect('logout');
} else {
$this->flash->addMessage('error', $this->translator->trans('delete.flash.unknown_error', ['%username%' => $username]));
$this->logger->error($this->translator->trans('log.delete.flash.unknown_error'), ['username' => $username, 'code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('/delete');
$this->logger->error($this->translator->trans('log.delete.unknown_error'), ['username' => $username, 'code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('delete');
}
}
}

View file

@ -0,0 +1,76 @@
<?php
use JeremyKendall\Slim\Auth\Authenticator;
use Slim\Flash\Messages;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Slim\Http\Request;
use Slim\Http\Response;
use Symfony\Component\Translation\Translator;
final class LoginAction
{
private $view;
private $logger;
private $flash;
private $translator;
private $auth;
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator, Authenticator $auth)
{
$this->view = $view;
$this->logger = $logger;
$this->flash = $flash;
$this->translator = $translator;
$this->auth = $auth;
}
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',
]);
$validator->validation_rules([
'username' => 'required|alpha_numeric|max_len,64|min_len,3',
'password' => 'required|max_len,255|min_len,8',
]);
if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash);
return $response->withRedirect('/login');
}
$username = $body['username'];
$password = $body['password'];
$result = $this->auth->authenticate($username, $password);
if ($result->isValid()) {
if (empty(UserRegistered::with([])->find($username))) {
$userRegistered = new UserRegistered();
$userRegistered->username = $username;
$userRegistered->generateDeleteCode();
$userRegistered->save();
}
$this->flash->addMessage('success', $this->translator->trans('login.flash.success', ['%username%' => $username, '%server%' => getenv('site_xmpp_server_displayname')]));
$this->logger->info($this->translator->trans('log.login', ['%username%' => $username]));
return $response->withRedirect('/');
}
$this->flash->addMessage('error', $this->translator->trans('login.flash.wrong_credentials'));
return $response->withRedirect('login');
}
// render GET
$this->view->render($response, 'login.twig', [
'title' => $this->translator->trans('login.title'),
]);
}
}

View file

@ -0,0 +1,36 @@
<?php
use JeremyKendall\Slim\Auth\Authenticator;
use Slim\Flash\Messages;
use Slim\Views\Twig;
use Psr\Log\LoggerInterface;
use Slim\Http\Request;
use Slim\Http\Response;
use Symfony\Component\Translation\Translator;
final class LogoutAction
{
private $view;
private $logger;
private $flash;
private $translator;
private $auth;
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator, Authenticator $auth)
{
$this->view = $view;
$this->logger = $logger;
$this->flash = $flash;
$this->translator = $translator;
$this->auth = $auth;
}
public function __invoke(Request $request, Response $response, $args)
{
$this->flash->addMessage('success', $this->translator->trans('logout.flash.success'));
$this->logger->info($this->translator->trans('log.logout', ['%username%' => $this->auth->getIdentity()['identity']]));
$this->auth->logout();
return $response->withRedirect('login');
}
}

View file

@ -0,0 +1,83 @@
<?php
use Curl\Curl;
use JeremyKendall\Slim\Auth\Authenticator;
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 PasswordAction
{
private $view;
private $translator;
private $logger;
private $flash;
private $auth;
public function __construct(Twig $view, LoggerInterface $logger, Messages $flash, Translator $translator, Authenticator $auth)
{
$this->view = $view;
$this->translator = $translator;
$this->logger = $logger;
$this->flash = $flash;
$this->auth = $auth;
}
public function __invoke(Request $request, Response $response, $args)
{
$body = $request->getParsedBody();
if ($request->isPost()) {
// Form validation
$validator = new Validator();
$validator->validation_rules([
'password' => 'required|max_len,255|min_len,8',
'password_confirmation' => 'required|max_len,255|min_len,8',
]);
if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash);
return $response->withRedirect('password');
}
$password = $body['password'];
$passwordConfirmation = $body['password_confirmation'];
$this->logger->debug($password);
$this->logger->debug($passwordConfirmation);
if ($password != $passwordConfirmation) {
$this->flash->addMessage('error', $this->translator->trans('password.flash.not_the_same'));
return $response->withRedirect('password');
}
// update password
$username = $this->auth->getIdentity()['identity'];
$curl = new Curl();
$curl->setBasicAuthentication(getenv('xmpp_curl_auth_admin_username'), getenv('xmpp_curl_auth_admin_password'));
$curl->patch(getenv('xmpp_curl_uri') . '/user/' . $username . '/password', json_encode(['password' => $password]));
$curl->close();
if ($curl->http_status_code == 200) {
$this->flash->addMessage('success', $this->translator->trans('password.flash.success', ['%username%' => $username]));
$this->logger->info($this->translator->trans('log.password.success', ['%username%' => $username]));
return $response->withRedirect('logout');
} else {
$this->flash->addMessage('error', $this->translator->trans('password.flash.unknown_error', ['%username%' => $username]));
$this->logger->error($this->translator->trans('log.password.unknown_error'), ['username' => $username, 'code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('password');
}
}
// render GET
$this->view->render($response, 'password.twig', [
'title' => $this->translator->trans('password.title'),
]);
return $response;
}
}

View file

@ -45,7 +45,7 @@ final class SignUpAction
]);
if (!$validator->run($body)) {
$validator->addErrorsToFlashMessage($this->flash);
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
}
$username = $body['username'];
@ -55,11 +55,11 @@ final class SignUpAction
// waiting queue
if ((UserAwaitingVerification::with([])->where('email', $email)->get()->count() > 0)) {
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_email', ['%email%' => $email]));
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
}
if ((UserAwaitingVerification::with([])->where('username', $username)->get()->count() > 0)) {
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_username', ['%username%' => $username]));
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
}
// xmpp accounts
@ -70,7 +70,7 @@ final class SignUpAction
if ($curl->http_status_code != 404) {
$this->flash->addMessage('error', $this->translator->trans('sign.up.flash.already_in_use_email_and_username', ['%email%' => $email, '%username%' => $username]));
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
}
$userAwaiting = new UserAwaitingVerification();
@ -117,7 +117,7 @@ final class SignUpAction
$this->flash->addMessage('success', $this->translator->trans('sign.up.flash.success'));
$this->logger->info($this->translator->trans('log.signed.up', ['%username%' => $userAwaiting->username]));
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
}
// render GET

View file

@ -47,7 +47,7 @@ final class VerificationAction
$this->flash->addMessage('error', $this->translator->trans('verification.flash.already_in_use_username', ['%username%' => $userAwaiting->username]));
$userAwaiting->delete();
return $response->withRedirect('/signup');
return $response->withRedirect('signup');
} else if ($curl->http_status_code == 201) {
$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]));
@ -73,7 +73,7 @@ final class VerificationAction
$userRegistered = new UserRegistered();
$userRegistered->username = $userAwaiting->username;
$userRegistered->delete_code = hash('sha256', (time() . $userAwaiting->username . rand()));
$userRegistered->generateDeleteCode();
$userRegistered->save();
$mailer = new PHPMailer();
@ -97,7 +97,7 @@ final class VerificationAction
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'), ['username' => $userAwaiting->username, 'code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
$this->logger->warning($this->translator->trans('log.verification.unknown_error'), ['username' => $userAwaiting->username, 'code' => $curl->http_status_code, 'message' => $curl->http_error_message]);
return $response->withRedirect('/');
}
}

View file

@ -6,4 +6,9 @@ class UserRegistered extends Model
public $table = 'users_registered';
public $primaryKey = 'username';
public $timestamps = false;
public function generateDeleteCode()
{
$this->delete_code = hash('sha256', (time() . $this->username . rand()));
}
}

View file

@ -0,0 +1,87 @@
<?php
namespace Auth;
use ACL;
use Fabiang\Xmpp\Client;
use Fabiang\Xmpp\Exception\Stream\AuthenticationErrorException;
use Fabiang\Xmpp\Options;
use Psr\Log\LoggerInterface;
use UserRegistered;
use Zend\Authentication\Adapter\AbstractAdapter;
use Zend\Authentication\Result;
class XmppAdapter extends AbstractAdapter
{
/**
* @var string
*/
private $host;
/**
* @var integer
*/
private $port;
/**
* @var string
*/
private $connectionType;
/**
* @var LoggerInterface $logger
*/
private $logger;
/**
* XmppAdapter constructor.
*
* @param $host
* @param $port
* @param LoggerInterface $logger
*/
public function __construct($host, $port, LoggerInterface $logger, $connectionType = 'tcp')
{
$this->host = $host;
$this->port = $port;
$this->logger = $logger;
$this->connectionType = $connectionType;
}
/**
* Performs an authentication attempt
*
* @return \Zend\Authentication\Result
* @throws \Zend\Authentication\Adapter\Exception\ExceptionInterface If authentication cannot be performed
*/
public function authenticate()
{
$address = "$this->connectionType://$this->host:$this->port";
$options = new Options($address);
$options->setLogger($this->logger)
->setUsername($this->getIdentity())
->setPassword($this->getCredential());
$client = new Client($options);
try {
/*$userRegistered = UserRegistered::with([])->find($this->getIdentity());
if (empty($userRegistered)) {
throw new AuthenticationErrorException;
}*/
$client->connect();
} catch (AuthenticationErrorException $e) {
return new Result(
Result::FAILURE_CREDENTIAL_INVALID,
array(),
array('Invalid username or password provided')
);
}
$user = ['identity' => $this->getIdentity(), 'role' => ACL::$ROLE_MEMBER];
return new Result(Result::SUCCESS, $user, array());
}
}

View file

@ -4,7 +4,7 @@ use Illuminate\Database\Capsule\Manager;
class DatabaseHelper
{
public static function bootORM()
public static function getAppDatabase()
{
$config = Config::$CONFIG['db_settings'];
$path = $config['database'];

View file

@ -24,8 +24,15 @@
<nav id="nav-left">
<div id="nav-list">
<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>
<!-- current user -->
{% if currentUser is not empty %}
<a href="{{ base_url() }}{{ path_for('password') }}">{{ getenv('site_navbar_password_displayname') }}</a>
<a href="{{ base_url() }}{{ path_for('delete') }}">{{ getenv('site_navbar_delete_displayname') }}</a>
{% else %}
<a href="{{ base_url() }}{{ path_for('signup') }}">{{ getenv('site_navbar_signup_displayname') }}</a>
<a href="{{ base_url() }}{{ path_for('login') }}">{{ getenv('site_navbar_login_displayname') }}</a>
{% endif %}
{% if getenv('site_navbar_backlink_enabled') == 'true' %}
<br />
@ -36,8 +43,14 @@
</div>
<!-- Nav footer -->
<!-- current user show -->
{% if currentUser is not empty %}
<footer>
{% trans with {'%username%': currentUser.identity, '%server%': getenv('site_xmpp_server_displayname') } %}logged.in.site{% endtrans %}
<a href="{{ base_url() }}{{ path_for('logout') }}">{{ getenv('site_navbar_logout_displayname') }}</a>
</footer>
{% endif %}
</nav>

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

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

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

@ -0,0 +1,15 @@
{% extends 'base.twig' %}
{% block content %}
<form class="" role="form" name="password" id="password" method="post">
<h1>{{ title }}</h1>
<label for="password">{% trans %}password.form.password{% endtrans %}</label><br/>
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}password.form.password.placeholder{% endtrans %}" required>
<br/><br/>
<label for="password_confirmation">{% trans %}password.form.password_confirmation{% endtrans %}</label><br/>
<input type="password" id="password_confirmation" name="password_confirmation" class="form-control" placeholder="{% trans %}password.form.password_confirmation.placeholder{% endtrans %}" required>
<br/><br/>
<input class="" type="submit" name="password_change" value="{% trans %}password.form.button{% endtrans %}"/>
</form>
{% endblock %}

View file

@ -10,7 +10,7 @@
<label for="email">{% trans %}sign.up.form.email{% endtrans %}</label><br/>
<input type="text" id="email" name="email" class="" placeholder="{% trans %}sign.up.form.email.placeholder{% endtrans %}" value="" autofocus required>
<br/><br/>
<label for="username">{% trans %}sign.up.form.password{% endtrans %}</label><br/>
<label for="password">{% trans %}sign.up.form.password{% endtrans %}</label><br/>
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}sign.up.form.password.placeholder{% endtrans %}" required>
<br/><br/>
<input class="" type="submit" name="signup" value="{% trans %}sign.up.form.button{% endtrans %}"/>