simple account management with deletion
This commit is contained in:
parent
1840592c1b
commit
7f602781d3
22 changed files with 521 additions and 24 deletions
12
README.md
12
README.md
|
@ -1,10 +1,15 @@
|
||||||
# README #
|
# 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
|
This app uses
|
||||||
|
|
||||||
* Slim Version 3
|
* Slim Version 3
|
||||||
|
* Slim Auth
|
||||||
* Eloquent ORM
|
* Eloquent ORM
|
||||||
* PHPMigration
|
* PHPMigration
|
||||||
* GUMP Validation
|
* 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.
|
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 ##
|
## 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:
|
- 0.1.1:
|
||||||
- updated readme and `env.example`
|
- updated readme and `env.example`
|
||||||
- fix some language validator inconsistencies
|
- fix some language validator inconsistencies
|
||||||
|
|
|
@ -9,7 +9,7 @@ require_once __DIR__ . DIRECTORY_SEPARATOR . '../vendor/autoload.php';
|
||||||
*/
|
*/
|
||||||
$env = EnvironmentHelper::getAppEnvironment();
|
$env = EnvironmentHelper::getAppEnvironment();
|
||||||
$config = Config::$CONFIG;
|
$config = Config::$CONFIG;
|
||||||
$db = DatabaseHelper::bootORM();
|
$db = DatabaseHelper::getAppDatabase();
|
||||||
$translator = TranslationHelper::getAppTranslator();
|
$translator = TranslationHelper::getAppTranslator();
|
||||||
$logger = LoggerHelper::getAppLogger();
|
$logger = LoggerHelper::getAppLogger();
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
"davedevelopment/phpmig": "^1.2",
|
"davedevelopment/phpmig": "^1.2",
|
||||||
"symfony/translation": "^3.1",
|
"symfony/translation": "^3.1",
|
||||||
"symfony/twig-bridge": "^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": {
|
"config": {
|
||||||
"bin-dir": "bin/"
|
"bin-dir": "bin/"
|
||||||
|
|
89
config/ACL.php
Normal file
89
config/ACL.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,9 +25,18 @@ $container[SignUpAction::class] = function ($c) {
|
||||||
$container[VerificationAction::class] = function ($c) {
|
$container[VerificationAction::class] = function ($c) {
|
||||||
return new VerificationAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
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) {
|
$container[DeleteAction::class] = function ($c) {
|
||||||
return new DeleteAction($c->get('view'), $c->get('logger'), $c->get('flash'), $c->get('translator'));
|
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
|
// Routes
|
||||||
// error
|
// error
|
||||||
|
@ -40,4 +49,7 @@ $app->get('/500', InternalApplicationError::class)->setName('500');
|
||||||
$app->get('/', HomeAction::class)->setName('/');
|
$app->get('/', HomeAction::class)->setName('/');
|
||||||
$app->map(['GET', 'POST'], '/signup', SignUpAction::class)->setName('signup');
|
$app->map(['GET', 'POST'], '/signup', SignUpAction::class)->setName('signup');
|
||||||
$app->get('/verification/{verificationCode}', VerificationAction::class)->setName('verification');
|
$app->get('/verification/{verificationCode}', VerificationAction::class)->setName('verification');
|
||||||
$app->map(['GET', 'POST'], '/delete', DeleteAction::class)->setName('delete');
|
$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');
|
|
@ -1,8 +1,11 @@
|
||||||
# site settings
|
# site settings
|
||||||
site_title=""
|
site_title="hoth.one"
|
||||||
site_navbar_home_displayname="Home"
|
site_navbar_home_displayname="Home"
|
||||||
site_navbar_signup_displayname="Sign up"
|
site_navbar_signup_displayname="Sign up"
|
||||||
|
site_navbar_password_displayname="Change password"
|
||||||
site_navbar_delete_displayname="Delete Account"
|
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_enabled="false" # enables a link in the navbar to go back to e.g. main server site
|
||||||
site_navbar_backlink_uri=""
|
site_navbar_backlink_uri=""
|
||||||
site_navbar_backlink_displayname=""
|
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_timeout and non-verified users will be deleted
|
||||||
verification_cleanup_time="7 day"
|
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
|
# mod_admin_rest Settings
|
||||||
xmpp_curl_uri="/admin_rest" # full 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_username="" # configured in prosody lua file
|
||||||
|
|
|
@ -4,7 +4,7 @@ use \Phpmig\Adapter;
|
||||||
|
|
||||||
$container = new ArrayObject();
|
$container = new ArrayObject();
|
||||||
$container['env'] = EnvironmentHelper::getAppEnvironment();
|
$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');
|
$container['phpmig.adapter'] = new Phpmig\Adapter\PDO\Sql($container['db']->getConnection()->getPdo(), 'migrations');
|
||||||
|
|
||||||
|
|
|
@ -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.success: Successfully deleted your account %username%@%server%.
|
||||||
delete.flash.unknown_error: Could not process deletion of %username%. Please contact administrator.
|
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
|
||||||
cleanup.mail.subject: %server%: jabber account verification expired
|
cleanup.mail.subject: %server%: jabber account verification expired
|
||||||
cleanup.mail.body: |
|
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.verification.cleanup: %username% did not verify. Deleted.
|
||||||
log.delete.success: %username% deleted their account.
|
log.delete.success: %username% deleted their account.
|
||||||
log.delete.unknown_error: Unknown error in XMPP Rest API.
|
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
|
||||||
error.401.title: 401
|
error.401.title: 401
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
use Auth\XmppAdapter;
|
||||||
|
use Auth\XmppValidator;
|
||||||
use Slim\Http\Request;
|
use Slim\Http\Request;
|
||||||
use Slim\Http\Response;
|
use Slim\Http\Response;
|
||||||
|
|
||||||
|
@ -31,7 +33,7 @@ $container['config'] = function() {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Database
|
// Database
|
||||||
$capsule = DatabaseHelper::bootORM();
|
$capsule = DatabaseHelper::getAppDatabase();
|
||||||
$container['db'] = function () use ($capsule) {
|
$container['db'] = function () use ($capsule) {
|
||||||
return $capsule;
|
return $capsule;
|
||||||
};
|
};
|
||||||
|
@ -48,6 +50,19 @@ $container['logger'] = function () {
|
||||||
return $logger;
|
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
|
// View
|
||||||
$container['flash'] = function () {
|
$container['flash'] = function () {
|
||||||
return new Slim\Flash\Messages;
|
return new Slim\Flash\Messages;
|
||||||
|
@ -67,6 +82,7 @@ $container['view'] = function ($container) use ($translator) {
|
||||||
}));
|
}));
|
||||||
$view['flash'] = $container['flash'];
|
$view['flash'] = $container['flash'];
|
||||||
$view['config'] = $container['config'];
|
$view['config'] = $container['config'];
|
||||||
|
$view['currentUser'] = ($container['authenticator']->hasIdentity() ? $container['authenticator']->getIdentity() : NULL); // currentUser in Twig
|
||||||
return $view;
|
return $view;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ final class DeleteAction
|
||||||
]);
|
]);
|
||||||
if (!$validator->run($body)) {
|
if (!$validator->run($body)) {
|
||||||
$validator->addErrorsToFlashMessage($this->flash);
|
$validator->addErrorsToFlashMessage($this->flash);
|
||||||
return $response->withRedirect('/delete');
|
return $response->withRedirect('delete');
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $body['username'];
|
$username = $body['username'];
|
||||||
|
@ -52,7 +52,7 @@ final class DeleteAction
|
||||||
|
|
||||||
if (empty($usersRegistered) || $usersRegistered->count() == 0) {
|
if (empty($usersRegistered) || $usersRegistered->count() == 0) {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('delete.flash.combination_not_found'));
|
$this->flash->addMessage('error', $this->translator->trans('delete.flash.combination_not_found'));
|
||||||
return $response->withRedirect('/delete');
|
return $response->withRedirect('delete');
|
||||||
} else {
|
} else {
|
||||||
$userRegistered = $usersRegistered->pop();
|
$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->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]));
|
$this->logger->info($this->translator->trans('log.delete.success', ['%username%' => $username]));
|
||||||
return $response->withRedirect('/');
|
return $response->withRedirect('logout');
|
||||||
} else {
|
} else {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('delete.flash.unknown_error', ['%username%' => $username]));
|
$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]);
|
$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');
|
return $response->withRedirect('delete');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
76
src/Control/Actions/LoginAction.php
Normal file
76
src/Control/Actions/LoginAction.php
Normal 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'),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
36
src/Control/Actions/LogoutAction.php
Normal file
36
src/Control/Actions/LogoutAction.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
83
src/Control/Actions/PasswordAction.php
Normal file
83
src/Control/Actions/PasswordAction.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ final class SignUpAction
|
||||||
]);
|
]);
|
||||||
if (!$validator->run($body)) {
|
if (!$validator->run($body)) {
|
||||||
$validator->addErrorsToFlashMessage($this->flash);
|
$validator->addErrorsToFlashMessage($this->flash);
|
||||||
return $response->withRedirect('/signup');
|
return $response->withRedirect('signup');
|
||||||
}
|
}
|
||||||
|
|
||||||
$username = $body['username'];
|
$username = $body['username'];
|
||||||
|
@ -55,11 +55,11 @@ final class SignUpAction
|
||||||
// waiting queue
|
// waiting queue
|
||||||
if ((UserAwaitingVerification::with([])->where('email', $email)->get()->count() > 0)) {
|
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]));
|
$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)) {
|
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]));
|
$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
|
// xmpp accounts
|
||||||
|
@ -70,7 +70,7 @@ final class SignUpAction
|
||||||
|
|
||||||
if ($curl->http_status_code != 404) {
|
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]));
|
$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();
|
$userAwaiting = new UserAwaitingVerification();
|
||||||
|
@ -117,7 +117,7 @@ final class SignUpAction
|
||||||
|
|
||||||
$this->flash->addMessage('success', $this->translator->trans('sign.up.flash.success'));
|
$this->flash->addMessage('success', $this->translator->trans('sign.up.flash.success'));
|
||||||
$this->logger->info($this->translator->trans('log.signed.up', ['%username%' => $userAwaiting->username]));
|
$this->logger->info($this->translator->trans('log.signed.up', ['%username%' => $userAwaiting->username]));
|
||||||
return $response->withRedirect('/signup');
|
return $response->withRedirect('signup');
|
||||||
}
|
}
|
||||||
|
|
||||||
// render GET
|
// render GET
|
||||||
|
|
|
@ -47,7 +47,7 @@ final class VerificationAction
|
||||||
$this->flash->addMessage('error', $this->translator->trans('verification.flash.already_in_use_username', ['%username%' => $userAwaiting->username]));
|
$this->flash->addMessage('error', $this->translator->trans('verification.flash.already_in_use_username', ['%username%' => $userAwaiting->username]));
|
||||||
|
|
||||||
$userAwaiting->delete();
|
$userAwaiting->delete();
|
||||||
return $response->withRedirect('/signup');
|
return $response->withRedirect('signup');
|
||||||
} else if ($curl->http_status_code == 201) {
|
} else if ($curl->http_status_code == 201) {
|
||||||
$this->flash->addMessage('success', $this->translator->trans('verification.flash.success', ['%username%' => $userAwaiting->username]));
|
$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]));
|
$this->logger->info($this->translator->trans('log.verification.sucess', ['%username%' => $userAwaiting->username]));
|
||||||
|
@ -73,7 +73,7 @@ final class VerificationAction
|
||||||
|
|
||||||
$userRegistered = new UserRegistered();
|
$userRegistered = new UserRegistered();
|
||||||
$userRegistered->username = $userAwaiting->username;
|
$userRegistered->username = $userAwaiting->username;
|
||||||
$userRegistered->delete_code = hash('sha256', (time() . $userAwaiting->username . rand()));
|
$userRegistered->generateDeleteCode();
|
||||||
$userRegistered->save();
|
$userRegistered->save();
|
||||||
|
|
||||||
$mailer = new PHPMailer();
|
$mailer = new PHPMailer();
|
||||||
|
@ -97,7 +97,7 @@ final class VerificationAction
|
||||||
return $response->withRedirect('/');
|
return $response->withRedirect('/');
|
||||||
} else {
|
} else {
|
||||||
$this->flash->addMessage('error', $this->translator->trans('verification.flash.unknown_error', ['%username%' => $userAwaiting->username]));
|
$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('/');
|
return $response->withRedirect('/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,4 +6,9 @@ class UserRegistered extends Model
|
||||||
public $table = 'users_registered';
|
public $table = 'users_registered';
|
||||||
public $primaryKey = 'username';
|
public $primaryKey = 'username';
|
||||||
public $timestamps = false;
|
public $timestamps = false;
|
||||||
|
|
||||||
|
public function generateDeleteCode()
|
||||||
|
{
|
||||||
|
$this->delete_code = hash('sha256', (time() . $this->username . rand()));
|
||||||
|
}
|
||||||
}
|
}
|
87
src/Util/Auth/XmppAdapter.php
Normal file
87
src/Util/Auth/XmppAdapter.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use Illuminate\Database\Capsule\Manager;
|
||||||
|
|
||||||
class DatabaseHelper
|
class DatabaseHelper
|
||||||
{
|
{
|
||||||
public static function bootORM()
|
public static function getAppDatabase()
|
||||||
{
|
{
|
||||||
$config = Config::$CONFIG['db_settings'];
|
$config = Config::$CONFIG['db_settings'];
|
||||||
$path = $config['database'];
|
$path = $config['database'];
|
||||||
|
|
|
@ -24,8 +24,15 @@
|
||||||
<nav id="nav-left">
|
<nav id="nav-left">
|
||||||
<div id="nav-list">
|
<div id="nav-list">
|
||||||
<a href="{{ base_url() }}{{ path_for('/') }}">{{ getenv('site_navbar_home_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>
|
<!-- 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' %}
|
{% if getenv('site_navbar_backlink_enabled') == 'true' %}
|
||||||
<br />
|
<br />
|
||||||
|
@ -36,8 +43,14 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Nav footer -->
|
<!-- Nav footer -->
|
||||||
|
<!-- current user show -->
|
||||||
|
{% if currentUser is not empty %}
|
||||||
<footer>
|
<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>
|
</footer>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
|
15
src/View/login.twig
Normal file
15
src/View/login.twig
Normal 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
15
src/View/password.twig
Normal 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 %}
|
|
@ -10,7 +10,7 @@
|
||||||
<label for="email">{% trans %}sign.up.form.email{% endtrans %}</label><br/>
|
<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>
|
<input type="text" id="email" name="email" class="" placeholder="{% trans %}sign.up.form.email.placeholder{% endtrans %}" value="" autofocus required>
|
||||||
<br/><br/>
|
<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>
|
<input type="password" id="password" name="password" class="form-control" placeholder="{% trans %}sign.up.form.password.placeholder{% endtrans %}" required>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<input class="" type="submit" name="signup" value="{% trans %}sign.up.form.button{% endtrans %}"/>
|
<input class="" type="submit" name="signup" value="{% trans %}sign.up.form.button{% endtrans %}"/>
|
||||||
|
|
Reference in a new issue