mirror of https://github.com/kevachat/webapp.git
ghost
1 year ago
7 changed files with 743 additions and 0 deletions
@ -0,0 +1,646 @@
@@ -0,0 +1,646 @@
|
||||
<?php |
||||
|
||||
namespace App\Controller; |
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
||||
use Symfony\Contracts\Translation\TranslatorInterface; |
||||
|
||||
use Symfony\Component\Routing\Annotation\Route; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
|
||||
class UserController extends AbstractController |
||||
{ |
||||
private $_algorithm = PASSWORD_BCRYPT; |
||||
|
||||
private $_options = |
||||
[ |
||||
'cost' => 12 |
||||
]; |
||||
|
||||
#[Route( |
||||
'/user', |
||||
name: 'user_index', |
||||
methods: |
||||
[ |
||||
'GET' |
||||
] |
||||
)] |
||||
public function index( |
||||
?Request $request |
||||
): Response |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login' |
||||
); |
||||
} |
||||
|
||||
#[Route( |
||||
'/join', |
||||
name: 'user_join', |
||||
methods: |
||||
[ |
||||
'GET' |
||||
] |
||||
)] |
||||
public function join( |
||||
?Request $request |
||||
): Response |
||||
{ |
||||
// Check user session does not exist to continue |
||||
if (!empty($request->cookies->get('KEVACHAT_SESSION'))) |
||||
{ |
||||
// Redirect to logout |
||||
return $this->redirectToRoute( |
||||
'user_logout' |
||||
); |
||||
} |
||||
|
||||
return $this->render( |
||||
'default/user/join.html.twig', |
||||
[ |
||||
'request' => $request |
||||
] |
||||
); |
||||
} |
||||
|
||||
#[Route( |
||||
'/login', |
||||
name: 'user_login', |
||||
methods: |
||||
[ |
||||
'GET' |
||||
] |
||||
)] |
||||
public function login( |
||||
?Request $request |
||||
): Response |
||||
{ |
||||
// Check user session does not exist to continue |
||||
if (!empty($request->cookies->get('KEVACHAT_SESSION'))) |
||||
{ |
||||
// Redirect to logout |
||||
return $this->redirectToRoute( |
||||
'user_logout' |
||||
); |
||||
} |
||||
|
||||
return $this->render( |
||||
'default/user/login.html.twig', |
||||
[ |
||||
'request' => $request |
||||
] |
||||
); |
||||
} |
||||
|
||||
#[Route( |
||||
'/logout', |
||||
name: 'user_logout', |
||||
methods: |
||||
[ |
||||
'GET' |
||||
] |
||||
)] |
||||
public function logout( |
||||
?Request $request |
||||
): Response |
||||
{ |
||||
// Connect memcached |
||||
$memcached = new \Memcached(); |
||||
$memcached->addServer( |
||||
$this->getParameter('app.memcached.host'), |
||||
$this->getParameter('app.memcached.port') |
||||
); |
||||
|
||||
// Make sure cookies exist |
||||
if (!empty($request->cookies->get('KEVACHAT_SESSION')) && preg_match('/[A-z0-9]{32}/', $request->cookies->get('KEVACHAT_SESSION'))) |
||||
{ |
||||
// Delete from memory |
||||
$memcached->delete($session); |
||||
|
||||
// Delete cookies |
||||
setcookie('KEVACHAT_SESSION', '', -1); |
||||
} |
||||
|
||||
// Redirect to main page |
||||
return $this->redirectToRoute( |
||||
'user_login' |
||||
); |
||||
} |
||||
|
||||
#[Route( |
||||
'/join', |
||||
name: 'user_add', |
||||
methods: |
||||
[ |
||||
'POST' |
||||
] |
||||
)] |
||||
public function add( |
||||
Request $request, |
||||
TranslatorInterface $translator |
||||
): Response |
||||
{ |
||||
// Check maintenance mode disabled |
||||
if ($this->getParameter('app.maintenance')) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Maintenance, please try again later!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Check user session does not exist to continue |
||||
if (!empty($request->cookies->get('KEVACHAT_SESSION'))) |
||||
{ |
||||
// Redirect to logout |
||||
return $this->redirectToRoute( |
||||
'user_logout' |
||||
); |
||||
} |
||||
|
||||
// Trim extra spaces from username |
||||
$username = trim( |
||||
$request->get('username') |
||||
); |
||||
|
||||
// Connect memcached |
||||
$memcached = new \Memcached(); |
||||
$memcached->addServer( |
||||
$this->getParameter('app.memcached.host'), |
||||
$this->getParameter('app.memcached.port') |
||||
); |
||||
|
||||
// Create IP delay record |
||||
$memory = md5( |
||||
sprintf( |
||||
'%s.UserController::add:add.user.remote.ip.delay:%s', |
||||
__DIR__, |
||||
$request->getClientIp(), |
||||
), |
||||
); |
||||
|
||||
// Validate remote IP limits |
||||
if ($delay = (int) $memcached->get($memory)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => sprintf( |
||||
$translator->trans('Please wait %s seconds before register new username!'), |
||||
(int) $this->getParameter('app.add.user.remote.ip.delay') - (time() - $delay) |
||||
) |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Check client connection |
||||
if (!$client = $this->_client()) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not connect wallet database!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Check users database accessible |
||||
if (!$namespace = $this->_namespace($client)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not access user database, try again later!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate kevacoin key requirements |
||||
if (mb_strlen($username) < 1 || mb_strlen($username) > 520) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username length out of KevaCoin protocol limits!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate meta NS |
||||
if (str_starts_with($username, '_')) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username contain meta format!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username regex |
||||
if (!preg_match($this->getParameter('app.add.user.name.regex'), $username)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => sprintf( |
||||
$translator->trans('Username does not match node requirements: %s!'), |
||||
$this->getParameter('app.add.user.name.regex') |
||||
) |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username blacklist (reserved) |
||||
if (in_array($username, (array) explode('|', $this->getParameter('app.add.user.name.blacklist')))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username reserved by node!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username exist |
||||
if ($this->_hash($client, $namespace, $username)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username already taken!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate password length |
||||
if (mb_strlen($request->get('password')) <= 12) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Please, provide stronger password!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate passwords match |
||||
if ($request->get('password') !== $request->get('repeat')) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Password repeat incorrect!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Generate password hash |
||||
if (!$hash = password_hash($request->get('password'), $this->_algorithm, $this->_options)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not generate password hash!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Auth success, add user to DB |
||||
if (!$this->_add($client, $namespace, $username, $hash)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_add', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not create user in blockchain!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Register event time |
||||
$memcached->set( |
||||
$memory, |
||||
time(), |
||||
(int) $this->getParameter('app.add.user.remote.ip.delay') // auto remove on cache expire |
||||
); |
||||
|
||||
// Auth success, create user session |
||||
$session = md5( |
||||
sprintf( |
||||
'%s.%s.%s', |
||||
$request->get('username'), |
||||
$request->getClientIp(), |
||||
rand() |
||||
) |
||||
); |
||||
|
||||
// Save session to memory |
||||
if (!$memcached->set($session, $request->get('username'), (int) time() + $this->getParameter('app.session.default.timeout'))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not save user session!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Save session to user cookies |
||||
if (!setcookie('KEVACHAT_SESSION', $session, time() + $this->getParameter('app.session.default.timeout'))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not create session cookie!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Redirect to main page |
||||
return $this->redirectToRoute( |
||||
'room_index' |
||||
); |
||||
|
||||
// Redirect to login page |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username') |
||||
] |
||||
); |
||||
} |
||||
|
||||
#[Route( |
||||
'/login', |
||||
name: 'user_auth', |
||||
methods: |
||||
[ |
||||
'POST' |
||||
] |
||||
)] |
||||
public function auth( |
||||
Request $request, |
||||
TranslatorInterface $translator |
||||
): Response |
||||
{ |
||||
// Check maintenance mode disabled |
||||
if ($this->getParameter('app.maintenance')) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Maintenance, please try again later!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Check user session does not exist to continue |
||||
if (!empty($request->cookies->get('KEVACHAT_SESSION'))) |
||||
{ |
||||
// Redirect to logout |
||||
return $this->redirectToRoute( |
||||
'user_logout' |
||||
); |
||||
} |
||||
|
||||
// Connect memcached |
||||
$memcached = new \Memcached(); |
||||
$memcached->addServer( |
||||
$this->getParameter('app.memcached.host'), |
||||
$this->getParameter('app.memcached.port') |
||||
); |
||||
|
||||
// Check client connection |
||||
if (!$client = $this->_client()) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not connect wallet database!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Check username namespace accessible |
||||
if (!$namespace = $this->_namespace($client)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not access user database, try again later!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Trim extra spaces from username |
||||
$username = trim( |
||||
$request->get('username') |
||||
); |
||||
|
||||
// Validate kevacoin key requirements |
||||
if (mb_strlen($username) < 1 || mb_strlen($username) > 520) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username length out of KevaCoin protocol limits!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate meta NS |
||||
if (str_starts_with($username, '_')) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username contain meta format!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username regex |
||||
if (!preg_match($this->getParameter('app.add.user.name.regex'), $username)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => sprintf( |
||||
$translator->trans('Username does not match node requirements: %s!'), |
||||
$this->getParameter('app.add.user.name.regex') |
||||
) |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username blacklist |
||||
if (in_array($username, (array) explode('|', $this->getParameter('app.add.user.name.blacklist')))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username reserved by node!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate username exist |
||||
if (!$hash = $this->_hash($client, $namespace, $username)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Username not found!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate password |
||||
if (!password_verify($request->get('password'), $hash)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Password invalid!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Validate password algo |
||||
if (password_needs_rehash($hash, $this->_algorithm, $this->_options)) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Password needs rehash!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Auth success, create user session |
||||
$session = md5( |
||||
sprintf( |
||||
'%s.%s.%s', |
||||
$request->get('username'), |
||||
$request->getClientIp(), |
||||
rand() |
||||
) |
||||
); |
||||
|
||||
// Save session to memory |
||||
if (!$memcached->set($session, $request->get('username'), (int) time() + $this->getParameter('app.session.default.timeout'))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not save user session!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Save session to user cookies |
||||
if (!setcookie('KEVACHAT_SESSION', $session, time() + $this->getParameter('app.session.default.timeout'))) |
||||
{ |
||||
return $this->redirectToRoute( |
||||
'user_login', |
||||
[ |
||||
'username' => $request->get('username'), |
||||
'error' => $translator->trans('Could not create session cookie!') |
||||
] |
||||
); |
||||
} |
||||
|
||||
// Redirect to main page |
||||
return $this->redirectToRoute( |
||||
'room_index' |
||||
); |
||||
} |
||||
|
||||
private function _client(): \Kevachat\Kevacoin\Client |
||||
{ |
||||
$client = new \Kevachat\Kevacoin\Client( |
||||
$this->getParameter('app.kevacoin.protocol'), |
||||
$this->getParameter('app.kevacoin.host'), |
||||
$this->getParameter('app.kevacoin.port'), |
||||
$this->getParameter('app.kevacoin.username'), |
||||
$this->getParameter('app.kevacoin.password') |
||||
); |
||||
|
||||
return $client; |
||||
} |
||||
|
||||
private function _namespace( |
||||
\Kevachat\Kevacoin\Client $client |
||||
): ?string |
||||
{ |
||||
foreach ((array) $client->kevaListNamespaces() as $value) |
||||
{ |
||||
if ($value['displayName'] === '_KEVACHAT_USERS_') |
||||
{ |
||||
return $value['namespaceId']; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
private function _hash( |
||||
\Kevachat\Kevacoin\Client $client, |
||||
string $namespace, |
||||
string $username |
||||
): ?string |
||||
{ |
||||
if ($user = $client->kevaGet($namespace, $username)) |
||||
{ |
||||
if (!empty($user['value'])) |
||||
{ |
||||
return (string) $user['value']; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
private function _add( |
||||
\Kevachat\Kevacoin\Client $client, |
||||
string $namespace, |
||||
string $username, |
||||
string $hash |
||||
): ?string |
||||
{ |
||||
return $client->kevaPut( |
||||
$namespace, |
||||
$username, |
||||
$hash |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,17 @@
@@ -0,0 +1,17 @@
|
||||
{% extends 'default/layout.html.twig' %} |
||||
{% block head_title_content %}{{ 'Join' | trans }} - {{ 'KevaChat' | trans }}{% endblock %} |
||||
{% block main_content %} |
||||
<form name="join" action="{{ path('user_add') }}" method="post"> |
||||
{% if request.get('error') %} |
||||
<output name="error">{{ request.get('error') }}</output> |
||||
{% endif %} |
||||
<label for="username">{{ 'Username' | trans }}</label> |
||||
<input type="text" name="username" id="username" value="{{ request.get('username') }}" placeholder="{{ 'Your public identity for this instance, permanently stored in blockchain' | trans }}" /> |
||||
<label for="password">{{ 'Password' | trans }}</label> |
||||
<input type="password" name="password" id="password" value="" placeholder="{{ 'Bcrypt hash of this password permanently stored in blockchain' | trans }}" /> |
||||
<label for="repeat">{{ 'Repeat password' | trans }}</label> |
||||
<input type="password" name="repeat" id="repeat" value="" placeholder="{{ 'Make sure your password is correct, you can not reset it later!' | trans }}" /> |
||||
<a href="{{ path('user_login') }}">{{ 'I have account' | trans }}</a> |
||||
<button type="submit">{{ 'join' | trans }}</button> |
||||
</form> |
||||
{% endblock %} |
@ -0,0 +1,15 @@
@@ -0,0 +1,15 @@
|
||||
{% extends 'default/layout.html.twig' %} |
||||
{% block head_title_content %}{{ 'Login' | trans }} - {{ 'KevaChat' | trans }}{% endblock %} |
||||
{% block main_content %} |
||||
<form name="login" action="{{ path('user_auth') }}" method="post"> |
||||
{% if request.get('error') %} |
||||
<output name="error">{{ request.get('error') }}</output> |
||||
{% endif %} |
||||
<label for="username">{{ 'Username' | trans }}</label> |
||||
<input type="text" name="username" id="username" value="{{ request.get('username') }}" /> |
||||
<label for="password">{{ 'Password' | trans }}</label> |
||||
<input type="password" name="password" id="password" value="" /> |
||||
<a href="{{ path('user_join') }}">{{ 'Create account' | trans }}</a> |
||||
<button type="submit">{{ 'login' | trans }}</button> |
||||
</form> |
||||
{% endblock %} |
Loading…
Reference in new issue