diff --git a/database/migrations/structure/20230409_0101_username.sql b/database/migrations/structure/20230409_0101_username.sql
new file mode 100644
index 0000000..adfcb72
--- /dev/null
+++ b/database/migrations/structure/20230409_0101_username.sql
@@ -0,0 +1,3 @@
+ALTER TABLE `users`
+ADD `username` varchar(100) DEFAULT NULL,
+ADD UNIQUE `username` (`username`);
diff --git a/database/migrations/structure/20230409_0214_user_personal_data.sql b/database/migrations/structure/20230409_0214_user_personal_data.sql
new file mode 100644
index 0000000..5085241
--- /dev/null
+++ b/database/migrations/structure/20230409_0214_user_personal_data.sql
@@ -0,0 +1,5 @@
+ALTER TABLE `users`
+ADD `full_name` varchar(255) NOT NULL DEFAULT '',
+ADD `nickname` varchar(255) NOT NULL DEFAULT '',
+ADD `phone` varchar(255) NOT NULL DEFAULT '',
+ADD `id_number` varchar(255) NOT NULL DEFAULT '';
diff --git a/public/static/js/rvr.js b/public/static/js/rvr.js
index dcf3d45..ed2ef94 100644
--- a/public/static/js/rvr.js
+++ b/public/static/js/rvr.js
@@ -158,12 +158,17 @@ var RVR = {
document.getElementById('cover').style.visibility = 'hidden';
},
- observeInput: function (input, buttonToToggle) {
- if (input.defaultValue !== input.value) {
- buttonToToggle.disabled = false;
- } else {
- buttonToToggle.disabled = true;
+ observeInput: function (form, observedInputs) {
+ var anyChanged = false;
+
+ for (var i = 0; i < observedInputs.length; i++) {
+ var input = form.elements[observedInputs[i]];
+ if (input.defaultValue !== input.value) {
+ anyChanged = true;
+ }
}
+
+ form.elements.submit.disabled = !anyChanged;
},
observeInputsInForm: function (form, observedInputs) {
@@ -174,12 +179,12 @@ var RVR = {
case 'INPUT':
case 'TEXTAREA':
input.oninput = function () {
- RVR.observeInput(this, form.elements.submit);
+ RVR.observeInput(form, observedInputs);
};
break;
case 'SELECT':
input.onchange = function () {
- RVR.observeInput(this, form.elements.submit);
+ RVR.observeInput(form, observedInputs);
};
break;
}
diff --git a/src/Cli/AddUserCommand.php b/src/Cli/AddUserCommand.php
index 27a0d0b..980cb79 100644
--- a/src/Cli/AddUserCommand.php
+++ b/src/Cli/AddUserCommand.php
@@ -21,6 +21,11 @@ class AddUserCommand extends Command
public function execute(InputInterface $input, OutputInterface $output): int
{
+ if (!filter_var($input->getArgument('email'), FILTER_VALIDATE_EMAIL)) {
+ $output->writeln('Please provide a valid email address.');
+ return 1;
+ }
+
$user = new User();
$user->setEmail($input->getArgument('email'));
$user->setPlainPassword($input->getArgument('password'));
diff --git a/src/Controller/LoginController.php b/src/Controller/LoginController.php
index 34baae4..7a83d8c 100644
--- a/src/Controller/LoginController.php
+++ b/src/Controller/LoginController.php
@@ -109,11 +109,11 @@ class LoginController
return new JsonContent(['success' => true]);
}
- $user = $this->userRepository->getByEmail($this->request->post('email'));
+ $user = $this->userRepository->getByEmailOrUsername($this->request->post('email'));
if ($user === null || !$user->checkPassword($this->request->post('password'))) {
return new JsonContent([
'error' => [
- 'errorText' => 'No user found with the given email address or the given password is wrong. You can request->post('email')) . '" title="Request password reset">request password reset!'
]
]);
@@ -200,11 +200,11 @@ class LoginController
}
}
- $user = $this->userRepository->getByEmail($this->request->post('email'));
+ $user = $this->userRepository->getByEmailOrUsername($this->request->post('email'));
if ($user === null) {
return new JsonContent([
'error' => [
- 'errorText' => 'No user found with the given email address.'
+ 'errorText' => 'No user found with the given email address / username.'
]
]);
}
diff --git a/src/Controller/OAuthLoginController.php b/src/Controller/OAuthLoginController.php
index 6bee3b0..7a970d4 100644
--- a/src/Controller/OAuthLoginController.php
+++ b/src/Controller/OAuthLoginController.php
@@ -106,7 +106,12 @@ class OAuthLoginController
'exp' => (int)$token->getExpiresDate()->format('U'),
'nonce' => $token->getNonce(),
'sub' => $user->getId(),
- 'email' => $user->getEmail()
+ 'email' => $user->getEmail(),
+ 'username' => $user->getUsername(),
+ 'full_name' => $user->getFullName(),
+ 'nickname' => $user->getNickname(),
+ 'phone' => $user->getPhone(),
+ 'id_number' => $user->getIdNumber()
];
$privateKey = file_get_contents(ROOT . '/' . $_ENV['JWT_RSA_PRIVATE_KEY']);
$jwt = JWT::encode($payload, $privateKey, 'RS256');
diff --git a/src/Controller/UserController.php b/src/Controller/UserController.php
index 8110d52..84c8c73 100644
--- a/src/Controller/UserController.php
+++ b/src/Controller/UserController.php
@@ -13,6 +13,7 @@ use SokoWeb\Response\HtmlContent;
use SokoWeb\Response\JsonContent;
use SokoWeb\Response\Redirect;
use SokoWeb\Util\JwtParser;
+use RVR\Repository\UserRepository;
class UserController implements ISecured
{
@@ -20,10 +21,13 @@ class UserController implements ISecured
private PersistentDataManager $pdm;
+ private UserRepository $userRepository;
+
public function __construct(IRequest $request)
{
$this->request = $request;
$this->pdm = new PersistentDataManager();
+ $this->userRepository = new UserRepository();
}
public function authorize(): bool
@@ -126,8 +130,39 @@ class UserController implements ISecured
return new JsonContent(['error' => ['errorText' => $error]]);
}
- if (strlen($this->request->post('password_new')) > 0) {
- if (strlen($this->request->post('password_new')) < 6) {
+ $newEmail = $this->request->post('email');
+ if ($newEmail !== $user->getEmail()) {
+ if (!filter_var($newEmail, FILTER_VALIDATE_EMAIL)) {
+ return new JsonContent(['error' => ['errorText' => 'Please provide a valid email address.']]);
+ }
+
+ if ($this->userRepository->getByEmail($newEmail) !== null) {
+ return new JsonContent(['error' => ['errorText' => 'The given email address belongs to another account.']]);
+ }
+
+ $user->setEmail($newEmail);
+ }
+
+ $newUsername = $this->request->post('username');
+ if ($newUsername !== $user->getUsername()) {
+ if (strlen($newUsername) > 0) {
+ if (filter_var($newUsername, FILTER_VALIDATE_EMAIL)) {
+ return new JsonContent(['error' => ['errorText' => 'Please select a username that is not a valid email address.']]);
+ }
+
+ if ($this->userRepository->getByUsername($newUsername) !== null) {
+ return new JsonContent(['error' => ['errorText' => 'The given username is already taken.']]);
+ }
+
+ $user->setUsername($newUsername);
+ } else {
+ $user->setUsername(null);
+ }
+ }
+
+ $newPassword = $this->request->post('password_new');
+ if (strlen($newPassword) > 0) {
+ if (strlen($newPassword) < 6) {
return new JsonContent([
'error' => [
'errorText' => 'The given new password is too short. Please choose a password that is at least 6 characters long!'
@@ -135,7 +170,7 @@ class UserController implements ISecured
]);
}
- if ($this->request->post('password_new') !== $this->request->post('password_new_confirm')) {
+ if ($newPassword !== $this->request->post('password_new_confirm')) {
return new JsonContent([
'error' => [
'errorText' => 'The given new passwords do not match.'
@@ -143,9 +178,13 @@ class UserController implements ISecured
]);
}
- $user->setPlainPassword($this->request->post('password_new'));
+ $user->setPlainPassword($newPassword);
}
+ $user->setNickname($this->request->post('nickname'));
+ $user->setPhone($this->request->post('phone'));
+ $user->setIdNumber($this->request->post('id_number'));
+
$this->pdm->saveToDb($user);
$this->request->session()->delete('authenticated_with_google_until');
diff --git a/src/PersistentData/Model/User.php b/src/PersistentData/Model/User.php
index 1a284d4..d0af3a9 100644
--- a/src/PersistentData/Model/User.php
+++ b/src/PersistentData/Model/User.php
@@ -8,12 +8,14 @@ class User extends Model implements IUser
{
protected static string $table = 'users';
- protected static array $fields = ['email', 'password', 'type', 'google_sub', 'created'];
+ protected static array $fields = ['email', 'username', 'password', 'type', 'google_sub', 'created', 'full_name', 'nickname', 'phone', 'id_number'];
private static array $types = ['user', 'admin'];
private string $email = '';
+ private ?string $username = null;
+
private ?string $password = null;
private string $type = 'user';
@@ -22,11 +24,24 @@ class User extends Model implements IUser
private DateTime $created;
+ private string $fullName = '';
+
+ private string $nickname = '';
+
+ private string $phone = '';
+
+ private string $idNumber = '';
+
public function setEmail(string $email): void
{
$this->email = $email;
}
+ public function setUsername(?string $username): void
+ {
+ $this->username = $username;
+ }
+
public function setPassword(?string $hashedPassword): void
{
$this->password = $hashedPassword;
@@ -59,11 +74,36 @@ class User extends Model implements IUser
$this->created = new DateTime($created);
}
+ public function setFullName(string $fullName): void
+ {
+ $this->fullName = $fullName;
+ }
+
+ public function setNickname(string $nickname): void
+ {
+ $this->nickname = $nickname;
+ }
+
+ public function setPhone(string $phone): void
+ {
+ $this->phone = $phone;
+ }
+
+ public function setIdNumber(string $idNumber): void
+ {
+ $this->idNumber = $idNumber;
+ }
+
public function getEmail(): string
{
return $this->email;
}
+ public function getUsername(): ?string
+ {
+ return $this->username;
+ }
+
public function getPassword(): ?string
{
return $this->password;
@@ -89,6 +129,26 @@ class User extends Model implements IUser
return $this->created->format('Y-m-d H:i:s');
}
+ public function getFullName(): string
+ {
+ return $this->fullName;
+ }
+
+ public function getNickname(): string
+ {
+ return $this->nickname;
+ }
+
+ public function getPhone(): string
+ {
+ return $this->phone;
+ }
+
+ public function getIdNumber(): string
+ {
+ return $this->idNumber;
+ }
+
public function hasPermission(int $permission): bool
{
switch ($permission) {
@@ -108,7 +168,7 @@ class User extends Model implements IUser
public function getDisplayName(): string
{
- return $this->email;
+ return $this->nickname ?: $this->fullName;
}
public function checkPassword(string $password): bool
diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php
index cc03b44..3882d9d 100644
--- a/src/Repository/UserRepository.php
+++ b/src/Repository/UserRepository.php
@@ -27,6 +27,23 @@ class UserRepository implements IUserRepository
return $this->pdm->selectFromDb($select, User::class);
}
+ public function getByUsername(string $username): ?User
+ {
+ $select = new Select(\Container::$dbConnection);
+ $select->where('username', '=', $username);
+
+ return $this->pdm->selectFromDb($select, User::class);
+ }
+
+ public function getByEmailOrUsername(string $emailOrUsername): ?User
+ {
+ if (filter_var($emailOrUsername, FILTER_VALIDATE_EMAIL)) {
+ return $this->getByEmail($emailOrUsername);
+ }
+
+ return $this->getByUsername($emailOrUsername);
+ }
+
public function getByGoogleSub(string $sub): ?User
{
$select = new Select(\Container::$dbConnection);
diff --git a/views/account/account.php b/views/account/account.php
index 315c71b..b24e73d 100644
--- a/views/account/account.php
+++ b/views/account/account.php
@@ -5,11 +5,11 @@
@section(main)
Account
-
-
+
+
+
+
+
+
+
diff --git a/views/login/login.php b/views/login/login.php
index 6c3cc79..d8dabfb 100644
--- a/views/login/login.php
+++ b/views/login/login.php
@@ -4,7 +4,7 @@
Login