diff --git a/actions-web.php b/actions-web.php
index e2f0a65..d59ba28 100644
--- a/actions-web.php
+++ b/actions-web.php
@@ -516,38 +516,6 @@ function changecity($userid, $city)
}
-function resetpassword($number)
-{
- global $db, $mailer;
-
- $number = $db->escape(trim($number));
-
- $result = $db->query("SELECT mail,userName FROM users WHERE number='$number'");
- if (!$result->num_rows) {
- response(_('No such user found.'), 1);
- }
-
- $row = $result->fetch_assoc();
- $email = $row['mail'];
- $username = $row['userName'];
-
- $subject = _('Password reset');
-
- mt_srand(crc32(microtime()));
- $password = substr(md5(mt_rand() . microtime() . $email), 0, 8);
-
- $result = $db->query("UPDATE users SET password=SHA2('$password',512) WHERE number='" . $number . "'");
-
- $names = preg_split("/[\s,]+/", $username);
- $firstname = $names[0];
- $message = _('Hello') . ' ' . $firstname . ",\n\n" .
- _('Your password has been reset successfully.') . "\n\n" .
- _('Your new password is:') . "\n" . $password;
-
- $mailer->sendMail($email, $subject, $message);
- response(_('Your password has been reset successfully.') . ' ' . _('Check your email.'));
-}
-
function mapgetmarkers($userId)
{
global $db, $configuration, $user;
diff --git a/admin.php b/admin.php
index 147f9b3..1d0de6a 100644
--- a/admin.php
+++ b/admin.php
@@ -63,7 +63,7 @@
= _('Map'); ?>
= _('Admin'); ?>
isLoggedIn()): ?>
- = _('Log out'); ?>
+ = _('Log out'); ?>
diff --git a/command.php b/command.php
index eb9bb17..ed2fed7 100644
--- a/command.php
+++ b/command.php
@@ -6,7 +6,6 @@
require_once 'actions-web.php';
$userid = $auth->getUserId();
-$session = $auth->getSessionId();
/**
* @var RentSystemInterface $rentSystem
@@ -33,18 +32,6 @@
$existing=trim($_GET["existing"]);
register($number,$smscode,$checkcode,$fullname,$useremail,$password,$password2,$existing);
break;
- case "login":
- $number=trim($_POST["number"]);
- $number = $phonePurifier->purify($number);
- $password=trim($_POST["password"]);
- $auth->login($number,$password);
- break;
- case "logout":
- $auth->logout();
- break;
- case "resetpassword":
- resetpassword($_GET["number"]);
- break;
case "list":
$stand=trim($_GET["stand"]);
listbikes($stand);
diff --git a/composer.json b/composer.json
index 320dc41..574d2d4 100644
--- a/composer.json
+++ b/composer.json
@@ -40,7 +40,8 @@
"symfony/twig-bundle": "^5.4",
"symfony/dotenv": "^5.4",
"symfony/translation": "^5.4",
- "symfony/asset": "^5.4"
+ "symfony/asset": "^5.4",
+ "symfony/security-bundle": "^5.4"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.10|^4.0",
diff --git a/config/bundles.php b/config/bundles.php
index b503e88..fe06784 100644
--- a/config/bundles.php
+++ b/config/bundles.php
@@ -3,7 +3,8 @@
declare(strict_types=1);
return [
- Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
- Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
+ \Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
+ \Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
\Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
+ \Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
];
\ No newline at end of file
diff --git a/config/packages/framework.php b/config/packages/framework.php
index 3343a07..fdbae3c 100644
--- a/config/packages/framework.php
+++ b/config/packages/framework.php
@@ -2,6 +2,7 @@
declare(strict_types=1);
+use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Config\FrameworkConfig;
return static function (FrameworkConfig $framework): void {
@@ -9,8 +10,12 @@
->throw(false);
$framework->session()
- ->storageFactoryId('session.storage.factory.php_bridge')
- ->handlerId(null);
+ ->enabled(true)
+ ->handlerId(null)
+ ->cookieSecure('auto')
+ ->cookieSamesite(Cookie::SAMESITE_LAX)
+ ->storageFactoryId('session.storage.factory.native')
+ ;
$framework->secret('%env(APP_SECRET)%');
diff --git a/config/packages/security.php b/config/packages/security.php
new file mode 100644
index 0000000..ada7dd2
--- /dev/null
+++ b/config/packages/security.php
@@ -0,0 +1,71 @@
+passwordHasher(PasswordAuthenticatedUserInterface::class)
+ ->algorithm('sha512')
+ ->encodeAsBase64(false)
+ ->iterations(1);
+
+ $security
+ ->provider('app_user_provider')
+ ->id(UserProvider::class);
+
+ $security
+ ->firewall('dev')
+ ->pattern('^/(_(profiler|wdt)|css|images|js)/')
+ ->security(false);
+
+ $mainFirewall = $security->firewall('main');
+ $mainFirewall->anonymous();
+ $mainFirewall
+ ->formLogin()
+ ->loginPath('login')
+ ->checkPath('login');
+ $mainFirewall
+ ->logout()
+ ->path('logout')
+ ->target('/');
+ $mainFirewall
+ ->rememberMe()
+ ->secret('%kernel.secret%')
+ ->lifetime(604800) // 1 week in seconds
+ ->tokenProvider(
+ [
+ 'service' => TokenProvider::class,
+ ]
+ );
+
+ $security
+ ->accessControl()
+ ->path('^/login$')
+ ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']);
+ $security
+ ->accessControl()
+ ->path('^/(sms/)?receive.php$')
+ ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']);
+ $security
+ ->accessControl()
+ ->path('^/register.php$')
+ ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']);
+ $security
+ ->accessControl()
+ ->path('^/agree.php$')
+ ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']);
+ $security
+ ->accessControl()
+ ->path('^/resetPassword$')
+ ->roles(['IS_AUTHENTICATED_ANONYMOUSLY']);
+
+ $security
+ ->accessControl()
+ ->path('^/')
+ ->roles(['ROLE_USER']);
+};
\ No newline at end of file
diff --git a/config/routes.php b/config/routes.php
index f0ebe04..00cff88 100644
--- a/config/routes.php
+++ b/config/routes.php
@@ -19,4 +19,10 @@
->controller([\BikeShare\Controller\SmsRequestController::class, 'index']);
$routes->add('agree', '/agree.php')
->controller([\BikeShare\Controller\AgreeController::class, 'index']);
+ $routes->add('login', '/login')
+ ->controller([\BikeShare\Controller\SecurityController::class, 'login']);
+ $routes->add('logout', '/logout')
+ ->controller([\BikeShare\Controller\SecurityController::class, 'logout']);
+ $routes->add('reset_password', '/resetPassword')
+ ->controller([\BikeShare\Controller\SecurityController::class, 'resetPassword']);
};
\ No newline at end of file
diff --git a/config/services.php b/config/services.php
index c66aa92..54284c7 100644
--- a/config/services.php
+++ b/config/services.php
@@ -50,6 +50,7 @@
'../src/SmsConnector/SmsGateway/SmsGateway.php',
'../src/App/Configuration.php',
'../src/App/Kernel.php',
+ '../src/App/Entity',
]);
$services->get(MysqliDb::class)
diff --git a/index.php b/index.php
index 1a8700d..3ac9d72 100644
--- a/index.php
+++ b/index.php
@@ -109,7 +109,7 @@
echo '';
}
- echo ' ', _('Log out'), '';
+ echo ' ', _('Log out'), '';
}
?>
diff --git a/public/images/logo_small.svg b/public/images/logo_small.svg
new file mode 100644
index 0000000..207a5de
--- /dev/null
+++ b/public/images/logo_small.svg
@@ -0,0 +1,7 @@
+
+
+
diff --git a/public/js/functions.js b/public/js/functions.js
index 99b7f52..b10a02e 100644
--- a/public/js/functions.js
+++ b/public/js/functions.js
@@ -11,7 +11,6 @@ $(document).ready(function () {
$('.bicycleactions').hide();
$('#notetext').hide();
$('#couponblock').hide();
- $('#passwordresetblock').hide();
$("#rent").hide();
$(document).ajaxStart(function () {
$('#overlay').show();
@@ -19,13 +18,6 @@ $(document).ready(function () {
$(document).ajaxStop(function () {
$('#overlay').hide();
});
- $("#password").focus(function () {
- $('#passwordresetblock').show();
- });
- $("#resetpassword").click(function () {
- if (window.ga) ga('send', 'event', 'buttons', 'click', 'password-reset');
- resetpassword();
- });
$("#rent").click(function () {
if (window.ga) ga('send', 'event', 'buttons', 'click', 'bike-rent');
rent();
@@ -504,20 +496,6 @@ function validatecoupon() {
});
}
-function resetpassword() {
- $('#passwordresetblock').hide();
- if (sms == 0 && $('#number').val() > 0) {
- $.ajax({
- url: "command.php?action=resetpassword&number=" + $('#number').val()
- }).done(function (jsonresponse) {
- jsonobject = $.parseJSON(jsonresponse);
- handleresponse(jsonobject);
- });
- } else if (sms == 1 && $('#number').val() > 0) {
- window.location = "register.php#reset" + $('#number').val();
- }
-}
-
function attachbicycleinfo(element, attachto) {
$('#' + attachto + ' .bikenumber').html($(element).attr('data-id'));
// show warning, if exists:
diff --git a/scan.php b/scan.php
index 596a38d..20b3db9 100644
--- a/scan.php
+++ b/scan.php
@@ -7,7 +7,6 @@
$auth->refreshSession();
$userid = $auth->getUserId();
-$session = $auth->getSessionId();
if (!$auth->isLoggedIn()) {
response("" . _('You are not logged in.') . "
", ERROR);
diff --git a/src/App/Entity/User.php b/src/App/Entity/User.php
new file mode 100644
index 0000000..ba64ec3
--- /dev/null
+++ b/src/App/Entity/User.php
@@ -0,0 +1,91 @@
+userId = $userId;
+ $this->number = $number;
+ $this->email = $email;
+ $this->password = $password;
+ $this->city = $city;
+ $this->userName = $userName;
+ $this->privileges = $privileges;
+ }
+
+ public function getCity(): string
+ {
+ return $this->city;
+ }
+
+ public function getNumber(): string
+ {
+ return $this->number;
+ }
+
+ public function getEmail(): string
+ {
+ return $this->email;
+ }
+
+ public function getPrivileges(): int
+ {
+ return $this->privileges;
+ }
+
+ public function getUserId(): int
+ {
+ return $this->userId;
+ }
+
+ public function getUsername(): string
+ {
+ return $this->userName;
+ }
+
+ public function getPassword(): string
+ {
+ return $this->password;
+ }
+
+ public function getUserIdentifier(): string
+ {
+ return $this->number;
+ }
+
+ public function getRoles(): array
+ {
+ return ['ROLE_USER'];
+ }
+
+ public function getSalt(): ?string
+ {
+ return null;
+ }
+
+ public function eraseCredentials()
+ {
+ // If you store any temporary, sensitive data on the user, clear it here
+ }
+}
diff --git a/src/App/Security/TokenProvider.php b/src/App/Security/TokenProvider.php
new file mode 100644
index 0000000..4354856
--- /dev/null
+++ b/src/App/Security/TokenProvider.php
@@ -0,0 +1,94 @@
+db = $db;
+ }
+
+ public function loadTokenBySeries(string $series)
+ {
+ if (!isset($this->tokens[$series])) {
+ $result = $this->db->query(
+ "SELECT * FROM remember_me_tokens WHERE series='$series'"
+ );
+ if (!$result || $result->rowCount() == 0) {
+ throw new TokenNotFoundException('No token found.');
+ }
+
+ $row = $result->fetchAssoc();
+
+ $this->tokens[$series] = new PersistentToken(
+ $row['class'],
+ $row['username'],
+ $row['series'],
+ $row['value'],
+ new \DateTime($row['lastUsed'])
+ );
+ }
+
+ return $this->tokens[$series];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateToken(string $series, string $tokenValue, \DateTime $lastUsed)
+ {
+ $currentToken = $this->loadTokenBySeries($series);
+
+ $token = new PersistentToken(
+ $currentToken->getClass(),
+ method_exists($currentToken, 'getUserIdentifier') ?
+ $currentToken->getUserIdentifier() : $currentToken->getUsername(),
+ $series,
+ $tokenValue,
+ $lastUsed
+ );
+ $this->tokens[$series] = $token;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteTokenBySeries(string $series)
+ {
+ $this->db->query(
+ "DELETE FROM remember_me_tokens WHERE series='$series'"
+ );
+
+ unset($this->tokens[$series]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function createNewToken(PersistentTokenInterface $token)
+ {
+ $this->db->query(
+ "INSERT INTO remember_me_tokens (class, username, series, value, lastUsed)
+ VALUES ('{$token->getClass()}',
+ '{$token->getUsername()}',
+ '{$token->getSeries()}',
+ '{$token->getTokenValue()}',
+ '{$token->getLastUsed()->format('Y-m-d H:i:s')}')"
+ );
+
+ $this->tokens[$token->getSeries()] = $token;
+ }
+}
diff --git a/src/App/Security/UserProvider.php b/src/App/Security/UserProvider.php
new file mode 100644
index 0000000..4162d31
--- /dev/null
+++ b/src/App/Security/UserProvider.php
@@ -0,0 +1,120 @@
+db = $db;
+ $this->phonePurifier = $phonePurifier;
+ }
+
+ /**
+ * @deprecated use loadUserByIdentifier() instead
+ */
+ public function loadUserByUsername(string $username)
+ {
+ return $this->loadUserByIdentifier($username);
+ }
+
+ /**
+ *
+ * Symfony calls this method if you use features like switch_user
+ * or remember_me. If you're not using these features, you do not
+ * need to implement this method.
+ *
+ * @throws UserNotFoundException if the user is not found
+ */
+ public function loadUserByIdentifier(string $identifier): UserInterface
+ {
+ $identifier = $this->phonePurifier->purify($identifier);
+ $result = $this->db->query(
+ "SELECT userId, number, mail, password, city, userName, privileges FROM users WHERE number='$identifier'"
+ );
+ if (!$result || $result->rowCount() == 0) {
+ throw new UserNotFoundException(sprintf('Unknown user %s', $identifier));
+ }
+
+ $row = $result->fetchAssoc();
+
+ return new User(
+ (int)$row['userId'],
+ $row['number'],
+ $row['mail'],
+ $row['password'],
+ $row['city'],
+ $row['userName'],
+ (int)$row['privileges'],
+ );
+ }
+
+ /**
+ * Refreshes the user after being reloaded from the session.
+ *
+ * When a user is logged in, at the beginning of each request, the
+ * User object is loaded from the session and then this method is
+ * called. Your job is to make sure the user's data is still fresh by,
+ * for example, re-querying for fresh User data.
+ *
+ * If your firewall is "stateless: true" (for a pure API), this
+ * method is not called.
+ *
+ * @return UserInterface
+ */
+ public function refreshUser(UserInterface $user): UserInterface
+ {
+ if (!$user instanceof User) {
+ throw new UnsupportedUserException(sprintf('Invalid user class "%s".', get_class($user)));
+ }
+
+ $user = $this->loadUserByIdentifier($user->getNumber());
+
+ return $user;
+ }
+
+ /**
+ * Tells Symfony to use this provider for this User class.
+ */
+ public function supportsClass(string $class): bool
+ {
+ return User::class === $class || is_subclass_of($class, User::class);
+ }
+
+ /**
+ * Upgrades the hashed password of a user, typically for using a better hash algorithm.
+ */
+ public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
+ {
+ $this->db->query(
+ "UPDATE users SET password='$newHashedPassword' WHERE number='" . $user->getNumber() . "'"
+ );
+
+ $user = new User(
+ $user->getUserId(),
+ $user->getNumber(),
+ $user->getEmail(),
+ $newHashedPassword,
+ $user->getCity(),
+ $user->getUsername(),
+ $user->getPrivileges()
+ );
+ }
+}
diff --git a/src/Authentication/Auth.php b/src/Authentication/Auth.php
index 53ab4a5..0a9c8f8 100644
--- a/src/Authentication/Auth.php
+++ b/src/Authentication/Auth.php
@@ -2,125 +2,51 @@
namespace BikeShare\Authentication;
-use BikeShare\Db\DbInterface;
+use Symfony\Component\Security\Core\Security;
+/**
+ * @deprecated
+ */
class Auth
{
- private const SESSION_EXPIRATION = 86400 * 14; // 14 days to keep user logged in
-
- /**
- * @var DbInterface
- */
- private $db;
+ private Security $security;
public function __construct(
- DbInterface $db
+ Security $security
) {
- $this->db = $db;
+ $this->security = $security;
}
public function getUserId()
{
- if (isset($_COOKIE["loguserid"])) {
- return (int)$this->db->escape(trim($_COOKIE["loguserid"]));
+ /**
+ * @var \BikeShare\App\Entity\User $user
+ */
+ $user = $this->security->getUser();
+ if (!is_null($user)) {
+ return $user->getUserId();
} else {
return 0;
}
}
- public function getSessionId()
- {
- if (isset($_COOKIE["logsession"])) {
- return $this->db->escape(trim($_COOKIE["logsession"]));
- } else {
- return '';
- }
- }
-
public function login($number, $password)
{
- $number = $this->db->escape(trim($number));
- $password = $this->db->escape(trim($password));
-
- $result = $this->db->query(
- "SELECT userId FROM users WHERE number='$number' AND password=SHA2('$password',512)"
- );
- if ($result && $result->rowCount() == 1) {
- $row = $result->fetchAssoc();
- $userId = $row['userId'];
- $sessionId = hash('sha256', $userId . $number . time());
- $timeStamp = time() + self::SESSION_EXPIRATION;
- $this->db->query("DELETE FROM sessions WHERE userId='$userId'");
- $this->db->query(
- "INSERT INTO sessions SET userId='$userId',sessionId='$sessionId',timeStamp='$timeStamp'"
- );
- $this->db->commit();
- setcookie('loguserid', $userId, $timeStamp);
- setcookie('logsession', $sessionId, $timeStamp);
- header('HTTP/1.1 302 Found');
- header('Location: /');
- header('Connection: close');
- } else {
- header('HTTP/1.1 302 Found');
- header('Location: /?error=1');
- header('Connection: close');
- }
+ throw new \Exception("Deprecated method");
}
public function logout()
{
- if ($this->isLoggedIn()) {
- $userid = $this->getUserId();
- $sessionId = $this->getSessionId();
- $this->db->query("DELETE FROM sessions WHERE userId='$userid' OR sessionId='$sessionId'");
- $this->db->commit();
- }
- setcookie("loguserid", "0", ['expires' => time() - 3600, 'path' => "/"]);
- setcookie("logsession", "", ['expires' => time() - 3600, 'path' => "/"]);
- header('HTTP/1.1 302 Found');
- header('Location: /');
- header('Connection: close');
+ throw new \Exception("Deprecated method");
}
public function refreshSession()
{
- if (!$this->isLoggedIn()) {
- return;
- }
-
- $this->db->query("DELETE FROM sessions WHERE timeStamp<='" . time() . "'");
- $userid = $this->getUserId();
- $sessionId = $this->getSessionId();
- $result = $this->db->query(
- "SELECT sessionId FROM sessions WHERE userId='$userid'
- AND sessionId='$sessionId' AND timeStamp>'" . time() . "'"
- );
- if ($result->rowCount() == 1) {
- $timestamp = time() + self::SESSION_EXPIRATION;
- $this->db->query(
- "UPDATE sessions SET timeStamp='$timestamp' WHERE userId='$userid' AND sessionId='$sessionId'"
- );
- $this->db->commit();
- } else {
- $this->logout();
- }
+// throw new \Exception("Deprecated method");
}
public function isLoggedIn()
{
- $session = $this->getSessionId();
-
- if (!empty($session)) {
- $userid = $this->getUserId();
- $result = $this->db->query(
- "SELECT sessionId FROM sessions WHERE
- userId='$userid' AND sessionId='$session' AND timeStamp>'" . time() . "'"
- );
- if ($result && $result->rowCount() == 1) {
- return true;
- }
- }
-
- return false;
+ return !is_null($this->security->getUser());
}
}
diff --git a/src/Controller/SecurityController.php b/src/Controller/SecurityController.php
new file mode 100644
index 0000000..55f0ad4
--- /dev/null
+++ b/src/Controller/SecurityController.php
@@ -0,0 +1,105 @@
+getLastAuthenticationError();
+ $lastUsername = $authenticationUtils->getLastUsername();
+
+ return $this->render(
+ 'security/login.html.twig',
+ [
+ 'isSmsSystemEnabled' => $configuration->get('connectors')['sms'] == '',
+ 'last_username' => $lastUsername,
+ 'error' => $error,
+ ]
+ );
+ }
+
+ /**
+ * @Route("/logout", name="logout")
+ */
+ public function logout()
+ {
+ // controller can be blank: it will never be executed!
+ throw new \Exception('Don\'t forget to activate logout in security.php');
+ }
+
+ /**
+ * @Route("/resetPassword", name="reset_password")
+ */
+ public function resetPassword(
+ Request $request,
+ Configuration $configuration,
+ MailSenderInterface $mailer,
+ UserProviderInterface $userProvider,
+ UserPasswordHasherInterface $passwordHasher,
+ TranslatorInterface $translator
+ ) {
+ if ($request->isMethod('POST')) {
+ $number = $request->request->get('_username');
+
+ try {
+ $user = $userProvider->loadUserByIdentifier($number);
+ } catch (UserNotFoundException $e) {
+ $user = null;
+ }
+
+ if (!is_null($user)) {
+ mt_srand(crc32(microtime()));
+ $plainPassword = substr(md5(mt_rand() . microtime() . $user->getUsername()), 0, 8);
+ $hashedPassword = $passwordHasher->hashPassword(
+ $user,
+ $plainPassword
+ );
+ $userProvider->upgradePassword($user, $hashedPassword);
+
+ $subject = $translator->trans('Password reset');
+ $names = preg_split("/[\s,]+/", $user->getUsername());
+ $firstname = $names[0];
+ $message = $translator->trans('Hello') . ' ' . $firstname . ",\n\n" .
+ $translator->trans('Your password has been reset successfully.') . "\n\n" .
+ $translator->trans('Your new password is:') . "\n" . $plainPassword;
+
+ $mailer->sendMail($user->getEmail(), $subject, $message);
+ }
+
+ $this->addFlash(
+ 'success',
+ $translator->trans('Your password has been reset successfully.')
+ . ' '
+ . $translator->trans('Check your email.')
+ );
+
+ return $this->redirectToRoute('reset_password');
+ }
+
+ return $this->render(
+ 'security/reset_password.html.twig',
+ [
+ 'isSmsSystemEnabled' => $configuration->get('connectors')['sms'] == '',
+ ]
+ );
+ }
+}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index c9cdee4..43e8fc3 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -9,7 +9,7 @@
diff --git a/templates/index.html.twig b/templates/index.html.twig
index f401af6..a2be36d 100644
--- a/templates/index.html.twig
+++ b/templates/index.html.twig
@@ -57,12 +57,7 @@
-->
-{% if auth.isLoggedIn %}
-
-{% else %}
-
-{% endif %}
+
- {% if auth.isLoggedIn %}
-
-
-
{{ siteName }}
-
-
-
-
-
-
-
-
-
+
+
+
{{ siteName }}
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
+
+
+
-
+
-
-
+